From 19842ed0c282c4ab8cba2b918136f3cadc6f4fe1 Mon Sep 17 00:00:00 2001 From: Meo597 <197331664+Meo597@users.noreply.github.com> Date: Wed, 14 Oct 2026 09:49:58 +0800 Subject: [PATCH] luar: whitelist --- common/geodata/lua.go | 2 +- common/geodata/lua_test.go | 6 ++++++ common/lua/luar.go | 31 ++++++++++++++++++++++++++++--- 3 files changed, 35 insertions(+), 4 deletions(-) diff --git a/common/geodata/lua.go b/common/geodata/lua.go index 43f7363ff..d74887d90 100644 --- a/common/geodata/lua.go +++ b/common/geodata/lua.go @@ -45,7 +45,7 @@ func RegisterLua(L *lua.LState) { "AnyMatch": luaIPAnyMatch, "Matches": luaIPMatches, "FilterIPs": newLuaIPFilterIPs(xlua.NewSlicePusher[net.IP](L)), - }) + }, "SetReverse", "ToggleReverse") return 1 })) diff --git a/common/geodata/lua_test.go b/common/geodata/lua_test.go index f66a57bc4..fafc49c76 100644 --- a/common/geodata/lua_test.go +++ b/common/geodata/lua_test.go @@ -78,6 +78,7 @@ local geodata = require("xray.geodata") local matcher = geodata.BuildIPMatcher("127.0.0.0/8") assert(matcher.Match == matcher.match and matcher.AnyMatch == matcher.anyMatch) assert(matcher.Matches == matcher.matches and matcher.FilterIPs == matcher.filterIPs) +assert(matcher.Reload == nil and matcher.reload == nil) assert(matcher:match(ip)) assert(matcher:anyMatch({ip}) and matcher:matches({ip})) assert(not matcher:AnyMatch(nil)) @@ -97,6 +98,11 @@ assert(matcher.missing == nil) local domain = geodata.BuildDomainMatcher("full:example.com") assert(domain.Match == domain.match and domain.MatchAny == domain.matchAny) +assert(domain.Reload == nil and domain.reload == nil) +for _, value in ipairs({matcher, domain}) do + assert(not pcall(function() return -value end)) + assert(not pcall(function() return value ^ {} end)) +end assert(domain:matchAny("example.com")) assert(#domain:Match("example.com") == 1) assert(domain:match("example.com")[1] == 0) diff --git a/common/lua/luar.go b/common/lua/luar.go index 072ee663f..8780db454 100644 --- a/common/lua/luar.go +++ b/common/lua/luar.go @@ -27,16 +27,37 @@ func NewSlicePusher[T any](L *glua.LState) func(*glua.LState, []T) { // it must leave the stack unchanged for the original luar wrapper. type DirectMethod func(L *glua.LState) (nresults int, handled bool) +// PushMethods pushes a luar userdata exposing only the named Go methods and +// their aliases through reflected calls. The method list is fixed by the first +// binding of that Go type in each LState. +func PushMethods(L *glua.LState, value any, methods ...string) { + PushWithDirectMethods(L, value, nil, methods...) +} + // PushWithDirectMethods pushes a luar userdata with typed Go method bindings. // Handled calls bypass luar's argument conversion and reflect.Call; method lookup // uses the methods table directly instead of luar's reflected __index handler. -// value must expose methods only. Bindings and their closures are installed once -// per Go type per LState, outside the method-call hot path. -func PushWithDirectMethods(L *glua.LState, value any, directMethods map[string]DirectMethod) { +// Only directMethods and additionalMethods are exposed, including their aliases. +// Bindings and their closures are installed once per Go type per LState, outside +// the method-call hot path. +func PushWithDirectMethods(L *glua.LState, value any, directMethods map[string]DirectMethod, additionalMethods ...string) { userdata := luar.New(L, value).(*glua.LUserData) metatable := userdata.Metatable.(*glua.LTable) methods := metatable.RawGetString("methods").(*glua.LTable) if metatable.RawGetString("__index") != methods { + // Keep all aliases for the explicitly exposed methods. + exposed := make(map[glua.LValue]bool, len(directMethods)+len(additionalMethods)) + for name := range directMethods { + exposed[methods.RawGetString(name)] = true + } + for _, name := range additionalMethods { + exposed[methods.RawGetString(name)] = true + } + for key, method := methods.Next(glua.LNil); key != glua.LNil; key, method = methods.Next(key) { + if !exposed[method] { + methods.RawSet(key, glua.LNil) + } + } for name, direct := range directMethods { original := methods.RawGetString(name) fn := L.NewFunction(func(L *glua.LState) int { @@ -52,6 +73,10 @@ func PushWithDirectMethods(L *glua.LState, value any, directMethods map[string]D } } } + // Pointer operations would bypass the method-only API. + metatable.RawSetString("__newindex", glua.LNil) + metatable.RawSetString("__pow", glua.LNil) + metatable.RawSetString("__unm", glua.LNil) metatable.RawSetString("__index", methods) } L.Push(userdata)