add Lua sandbox with timer system (milestones 1-5 complete)

This commit is contained in:
2026-01-18 14:28:44 +01:00
parent 2c36ac005d
commit a4ecb0f132
36 changed files with 10884 additions and 0 deletions

View File

@@ -0,0 +1,11 @@
-- Test module for SafeRequire tests
local M = {}
M.value = 42
M.name = "test_module"
function M.add(a, b)
return a + b
end
return M

View File

@@ -0,0 +1,5 @@
-- This script tests that text loading works
-- The actual bytecode rejection test is done from C++ side
-- by attempting to load a bytecode string directly
print("PASS: Text loading works")

View File

@@ -0,0 +1,12 @@
-- This script runs an infinite loop
-- It should be stopped by the instruction limit hook
local count = 0
while true do
count = count + 1
-- This loop should be interrupted by instruction hook
end
-- Should never reach here
error("FAIL: CPU limit not enforced - loop completed")

View File

@@ -0,0 +1,26 @@
-- Test that dangerous globals are nil
-- This script should run successfully if sandbox is properly configured
-- Note: 'require' is intentionally NOT in this list because the sandbox
-- provides a safe version when app_path is configured
local dangerous = {
"os", "io", "debug", "package", "ffi", "jit",
"dofile", "loadfile", "load", "loadstring",
"rawget", "rawset", "rawequal", "rawlen",
"collectgarbage", "newproxy"
}
local failed = {}
for _, name in ipairs(dangerous) do
local value = _G[name]
if value ~= nil then
table.insert(failed, name .. " (is " .. type(value) .. ")")
end
end
if #failed > 0 then
error("FAIL: These globals should be nil: " .. table.concat(failed, ", "))
end
print("PASS: All dangerous globals removed")

View File

@@ -0,0 +1,20 @@
-- This script intentionally tries to exhaust memory
-- When run with a 512KB limit, it should fail before completing
local t = {}
local i = 0
while true do
i = i + 1
-- Each string is 100KB
t[i] = string.rep("x", 100000)
-- Safety check - if we get past 100 iterations with 512KB limit,
-- something is wrong
if i > 100 then
error("FAIL: Should have hit memory limit by now (allocated ~10MB)")
end
end
-- Should never reach here
error("FAIL: Memory limit not enforced")

View File

@@ -0,0 +1,33 @@
-- Test that metatables are protected from manipulation
-- Test 1: String metatable should return protection value, not actual metatable
local mt = getmetatable("")
if mt ~= "string" then
error("FAIL: string metatable should return 'string', got " .. tostring(mt))
end
-- Test 2: Cannot add new globals
local ok, err = pcall(function()
_G.my_new_global = "test"
end)
if ok then
error("FAIL: Should not be able to add new globals")
end
-- Test 3: Cannot modify existing globals
local ok2, err2 = pcall(function()
_G.print = nil
end)
if ok2 then
error("FAIL: Should not be able to modify print")
end
-- Test 4: Cannot replace math table
local ok3, err3 = pcall(function()
_G.math = {}
end)
if ok3 then
error("FAIL: Should not be able to replace math")
end
print("PASS: Metatables protected")

View File

