316 lines
8.8 KiB
C++
316 lines
8.8 KiB
C++
// lua_fuzzer.cpp - Random Lua code generator for security testing
|
|
// Milestone 19: Security Testing Suite
|
|
|
|
#include "lua_fuzzer.h"
|
|
#include "lua_sandbox.h"
|
|
#include <sstream>
|
|
#include <chrono>
|
|
|
|
namespace mosis {
|
|
|
|
LuaFuzzer::LuaFuzzer(uint32_t seed) {
|
|
if (seed == 0) {
|
|
seed = static_cast<uint32_t>(
|
|
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<int> 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<char>('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<char>(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<int>(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<int>(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<int>(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
|