mirror of
https://github.com/XTLS/Xray-core.git
synced 2026-10-15 12:04:06 +00:00
lua: prevent memory address leaks through sandbox string.format and refine comments
This commit is contained in:
+30
-8
@@ -77,7 +77,7 @@ func (p *Program) NewState(ctx context.Context, register func(*glua.LState), val
|
|||||||
register(L)
|
register(L)
|
||||||
}
|
}
|
||||||
if !p.privileged {
|
if !p.privileged {
|
||||||
// Host registration needs package.preload; scripts must not access the cache or loaders.
|
// Hide package internals to prevent data snooping and sandbox escape.
|
||||||
L.SetGlobal(glua.LoadLibName, glua.LNil)
|
L.SetGlobal(glua.LoadLibName, glua.LNil)
|
||||||
}
|
}
|
||||||
L.Push(L.NewFunctionFromProto(p.proto))
|
L.Push(L.NewFunctionFromProto(p.proto))
|
||||||
@@ -119,12 +119,9 @@ func newSandboxedState() *glua.LState {
|
|||||||
} {
|
} {
|
||||||
L.SetGlobal(name, glua.LNil)
|
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)
|
|
||||||
|
|
||||||
// Preserve native Stringer/error output without using Lua's address-based defaults.
|
// Prevent printing memory addresses that reveal the memory layout.
|
||||||
L.SetGlobal("tostring", L.NewFunction(func(L *glua.LState) int {
|
toString := func(value glua.LValue) glua.LString {
|
||||||
value := L.CheckAny(1)
|
|
||||||
result := "<opaque>"
|
result := "<opaque>"
|
||||||
switch value.Type() {
|
switch value.Type() {
|
||||||
case glua.LTNil, glua.LTBool, glua.LTNumber, glua.LTString:
|
case glua.LTNil, glua.LTBool, glua.LTNumber, glua.LTString:
|
||||||
@@ -137,11 +134,36 @@ func newSandboxedState() *glua.LState {
|
|||||||
result = native.Error()
|
result = native.Error()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
L.Push(glua.LString(result))
|
return glua.LString(result)
|
||||||
|
}
|
||||||
|
L.SetGlobal("tostring", L.NewFunction(func(L *glua.LState) int {
|
||||||
|
L.Push(toString(L.CheckAny(1)))
|
||||||
return 1
|
return 1
|
||||||
}))
|
}))
|
||||||
|
|
||||||
// Capture preload before package is hidden; require() reads loaders from the registry.
|
// Prevent printing memory addresses that reveal the memory layout.
|
||||||
|
stringLib := L.GetGlobal(glua.StringLibName).(*glua.LTable)
|
||||||
|
stringLib.RawSetString("dump", glua.LNil)
|
||||||
|
format := stringLib.RawGetString("format")
|
||||||
|
stringLib.RawSetString("format", L.NewFunction(func(L *glua.LState) int {
|
||||||
|
nargs := L.GetTop()
|
||||||
|
for i := 2; i <= nargs; i++ {
|
||||||
|
value := L.Get(i)
|
||||||
|
switch value.Type() {
|
||||||
|
case glua.LTBool, glua.LTNumber, glua.LTString:
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
L.Replace(i, toString(value))
|
||||||
|
}
|
||||||
|
L.Insert(format, 1)
|
||||||
|
L.Call(nargs, 1)
|
||||||
|
return 1
|
||||||
|
}))
|
||||||
|
|
||||||
|
// Prevent seed output from compromising cryptographic security.
|
||||||
|
L.GetGlobal(glua.MathLibName).(*glua.LTable).RawSetString("randomseed", glua.LNil)
|
||||||
|
|
||||||
|
// Prevent module injection through the global package table.
|
||||||
module := L.GetGlobal(glua.LoadLibName).(*glua.LTable)
|
module := L.GetGlobal(glua.LoadLibName).(*glua.LTable)
|
||||||
preload := module.RawGetString("preload").(*glua.LTable)
|
preload := module.RawGetString("preload").(*glua.LTable)
|
||||||
loaders := L.CreateTable(1, 0)
|
loaders := L.CreateTable(1, 0)
|
||||||
|
|||||||
@@ -689,6 +689,71 @@ string.__tostring = nil
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestProgramSandboxStringFormat(t *testing.T) {
|
||||||
|
program := compileTestProgram(t, `
|
||||||
|
assert(string.format == require("string").format and string.format == ("").format)
|
||||||
|
for _, format in ipairs({string.format, require("string").format, ("").format}) do
|
||||||
|
assert(format("%s %04d %.2f %t", "hello", 42, 1.25, true) == "hello 0042 1.25 true")
|
||||||
|
assert(format("%q", "hello") == '"hello"')
|
||||||
|
assert(format("%[2]s:%[1]d", 42, "hello") == "hello:42")
|
||||||
|
assert(format("%%p") == "%p")
|
||||||
|
assert(format("%%p", {}) == format("%%p", "<opaque>"))
|
||||||
|
assert(format("%s", 42) == "42")
|
||||||
|
assert(format("%s", nil) == "nil")
|
||||||
|
assert(format("%s", testIP) == "192.0.2.1")
|
||||||
|
assert(format("%#v", testIP) == '"192.0.2.1"')
|
||||||
|
assert(format("%p", testIP) == format("%p", "192.0.2.1"))
|
||||||
|
assert(format("%s", testNativeStringer) == "0xdeadbeef")
|
||||||
|
assert(format("%s", testNativeError) == "lookup failed")
|
||||||
|
assert(not pcall(format))
|
||||||
|
for _, value in ipairs({{}, function() end, format, testUserdata, testThread, testChannel}) do
|
||||||
|
assert(format("%s", value) == "<opaque>")
|
||||||
|
assert(format("%v", value) == "<opaque>")
|
||||||
|
assert(format("%q", value) == '"<opaque>"')
|
||||||
|
assert(format("%#v", value) == '"<opaque>"')
|
||||||
|
for _, pattern in ipairs({"%p", "%#p", "%+020p", "%[1]p", "%[1]p %#[1]v",
|
||||||
|
"%x", "%X", "%#x", "%d", "%t", "%w"}) do
|
||||||
|
local result = format(pattern, value)
|
||||||
|
assert(result == format(pattern, "<opaque>"), pattern .. ": " .. result)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
assert(format("%p", nil) == format("%p", "nil"))
|
||||||
|
end
|
||||||
|
assert(("%s"):format({}) == "<opaque>")
|
||||||
|
`)
|
||||||
|
L, err := program.NewState(context.Background(), func(L *glua.LState) {
|
||||||
|
L.SetGlobal("testIP", luar.New(L, net.ParseIP("192.0.2.1")))
|
||||||
|
PushUserData(L, bytes.NewBufferString("0xdeadbeef"))
|
||||||
|
L.SetGlobal("testNativeStringer", L.Get(-1))
|
||||||
|
L.Pop(1)
|
||||||
|
PushError(L, errors.New("lookup failed"))
|
||||||
|
L.SetGlobal("testNativeError", L.Get(-1))
|
||||||
|
L.Pop(1)
|
||||||
|
userdata := L.NewUserData()
|
||||||
|
metatable := L.NewTable()
|
||||||
|
metatable.RawSetString("__tostring", L.NewFunction(func(L *glua.LState) int {
|
||||||
|
t.Error("string.format must not call __tostring")
|
||||||
|
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 L.GetTop() != 0 {
|
||||||
|
t.Fatalf("stack top = %d, want 0", L.GetTop())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestProgramSandboxModuleFailures(t *testing.T) {
|
func TestProgramSandboxModuleFailures(t *testing.T) {
|
||||||
program := compileTestProgram(t, `
|
program := compileTestProgram(t, `
|
||||||
assert(package == nil)
|
assert(package == nil)
|
||||||
|
|||||||
Reference in New Issue
Block a user