From 34efc32ff0872680efa0afc471cd59c7b26f8578 Mon Sep 17 00:00:00 2001 From: Meo597 <197331664+Meo597@users.noreply.github.com> Date: Wed, 14 Oct 2026 09:16:32 +0800 Subject: [PATCH] sandbox: disable more func --- common/lua/program.go | 16 +++++++++- common/lua/program_test.go | 64 ++++++++++++++++++++++++++++++++++---- 2 files changed, 73 insertions(+), 7 deletions(-) diff --git a/common/lua/program.go b/common/lua/program.go index 2bdd78311..5af1eb1d0 100644 --- a/common/lua/program.go +++ b/common/lua/program.go @@ -102,7 +102,6 @@ func newSandboxedState() *glua.LState { {glua.TabLibName, glua.OpenTable}, {glua.StringLibName, glua.OpenString}, {glua.MathLibName, glua.OpenMath}, - {glua.CoroutineLibName, glua.OpenCoroutine}, } { L.Push(L.NewFunction(lib.open)) L.Push(glua.LString(lib.name)) @@ -111,10 +110,25 @@ func newSandboxedState() *glua.LState { for _, name := range []string{ "dofile", "loadfile", "load", "loadstring", "getfenv", "setfenv", + "getmetatable", "setmetatable", "rawget", "rawset", "collectgarbage", "module", "newproxy", "print", "_printregs", } { L.SetGlobal(name, glua.LNil) } + L.GetGlobal(glua.StringLibName).(*glua.LTable).RawSetString("dump", glua.LNil) + L.GetGlobal(glua.MathLibName).(*glua.LTable).RawSetString("randomseed", glua.LNil) + + // Hide object addresses without invoking __tostring. + L.SetGlobal("tostring", L.NewFunction(func(L *glua.LState) int { + value := L.CheckAny(1) + switch value.Type() { + case glua.LTNil, glua.LTBool, glua.LTNumber, glua.LTString: + L.Push(glua.LString(value.String())) + default: + L.Push(glua.LString("")) + } + return 1 + })) // Capture preload before package is hidden; require() reads loaders from the registry. module := L.GetGlobal(glua.LoadLibName).(*glua.LTable) diff --git a/common/lua/program_test.go b/common/lua/program_test.go index d436201de..a78541e21 100644 --- a/common/lua/program_test.go +++ b/common/lua/program_test.go @@ -528,17 +528,19 @@ func TestProgramSandbox(t *testing.T) { local function check() assert(package == nil) assert(require("_G") == _G) - for _, name in ipairs({"io", "os", "debug", "channel", "dofile", "loadfile", + for _, name in ipairs({"io", "os", "debug", "channel", "coroutine", "dofile", "loadfile", "load", "loadstring", "getfenv", "setfenv", "collectgarbage", "module", - "newproxy", "print", "_printregs"}) do + "getmetatable", "setmetatable", "rawget", "rawset", "newproxy", "print", "_printregs"}) do assert(_G[name] == nil, name) end - for _, name in ipairs({"io", "os", "debug", "channel", "package", "escape"}) do + for _, name in ipairs({"io", "os", "debug", "channel", "coroutine", "package", "escape"}) do assert(not pcall(require, name), name) end - for _, name in ipairs({"math", "string", "table", "coroutine"}) do + for _, name in ipairs({"math", "string", "table"}) do assert(require(name) == _G[name], name) end + assert(string.dump == nil and ("").dump == nil and require("string").dump == nil) + assert(math.randomseed == nil and require("math").randomseed == nil) local helper = require("xray.test") assert(helper.value == "ok" and helper == require("xray.test")) assert(math.abs(-2) == 2 and string.upper("ok") == "OK") @@ -546,8 +548,7 @@ local function check() end check() function CheckSandbox() - local thread = coroutine.create(check) - assert(coroutine.resume(thread)) + check() local injected = false package = { preload = { @@ -599,6 +600,52 @@ end`) } } +func TestProgramSandboxToString(t *testing.T) { + program := compileTestProgram(t, ` +assert(tostring == require("_G").tostring) +for _, convert in ipairs({tostring, require("_G").tostring}) do + assert(convert(nil) == "nil") + assert(convert(true) == "true" and convert(false) == "false") + assert(convert(0) == "0" and convert(-42) == "-42" and convert(1.25) == "1.25") + assert(convert("") == "" and convert("hello") == "hello") + assert(convert(42, {}) == "42") + assert(not pcall(convert)) + for _, value in ipairs({{}, function() end, convert, testUserdata, testThread, testChannel}) do + local ok, result = pcall(convert, value) + assert(ok and result == "") + end +end +string.__tostring = function() error("string __tostring must not run") end +assert(tostring("hello") == "hello") +string.__tostring = nil +`) + var metamethodCalls int + L, err := program.NewState(context.Background(), func(L *glua.LState) { + userdata := L.NewUserData() + metatable := L.NewTable() + metatable.RawSetString("__tostring", L.NewFunction(func(L *glua.LState) int { + metamethodCalls++ + L.Push(glua.LString("custom")) + return 1 + })) + L.SetMetatable(userdata, metatable) + L.SetGlobal("testUserdata", userdata) + thread, cancel := L.NewThread() + if cancel != nil { + t.Cleanup(cancel) + } + L.SetGlobal("testThread", thread) + L.SetGlobal("testChannel", glua.LChannel(make(chan glua.LValue))) + }, nil) + if err != nil { + t.Fatal(err) + } + defer L.Close() + if metamethodCalls != 0 { + t.Fatalf("__tostring calls = %d, want 0", metamethodCalls) + } +} + func TestProgramSandboxModuleFailures(t *testing.T) { program := compileTestProgram(t, ` assert(package == nil) @@ -705,6 +752,11 @@ assert(type(coroutine.create) == "function" and require("coroutine") == coroutin assert(type(dofile) == "function" and type(loadfile) == "function") assert(type(getfenv) == "function" and type(setfenv) == "function") assert(type(collectgarbage) == "function" and type(print) == "function") +assert(type(getmetatable) == "function" and type(setmetatable) == "function") +assert(type(rawget) == "function" and type(rawset) == "function") +assert(tostring(42) == "42" and type(tostring({})) == "string") +assert(tostring(setmetatable({}, {__tostring = function() return "custom" end})) == "custom") +assert(type(string.dump) == "function" and type(math.randomseed) == "function") assert(type(package.loaders[2]) == "function" and type(package.path) == "string") assert(type(debug.traceback()) == "string" and type(os.time()) == "number") assert(assert(loadstring("return 42"))() == 42)