Lua: Sandbox by default (whitelisted functions); Disable inline scripts for now; Add more checks for security reasons (#7136)

https://github.com/XTLS/Xray-core/pull/7136#issuecomment-6158632073
https://github.com/XTLS/Xray-core/pull/7136#issuecomment-6170831601
https://github.com/XTLS/Xray-core/pull/7136#issuecomment-6184362904
This commit is contained in:
Meow
2026-10-16 01:55:35 +00:00
committed by RPRX
parent 8703fdb101
commit ac5d8a83ba
30 changed files with 2209 additions and 56 deletions
+1 -1
View File
@@ -52,7 +52,7 @@ func luaServers(s *DNS) []luaDNSServer {
func registerLua(L *lua.LState, servers []luaDNSServer, client featureDNS.Client) {
L.PreloadModule("xray.dns", func(L *lua.LState) int {
pushIPs := xlua.NewSlicePusher[net.IP](L)
pushIPs := xlua.NewReadOnlySlicePusher[net.IP](L)
serverList := L.CreateTable(len(servers), 0)
for i, client := range servers {
+1
View File
@@ -30,6 +30,7 @@ func newScriptEngine(script string) (*scriptEngine, error) {
func (e *scriptEngine) start(server *DNS) error {
pool, err := xlua.NewPool(server.ctx, scriptExecutionTimeout, e.program.NewStateFactory(
scriptExecutionTimeout*20,
256,
func(L *lua.LState) {
geodata.RegisterLua(L)
log.RegisterLua(L)
+7
View File
@@ -23,6 +23,9 @@ import (
const idleTimeout = 30 * time.Second
// DO NOT EDIT. Ignored by geodata, rejected by Lua.
const luaGuardHeader = "\x12\x01\x00"
type stage struct {
target string
temp string
@@ -170,6 +173,10 @@ func (d *downloader) downloadOne(asset *Asset) (stage, error) {
}
}()
if _, err := temp.WriteString(luaGuardHeader); err != nil {
temp.Close()
return stage{}, err
}
if err := d.fetch(asset.Url, temp); err != nil {
temp.Close()
return stage{}, err
+73
View File
@@ -0,0 +1,73 @@
package geodata
import (
"context"
"errors"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"testing"
commongeodata "github.com/xtls/xray-core/common/geodata"
xlua "github.com/xtls/xray-core/common/lua"
"github.com/yuin/gopher-lua/parse"
"google.golang.org/protobuf/proto"
)
func TestDownloadOneLuaGuard(t *testing.T) {
for _, tc := range []struct {
name string
body string
code string
}{
{"geodata", "\x0a\x03\x0a\x01A", "A"},
{"prefixed geodata", luaGuardHeader + "\x0a\x03\x0a\x01A", "A"},
{"Lua", "function HandleRoute() end", ""},
} {
t.Run(tc.name, func(t *testing.T) {
dir := t.TempDir()
t.Setenv("xray.location.asset", dir)
if err := os.WriteFile(filepath.Join(dir, "asset.lua"), nil, 0o600); err != nil {
t.Fatal(err)
}
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
_, _ = w.Write([]byte(tc.body))
}))
defer server.Close()
d := &downloader{ctx: context.Background(), httpClient: server.Client()}
staged, err := d.downloadOne(&Asset{Url: server.URL, File: "asset.lua"})
if err != nil {
t.Fatal(err)
}
tx, err := swapAll([]stage{staged})
if err != nil {
t.Fatal(err)
}
if err := tx.commit(); err != nil {
t.Fatal(err)
}
data, err := os.ReadFile(staged.target)
if err != nil || string(data) != luaGuardHeader+tc.body {
t.Fatalf("downloaded data = % x, %v", data, err)
}
program, err := xlua.Compile(staged.target)
var syntaxErr *parse.Error
if program != nil || !errors.As(err, &syntaxErr) || syntaxErr.Pos.Line != 1 || syntaxErr.Pos.Column != 1 {
t.Fatalf("want Lua rejection at the first byte, got %v, %v", program, err)
}
if tc.code != "" {
var list commongeodata.GeoSiteList
if err := proto.Unmarshal(data, &list); err != nil {
t.Fatal(err)
}
if len(list.Entry) != 1 || list.Entry[0].Code != tc.code {
t.Fatalf("header changed geodata: %v", &list)
}
if _, err := commongeodata.ParseDomainRules([]string{"ext:" + filepath.Base(staged.target) + ":" + tc.code}, commongeodata.Domain_Full); err != nil {
t.Fatalf("geodata loader rejected the header: %v", err)
}
}
})
}
}
+1 -1
View File
@@ -62,7 +62,7 @@ func (r *Router) RegisterLua(L *lua.LState) {
}
func registerLuaContext(L *lua.LState) {
pushIPs := xlua.NewSlicePusher[net.IP](L)
pushIPs := xlua.NewReadOnlySlicePusher[net.IP](L)
attributes := L.NewTypeMetatable(luaAttributesType)
L.SetField(attributes, "__index", L.NewFunction(func(L *lua.LState) int {
values := L.CheckUserData(1).Value.(map[string]string)
+1
View File
@@ -31,6 +31,7 @@ func newScriptEngine(script string) (*scriptEngine, error) {
func (e *scriptEngine) start(router *Router) error {
pool, err := xlua.NewPool(router.ctx, scriptExecutionTimeout, e.program.NewStateFactory(
scriptExecutionTimeout*20,
256,
func(L *lua.LState) {
geodata.RegisterLua(L)
log.RegisterLua(L)