diff --git a/common/platform/config_file.go b/common/platform/config_file.go deleted file mode 100644 index 13caf2731..000000000 --- a/common/platform/config_file.go +++ /dev/null @@ -1,50 +0,0 @@ -package platform - -import ( - "io" - "os" - "path/filepath" -) - -type configFile struct { - path string - info os.FileInfo -} - -var configFiles []configFile - -// ReadConfigFile reads a config source and records local regular files. -func ReadConfigFile(file *os.File) ([]byte, error) { - info, err := file.Stat() - if err != nil { - return nil, err - } - path := "" - if info.Mode().IsRegular() && file != os.Stdin { - path, err = filepath.Abs(file.Name()) - if err != nil { - return nil, err - } - path, err = filepath.EvalSymlinks(path) - if err != nil { - return nil, err - } - } - data, err := io.ReadAll(file) - if err != nil { - return nil, err - } - if info.Mode().IsRegular() { - configFiles = append(configFiles, configFile{path: path, info: info}) - } - return data, nil -} - -func isConfigFile(path string, info os.FileInfo) bool { - for _, file := range configFiles { - if file.path == path || os.SameFile(file.info, info) { - return true - } - } - return false -} diff --git a/common/platform/platform.go b/common/platform/platform.go index dcba6cc13..9a650876a 100644 --- a/common/platform/platform.go +++ b/common/platform/platform.go @@ -113,18 +113,11 @@ func ResolveLuaFile(path string) (string, error) { } } } - path, info, err := resolveFile(paths) + path, err := resolveFile(paths) if err != nil { return "", err } - path, err = resolveLuaTarget(path) - if err != nil { - return "", err - } - if isConfigFile(path, info) { - return "", fmt.Errorf("Lua file was loaded as a configuration file: %s", path) - } - return path, nil + return resolveLuaTarget(path) } func resolveLuaTarget(path string) (string, error) { @@ -138,12 +131,12 @@ func resolveLuaTarget(path string) (string, error) { return path, nil } -func resolveFile(paths []string) (string, os.FileInfo, error) { +func resolveFile(paths []string) (string, error) { var tried []string for _, path := range paths { path, err := filepath.Abs(path) if err != nil { - return "", nil, fmt.Errorf("failed to resolve file path: %w", err) + return "", fmt.Errorf("failed to resolve file path: %w", err) } tried = append(tried, path) info, err := os.Stat(path) @@ -151,12 +144,12 @@ func resolveFile(paths []string) (string, os.FileInfo, error) { continue } if err != nil { - return "", nil, fmt.Errorf("failed to inspect file %q: %w", path, err) + return "", fmt.Errorf("failed to inspect file %q: %w", path, err) } if !info.Mode().IsRegular() { - return "", nil, fmt.Errorf("file is not a regular file: %s", path) + return "", fmt.Errorf("file is not a regular file: %s", path) } - return path, info, nil + return path, nil } - return "", nil, fmt.Errorf("file not found; tried %q: %w", tried, os.ErrNotExist) + return "", fmt.Errorf("file not found; tried %q: %w", tried, os.ErrNotExist) } diff --git a/infra/conf/serial/config_security_test.go b/infra/conf/serial/config_security_test.go deleted file mode 100644 index 0a72b048e..000000000 --- a/infra/conf/serial/config_security_test.go +++ /dev/null @@ -1,194 +0,0 @@ -package serial_test - -import ( - "context" - "io" - "os" - "path/filepath" - "strings" - "testing" - "time" - - xlua "github.com/xtls/xray-core/common/lua" - "github.com/xtls/xray-core/core" - "github.com/xtls/xray-core/infra/conf" - "github.com/xtls/xray-core/infra/conf/serial" - _ "github.com/xtls/xray-core/main/confloader/external" - glua "github.com/yuin/gopher-lua" -) - -func TestConfigSourceSecurity(t *testing.T) { - // Keep registered files alive until every file identity check finishes. - dir := t.TempDir() - t.Run("polyglots", testConfigLuaPolyglots) - t.Run("merged", func(t *testing.T) { testMergedConfigCannotRunAsLua(t, dir) }) - t.Run("hardlink", func(t *testing.T) { testConfigHardLinkCannotRunAsLua(t, dir) }) - t.Run("stdin", func(t *testing.T) { testStdinConfigCannotRunAsLua(t, dir) }) -} - -func testConfigLuaPolyglots(t *testing.T) { - const luaBody = "polyglotExecuted = true\nfunction HandleRoute() return 'direct' end\n" - for _, tc := range []struct { - name string - decode func(io.Reader) (*conf.Config, error) - source string - script string - }{ - {"JSON", serial.DecodeJSONConfig, "null = 0\n" + luaBody, ""}, - {"YAML", serial.DecodeYAMLConfig, `--[[: ignored -routing: - script: payload.lua ---]] --: ignored -_G: assert((function() polyglotExecuted = true; function HandleRoute() return 'direct' end end)()) -`, "payload.lua"}, - {"TOML", serial.DecodeTOMLConfig, `routing = { script = "payload.lua" } --- = """ -polyglotExecuted = true -function HandleRoute() return 'direct' end --- """ -`, "payload.lua"}, - } { - t.Run(tc.name, func(t *testing.T) { - config, err := tc.decode(strings.NewReader(tc.source)) - if err != nil { - t.Fatal(err) - } - script := "" - if config.RouterConfig != nil { - script = config.RouterConfig.Script - } - if script != tc.script { - t.Fatalf("routing.script = %q, want %q", script, tc.script) - } - if built, err := config.Build(); err != nil || built == nil { - t.Fatalf("config Build failed: %v", err) - } - if _, err := serial.DecodeJSONConfigStrict(strings.NewReader(tc.source)); err == nil { - t.Fatal("strict JSON accepted the polyglot") - } - dir := t.TempDir() - t.Setenv("xray.location.confdir", dir) - if err := os.WriteFile(filepath.Join(dir, "payload.lua"), []byte(tc.source), 0o600); err != nil { - t.Fatal(err) - } - configPath := filepath.Join(dir, "payload."+strings.ToLower(tc.name)) - if err := os.WriteFile(configPath, []byte(tc.source), 0o600); err != nil { - t.Fatal(err) - } - if program, err := xlua.Compile(configPath); program != nil || err == nil || !strings.Contains(err.Error(), "Lua file extension must be .lua") { - t.Fatalf("Compile(%q) = %v, %v; want .lua extension rejection", configPath, program, err) - } - program, err := xlua.Compile("payload.lua") - if err != nil { - t.Fatal(err) - } - ctx, cancel := context.WithTimeout(context.Background(), time.Second) - defer cancel() - state, err := program.NewState(ctx, nil, nil) - if err != nil { - t.Fatal(err) - } - defer state.Close() - if state.GetGlobal("polyglotExecuted") != glua.LTrue { - t.Fatal("Lua top-level code did not execute") - } - if err := state.CallByParam(glua.P{Fn: state.GetGlobal("HandleRoute"), NRet: 1, Protect: true}); err != nil { - t.Fatal(err) - } - if got := state.Get(-1); got != glua.LString("direct") { - t.Fatalf("HandleRoute() = %v, want direct", got) - } - t.Logf("config built with routing.script=%q; the same bytes executed as Lua", script) - }) - } -} - -func testMergedConfigCannotRunAsLua(t *testing.T, dir string) { - t.Setenv("xray.location.confdir", dir) - base := filepath.Join(dir, "base.lua") - for format, body := range map[string]string{ - "json": "null = 0\nfunction HandleRoute() return 'direct' end\n", - "yaml": "_G: assert((function() function HandleRoute() return 'direct' end end)())\n", - "toml": "-- = \"\"\"\nfunction HandleRoute() return 'direct' end\n-- \"\"\"\n", - } { - t.Run(format, func(t *testing.T) { - path := filepath.Join(dir, format+".lua") - if err := os.WriteFile(base, []byte(`{"routing":{"script":"`+format+`.lua"}}`), 0o600); err != nil { - t.Fatal(err) - } - if err := os.WriteFile(path, []byte(body), 0o600); err != nil { - t.Fatal(err) - } - if program, err := xlua.Compile(path); err != nil || program == nil { - t.Fatalf("fixture must compile before config loading: %v", err) - } - if _, err := serial.BuildConfig([]*core.ConfigSource{ - {Name: base, Format: "json"}, - {Name: path, Format: format}, - }); err != nil { - t.Fatal(err) - } - for _, source := range []string{base, path} { - if program, err := xlua.Compile(source); program != nil || err == nil || !strings.Contains(err.Error(), "loaded as a configuration file") { - t.Fatalf("Compile(%q) = %v, %v; want config source rejection", source, program, err) - } - } - if err := os.Rename(path, path+".old"); err != nil { - t.Fatal(err) - } - if err := os.WriteFile(path, []byte("function HandleRoute() end"), 0o600); err != nil { - t.Fatal(err) - } - if program, err := xlua.Compile(path); program != nil || err == nil || !strings.Contains(err.Error(), "loaded as a configuration file") { - t.Fatalf("Compile = %v, %v; want replaced config path rejection", program, err) - } - }) - } - path := filepath.Join(dir, "ordinary.lua") - if err := os.WriteFile(path, []byte("function HandleRoute() end"), 0o600); err != nil { - t.Fatal(err) - } - if program, err := xlua.Compile(path); err != nil || program == nil { - t.Fatalf("ordinary script in the config directory must remain usable: %v", err) - } -} - -func testConfigHardLinkCannotRunAsLua(t *testing.T, dir string) { - path := filepath.Join(dir, "config.json") - if err := os.WriteFile(path, []byte("null = 0\nfunction HandleRoute() end"), 0o600); err != nil { - t.Fatal(err) - } - alias := filepath.Join(dir, "alias.lua") - if err := os.Link(path, alias); err != nil { - t.Fatal(err) - } - if _, err := serial.BuildConfig([]*core.ConfigSource{{Name: path, Format: "json"}}); err != nil { - t.Fatal(err) - } - if program, err := xlua.Compile(alias); program != nil || err == nil || !strings.Contains(err.Error(), "loaded as a configuration file") { - t.Fatalf("Compile = %v, %v; want config hard link rejection", program, err) - } -} - -func testStdinConfigCannotRunAsLua(t *testing.T, dir string) { - path := filepath.Join(dir, "config.lua") - if err := os.WriteFile(path, []byte("null = 0\nfunction HandleRoute() end"), 0o600); err != nil { - t.Fatal(err) - } - file, err := os.Open(path) - if err != nil { - t.Fatal(err) - } - originalStdin := os.Stdin - os.Stdin = file - t.Cleanup(func() { - os.Stdin = originalStdin - file.Close() - }) - if _, err := serial.BuildConfig([]*core.ConfigSource{{Name: "stdin:", Format: "json"}}); err != nil { - t.Fatal(err) - } - if program, err := xlua.Compile(path); program != nil || err == nil || !strings.Contains(err.Error(), "loaded as a configuration file") { - t.Fatalf("Compile = %v, %v; want redirected config rejection", program, err) - } -} diff --git a/main/confloader/external/external.go b/main/confloader/external/external.go index a812cea4f..02066109b 100644 --- a/main/confloader/external/external.go +++ b/main/confloader/external/external.go @@ -13,7 +13,6 @@ import ( "github.com/xtls/xray-core/common/buf" "github.com/xtls/xray-core/common/errors" - "github.com/xtls/xray-core/common/platform" "github.com/xtls/xray-core/common/utils" "github.com/xtls/xray-core/main/confloader" ) @@ -29,15 +28,10 @@ func ConfigLoader(arg string) (out io.Reader, err error) { data, err = FetchHTTPContent(arg) case arg == "stdin:": - data, err = platform.ReadConfigFile(os.Stdin) + data, err = io.ReadAll(os.Stdin) default: - file, openErr := os.Open(arg) - if openErr != nil { - return nil, openErr - } - defer file.Close() - data, err = platform.ReadConfigFile(file) + data, err = os.ReadFile(arg) } if err != nil {