From ef4f4888df9ee086c3b60fbfe9573a5ce7c88d3d Mon Sep 17 00:00:00 2001 From: Meo597 <197331664+Meo597@users.noreply.github.com> Date: Thu, 15 Oct 2026 04:33:35 +0800 Subject: [PATCH] lua: allow os time functions in sandbox --- common/lua/program.go | 11 +++++++++++ common/lua/program_test.go | 18 +++++++++++++----- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/common/lua/program.go b/common/lua/program.go index 33b9a9a5f..b166829b7 100644 --- a/common/lua/program.go +++ b/common/lua/program.go @@ -107,6 +107,7 @@ func newSandboxedState() *glua.LState { {glua.TabLibName, glua.OpenTable}, {glua.StringLibName, glua.OpenString}, {glua.MathLibName, glua.OpenMath}, + {glua.OsLibName, glua.OpenOs}, } { L.Push(L.NewFunction(lib.open)) L.Push(glua.LString(lib.name)) @@ -194,6 +195,16 @@ func newSandboxedState() *glua.LState { // Prevent seed output from compromising cryptographic security. L.GetGlobal(glua.MathLibName).(*glua.LTable).RawSetString("randomseed", glua.LNil) + // Keep only time functions, including when os is accessed through require. + osLib := L.GetGlobal(glua.OsLibName).(*glua.LTable) + osLib.ForEach(func(key, _ glua.LValue) { + switch key { + case glua.LString("clock"), glua.LString("date"), glua.LString("difftime"), glua.LString("time"): + default: + osLib.RawSet(key, glua.LNil) + } + }) + // Prevent module injection through the global package table. module := L.GetGlobal(glua.LoadLibName).(*glua.LTable) preload := module.RawGetString("preload").(*glua.LTable) diff --git a/common/lua/program_test.go b/common/lua/program_test.go index 38d9468bb..c18b69466 100644 --- a/common/lua/program_test.go +++ b/common/lua/program_test.go @@ -553,17 +553,25 @@ func TestProgramSandbox(t *testing.T) { local function check() assert(package == nil) assert(require("_G") == _G) - for _, name in ipairs({"io", "os", "debug", "channel", "coroutine", "dofile", "loadfile", + for _, name in ipairs({"io", "debug", "channel", "ch", "coroutine", "dofile", "loadfile", "load", "loadstring", "getfenv", "setfenv", "collectgarbage", "module", "getmetatable", "setmetatable", "rawget", "rawset", "newproxy", "print", "_printregs"}) do assert(_G[name] == nil, name) end - for _, name in ipairs({"io", "os", "debug", "channel", "coroutine", "package", "escape"}) do + for _, name in ipairs({"io", "debug", "channel", "ch", "coroutine", "package", "escape"}) do assert(not pcall(require, name), name) end - for _, name in ipairs({"math", "string", "table"}) do + for _, name in ipairs({"math", "string", "table", "os"}) do assert(require(name) == _G[name], name) end + for _, lib in ipairs({os, require("os")}) do + for _, name in ipairs({"clock", "date", "difftime", "time"}) do + assert(type(lib[name]) == "function", "os." .. name) + end + for _, name in ipairs({"execute", "exit", "getenv", "remove", "rename", "setenv", "setlocale", "tmpname"}) do + assert(lib[name] == nil, "os." .. name) + end + 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") @@ -1144,8 +1152,8 @@ func TestProgramPrivilegesAreCompileTimeSnapshot(t *testing.T) { } check := func(L *glua.LState) { t.Helper() - if got := L.GetGlobal("os").Type() == glua.LTTable; got != privileged { - t.Fatalf("os available = %t, want compile-time value %t", got, privileged) + if got := L.GetField(L.GetGlobal("os"), "execute").Type() == glua.LTFunction; got != privileged { + t.Fatalf("os.execute available = %t, want compile-time value %t", got, privileged) } } factory := program.NewStateFactory(time.Second, nil, nil)