// lua_fuzzer.cpp - Random Lua code generator for security testing // Milestone 19: Security Testing Suite #include "lua_fuzzer.h" #include "lua_sandbox.h" #include #include namespace mosis { LuaFuzzer::LuaFuzzer(uint32_t seed) { if (seed == 0) { seed = static_cast( std::chrono::steady_clock::now().time_since_epoch().count()); } m_rng.seed(seed); } LuaFuzzer::~LuaFuzzer() = default; int LuaFuzzer::RandomInt(int min, int max) { std::uniform_int_distribution dist(min, max); return dist(m_rng); } bool LuaFuzzer::RandomBool() { return RandomInt(0, 1) == 1; } char LuaFuzzer::RandomChar() { static const char chars[] = "abcdefghijklmnopqrstuvwxyz_"; return chars[RandomInt(0, sizeof(chars) - 2)]; } std::string LuaFuzzer::GenerateIdentifier() { int len = RandomInt(1, 8); std::string id; id += RandomChar(); // First char must be letter or underscore for (int i = 1; i < len; i++) { if (RandomBool()) { id += RandomChar(); } else { id += static_cast('0' + RandomInt(0, 9)); } } return id; } std::string LuaFuzzer::GenerateLiteral() { int type = RandomInt(0, 4); switch (type) { case 0: // nil return "nil"; case 1: // boolean return RandomBool() ? "true" : "false"; case 2: // integer return std::to_string(RandomInt(-1000, 1000)); case 3: // float return std::to_string(RandomInt(-100, 100)) + "." + std::to_string(RandomInt(0, 99)); case 4: // string { int len = RandomInt(0, 20); std::string s = "\""; for (int i = 0; i < len; i++) { char c = static_cast(RandomInt(32, 126)); if (c == '"' || c == '\\') { s += '\\'; } s += c; } s += "\""; return s; } default: return "nil"; } } std::string LuaFuzzer::GenerateOperator() { static const char* ops[] = { "+", "-", "*", "/", "//", "%", "^", "==", "~=", "<", ">", "<=", ">=", "and", "or", ".." }; return ops[RandomInt(0, sizeof(ops)/sizeof(ops[0]) - 1)]; } std::string LuaFuzzer::GenerateTableConstructor(int depth) { if (depth > static_cast(m_max_nesting)) { return "{}"; } int count = RandomInt(0, 5); if (count == 0) { return "{}"; } std::ostringstream ss; ss << "{"; for (int i = 0; i < count; i++) { if (i > 0) ss << ", "; int style = RandomInt(0, 2); switch (style) { case 0: // array style ss << GenerateExpression(depth + 1); break; case 1: // record style ss << GenerateIdentifier() << " = " << GenerateExpression(depth + 1); break; case 2: // general style ss << "[" << GenerateExpression(depth + 1) << "] = " << GenerateExpression(depth + 1); break; } } ss << "}"; return ss.str(); } std::string LuaFuzzer::GenerateFunctionCall(int depth) { // Use only very safe built-in functions static const char* funcs[] = { "tostring", "tonumber", "type" }; std::ostringstream ss; ss << funcs[RandomInt(0, sizeof(funcs)/sizeof(funcs[0]) - 1)]; ss << "("; ss << GenerateLiteral(); // Just use literals, not recursive expressions ss << ")"; return ss.str(); } std::string LuaFuzzer::GenerateExpression(int depth) { if (depth > static_cast(m_max_nesting)) { return GenerateLiteral(); } int type = RandomInt(0, 7); switch (type) { case 0: case 1: return GenerateLiteral(); case 2: return GenerateIdentifier(); case 3: // binary op return "(" + GenerateExpression(depth + 1) + " " + GenerateOperator() + " " + GenerateExpression(depth + 1) + ")"; case 4: // unary op { static const char* unary[] = {"-", "not ", "#"}; return std::string(unary[RandomInt(0, 2)]) + "(" + GenerateExpression(depth + 1) + ")"; } case 5: return GenerateTableConstructor(depth + 1); case 6: return GenerateFunctionCall(depth + 1); case 7: // table access return GenerateIdentifier() + "[" + GenerateExpression(depth + 1) + "]"; default: return GenerateLiteral(); } } std::string LuaFuzzer::GenerateStatement(int depth) { if (depth > static_cast(m_max_nesting)) { return "local _ = nil"; } int type = RandomInt(0, 5); std::ostringstream ss; switch (type) { case 0: // local assignment ss << "local " << GenerateIdentifier() << " = " << GenerateLiteral(); break; case 1: // assignment (to local) ss << "local " << GenerateIdentifier() << " = " << GenerateExpression(depth + 1); break; case 2: // function call statement ss << GenerateFunctionCall(depth + 1); break; case 3: // if statement (simple) ss << "if " << GenerateLiteral() << " then\n" << " local _ = " << GenerateLiteral() << "\n" << "end"; break; case 4: // for numeric (small) ss << "for " << GenerateIdentifier() << " = 1, " << RandomInt(1, 5) << " do\n" << " local _ = " << GenerateLiteral() << "\n" << "end"; break; case 5: // do block ss << "do\n" << " local " << GenerateIdentifier() << " = " << GenerateLiteral() << "\n" << "end"; break; default: ss << "local _ = nil"; } return ss.str(); } std::string LuaFuzzer::GenerateRandomCode() { std::ostringstream ss; // Generate 1-5 statements int count = RandomInt(1, 5); for (int i = 0; i < count; i++) { ss << GenerateStatement(0) << "\n"; } // Optionally add a return if (RandomBool()) { ss << "return " << GenerateExpression(0) << "\n"; } return ss.str(); } bool LuaFuzzer::VerifySandboxIntegrity() { // Create a fresh sandbox and verify basic operations work SandboxContext ctx{ .app_id = "fuzzer.verify", .app_path = ".", .permissions = {}, .is_system_app = false }; LuaSandbox sandbox(ctx); // Test basic arithmetic if (!sandbox.LoadString("return 1 + 1", "verify1")) { return false; } // Test string operations if (!sandbox.LoadString("return string.len('test')", "verify2")) { return false; } // Test table operations if (!sandbox.LoadString("local t = {1,2,3}; return #t", "verify3")) { return false; } // Verify dangerous functions are still blocked if (sandbox.LoadString("return os.execute", "verify_os")) { return false; // os.execute should not exist } if (sandbox.LoadString("return io.open", "verify_io")) { return false; // io.open should not exist } return true; } FuzzResult LuaFuzzer::Run(size_t iterations) { FuzzResult result; result.iterations = iterations; for (size_t i = 0; i < iterations; i++) { m_total_runs++; // Generate random code std::string code = GenerateRandomCode(); // Create sandbox with limits SandboxContext ctx{ .app_id = "fuzzer.test", .app_path = ".", .permissions = {}, .is_system_app = false }; SandboxLimits limits; limits.memory_bytes = 10 * 1024 * 1024; // 10MB limit limits.instructions_per_call = 100000; // 100k instruction limit try { LuaSandbox sandbox(ctx, limits); // Execute bool ok = sandbox.LoadString(code, "fuzz_" + std::to_string(i)); if (!ok) { m_errors_caught++; result.errors_caught++; // This is expected - random code often has errors } } catch (const std::exception& e) { // Caught exception - not a crash, but unexpected m_errors_caught++; result.errors_caught++; result.error = std::string("Exception: ") + e.what(); } catch (...) { // Unknown exception - this is bad m_crashes++; result.crashed = true; result.error = "Unknown exception caught"; return result; } } // Verify sandbox is still working correctly result.sandbox_intact = VerifySandboxIntegrity(); return result; } } // namespace mosis