lua: validate script extension after resolving symlinks

This commit is contained in:
Meo597
2026-10-14 23:12:30 +08:00
parent bd28a87f0b
commit e04191e65b
4 changed files with 85 additions and 9 deletions
+10 -3
View File
@@ -40,11 +40,18 @@ func TestDownloadOneLuaGuard(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
data, err := os.ReadFile(staged.temp) 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 { if err != nil || string(data) != luaGuardHeader+tc.body {
t.Fatalf("downloaded data = % x, %v", data, err) t.Fatalf("downloaded data = % x, %v", data, err)
} }
program, err := xlua.Compile(staged.temp) program, err := xlua.Compile(staged.target)
var syntaxErr *parse.Error var syntaxErr *parse.Error
if program != nil || !errors.As(err, &syntaxErr) || syntaxErr.Pos.Line != 1 || syntaxErr.Pos.Column != 1 { 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) t.Fatalf("want Lua rejection at the first byte, got %v, %v", program, err)
@@ -57,7 +64,7 @@ func TestDownloadOneLuaGuard(t *testing.T) {
if len(list.Entry) != 1 || list.Entry[0].Code != tc.code { if len(list.Entry) != 1 || list.Entry[0].Code != tc.code {
t.Fatalf("header changed geodata: %v", &list) t.Fatalf("header changed geodata: %v", &list)
} }
if _, err := commongeodata.ParseDomainRules([]string{"ext:" + filepath.Base(staged.temp) + ":" + tc.code}, commongeodata.Domain_Full); err != nil { 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) t.Fatalf("geodata loader rejected the header: %v", err)
} }
} }
+21 -4
View File
@@ -21,10 +21,7 @@ import (
) )
func TestProgramStatesAreIndependent(t *testing.T) { func TestProgramStatesAreIndependent(t *testing.T) {
path := filepath.Join(t.TempDir(), "state.lua") path := writeTestScript(t, "value = (value or 0) + 1")
if err := os.WriteFile(path, []byte("value = (value or 0) + 1"), 0o600); err != nil {
t.Fatal(err)
}
program, err := Compile(path) program, err := Compile(path)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@@ -191,6 +188,22 @@ func TestCompileInlineDiagnostics(t *testing.T) {
} }
*/ */
func TestCompileFileExtension(t *testing.T) {
for _, name := range []string{"script", "script.dat", "script.lua.txt", "script.LUA"} {
path := filepath.Join(t.TempDir(), name)
if err := os.WriteFile(path, []byte("value = 42"), 0o600); err != nil {
t.Fatal(err)
}
if program, err := Compile(path); program != nil || err == nil || !strings.Contains(err.Error(), "Lua file extension must be .lua") {
t.Fatalf("Compile(%q) = %v, %v; want nil program and .lua extension error", path, program, err)
}
}
path := writeTestScript(t, "value = 42") + string(filepath.Separator) + "."
if program, err := Compile(path); program == nil || err != nil {
t.Fatalf("Compile(%q) = %v, %v; want acceptance of the resolved .lua path", path, program, err)
}
}
func TestCompileRejectsReadFailure(t *testing.T) { func TestCompileRejectsReadFailure(t *testing.T) {
path := filepath.Join(t.TempDir(), "unreadable.lua") path := filepath.Join(t.TempDir(), "unreadable.lua")
if err := os.WriteFile(path, []byte("value = 42"), 0o600); err != nil { if err := os.WriteFile(path, []byte("value = 42"), 0o600); err != nil {
@@ -255,6 +268,10 @@ func writeTestScript(t *testing.T, code string) string {
if err := os.WriteFile(path, []byte(code), 0o600); err != nil { if err := os.WriteFile(path, []byte(code), 0o600); err != nil {
t.Fatal(err) t.Fatal(err)
} }
path, err := filepath.EvalSymlinks(path)
if err != nil {
t.Fatal(err)
}
return path return path
} }
+17 -2
View File
@@ -93,7 +93,7 @@ func GetConfDirPath() string {
return configPath return configPath
} }
// ResolveLuaFile finds a local Lua script and returns its absolute path. // ResolveLuaFile finds a local .lua script and returns its resolved absolute path.
// Relative paths: XRAY_LOCATION_CONFDIR > XRAY_LOCATION_CONFIG > working dir > executable dir. // Relative paths: XRAY_LOCATION_CONFDIR > XRAY_LOCATION_CONFIG > working dir > executable dir.
func ResolveLuaFile(path string) (string, error) { func ResolveLuaFile(path string) (string, error) {
if path == "" { if path == "" {
@@ -113,7 +113,22 @@ func ResolveLuaFile(path string) (string, error) {
} }
} }
} }
return resolveFile(paths) path, err := resolveFile(paths)
if err != nil {
return "", err
}
return resolveLuaTarget(path)
}
func resolveLuaTarget(path string) (string, error) {
path, err := filepath.EvalSymlinks(path)
if err != nil {
return "", fmt.Errorf("failed to resolve Lua file symlinks: %w", err)
}
if filepath.Ext(path) != ".lua" {
return "", errors.New("Lua file extension must be .lua")
}
return path, nil
} }
func resolveFile(paths []string) (string, error) { func resolveFile(paths []string) (string, error) {
+37
View File
@@ -5,6 +5,7 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"runtime" "runtime"
"strings"
"testing" "testing"
"github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common"
@@ -88,6 +89,10 @@ func TestResolveLuaFile(t *testing.T) {
for _, path := range paths[:3] { for _, path := range paths[:3] {
common.Must(os.WriteFile(path, nil, 0o600)) common.Must(os.WriteFile(path, nil, 0o600))
} }
for i, path := range paths {
paths[i], err = filepath.EvalSymlinks(path)
common.Must(err)
}
if got, err := ResolveLuaFile(paths[2]); err != nil || got != paths[2] { if got, err := ResolveLuaFile(paths[2]); err != nil || got != paths[2] {
t.Fatalf("absolute path = %q, %v; want %q", got, err, paths[2]) t.Fatalf("absolute path = %q, %v; want %q", got, err, paths[2])
} }
@@ -115,3 +120,35 @@ func TestResolveLuaFile(t *testing.T) {
} }
} }
} }
func TestResolveLuaFileSymlink(t *testing.T) {
for _, tc := range []struct {
link, target string
valid bool
}{
{"alias.lua", "data.dat", false},
{"alias.dat", "script.lua", true},
} {
t.Run(tc.link, func(t *testing.T) {
dir, err := filepath.EvalSymlinks(t.TempDir())
common.Must(err)
target := filepath.Join(dir, tc.target)
common.Must(os.WriteFile(target, nil, 0o600))
link := filepath.Join(dir, tc.link)
if err := os.Symlink(tc.target, link); err != nil {
if runtime.GOOS == "windows" {
t.Skipf("cannot create symlinks: %v", err)
}
t.Fatal(err)
}
got, err := ResolveLuaFile(link)
if tc.valid {
if err != nil || got != target {
t.Fatalf("ResolveLuaFile(%q) = %q, %v; want %q", link, got, err, target)
}
} else if err == nil || got != "" || !strings.Contains(err.Error(), "Lua file extension must be .lua") {
t.Fatalf("ResolveLuaFile(%q) = %q, %v; want .lua extension error", link, got, err)
}
})
}
}