@@ -0,0 +1,158 @@
-- Test that safe/normal Lua operations still work correctly
local function check(cond, msg)
if not cond then
error("FAIL: " .. msg)
end
end
-- ============================================
-- MATH OPERATIONS
-- ============================================
local x = math.sin(1.5) + math.floor(3.7)
check(type(x) == "number", "Math operations failed")
check(math.abs(-5) == 5, "math.abs failed")
check(math.max(1, 2, 3) == 3, "math.max failed")
check(math.min(1, 2, 3) == 1, "math.min failed")
check(math.floor(3.9) == 3, "math.floor failed")
check(math.ceil(3.1) == 4, "math.ceil failed")
-- ============================================
-- STRING OPERATIONS
-- ============================================
local s = string.format("hello %d", 42)
check(s == "hello 42", "string.format failed")
local upper = string.upper("test")
check(upper == "TEST", "string.upper failed")
local lower = string.lower("TEST")
check(lower == "test", "string.lower failed")
local sub = string.sub("hello", 2, 4)
check(sub == "ell", "string.sub failed")
local len = string.len("hello")
check(len == 5, "string.len failed")
local rep = string.rep("ab", 3)
check(rep == "ababab", "string.rep failed")
local rev = string.reverse("hello")
check(rev == "olleh", "string.reverse failed")
-- ============================================
-- TABLE OPERATIONS
-- ============================================
local t = {1, 2, 3}
table.insert(t, 4)
check(#t == 4, "table.insert failed")
check(t[4] == 4, "table.insert value failed")
local removed = table.remove(t)
check(removed == 4, "table.remove failed")
check(#t == 3, "table.remove length failed")
local t2 = {3, 1, 2}
table.sort(t2)
check(t2[1] == 1 and t2[2] == 2 and t2[3] == 3, "table.sort failed")
local concat = table.concat({"a", "b", "c"}, ",")
check(concat == "a,b,c", "table.concat failed")
-- ============================================
-- ITERATION
-- ============================================
local count = 0
for i, v in ipairs({1, 2, 3, 4}) do
count = count + 1
end
check(count == 4, "ipairs iteration failed")
count = 0
for k, v in pairs({a=1, b=2, c=3}) do
count = count + 1
end
check(count == 3, "pairs iteration failed")
-- next function
local t3 = {a=1, b=2}
local k, v = next(t3)
check(k ~= nil and v ~= nil, "next function failed")
-- ============================================
-- ERROR HANDLING
-- ============================================
local ok, err = pcall(function()
error("test error")
end)
check(not ok, "pcall should return false for error")
check(err:find("test error"), "Error message should contain 'test error'")
local ok2, result = pcall(function()
return 42
end)
check(ok2 and result == 42, "pcall should return success value")
-- xpcall with traceback
local ok3, err3 = xpcall(function()
error("xpcall test")
end, function(e)
return "caught: " .. tostring(e)
end)
check(not ok3, "xpcall should return false for error")
check(err3:find("caught"), "xpcall error handler should run")
-- ============================================
-- TYPE CHECKS
-- ============================================
check(type({}) == "table", "type table failed")
check(type("") == "string", "type string failed")
check(type(123) == "number", "type number failed")
check(type(true) == "boolean", "type boolean failed")
check(type(nil) == "nil", "type nil failed")
check(type(function() end) == "function", "type function failed")
-- ============================================
-- CONVERSION
-- ============================================
check(tonumber("42") == 42, "tonumber string failed")
check(tonumber("3.14") == 3.14, "tonumber float failed")
check(tonumber("abc") == nil, "tonumber invalid failed")
check(tonumber(42) == 42, "tonumber number failed")
check(tostring(42) == "42", "tostring number failed")
check(tostring(true) == "true", "tostring boolean failed")
check(type(tostring({})) == "string", "tostring table failed")
-- ============================================
-- SELECT
-- ============================================
local a, b = select(2, 1, 2, 3)
check(a == 2 and b == 3, "select failed")
check(select("#", 1, 2, 3, 4) == 4, "select # failed")
-- ============================================
-- ASSERT
-- ============================================
local ok4, err4 = pcall(function()
assert(true, "should not fail")
end)
check(ok4, "assert true failed")
local ok5, err5 = pcall(function()
assert(false, "intentional fail")
end)
check(not ok5, "assert false should fail")
check(err5:find("intentional fail"), "assert message wrong")
-- ============================================
-- UTF8 (if available)
-- ============================================
if utf8 then
local len = utf8.len("hello")
check(len == 5, "utf8.len failed")
end
print("PASS: All safe operations work correctly")

View File

@@ -0,0 +1,18 @@
-- Test that string.dump is removed
-- string.dump can be used to create bytecode from functions,
-- which could be used to bypass sandbox restrictions
if string.dump ~= nil then
error("FAIL: string.dump should be nil but exists")
end
-- Also verify string table exists and other functions work
if string.upper == nil then
error("FAIL: string.upper should exist")
end
if string.format == nil then
error("FAIL: string.format should exist")
end
print("PASS: string.dump removed, other string functions intact")