diff --git a/experimental/boxdd/cmd_run_windows.go b/experimental/boxdd/cmd_run_windows.go index e3cb3c9ea..d57f93f36 100644 --- a/experimental/boxdd/cmd_run_windows.go +++ b/experimental/boxdd/cmd_run_windows.go @@ -2,9 +2,7 @@ package main import ( "os" - "path/filepath" "runtime" - "strings" "time" E "github.com/sagernet/sing/common/exceptions" @@ -29,10 +27,12 @@ func preparePlatformWorkingDirectory() error { if listenAddress != "" { return os.MkdirAll(workingDirectory, 0o700) } - if !strings.EqualFold(filepath.Clean(workingDirectory), filepath.Clean(defaultServiceWorkingDirectory)) { - return E.New("the Windows service working directory must be ", defaultServiceWorkingDirectory) + serviceWorkingDirectory, err := resolveWindowsServiceWorkingDirectory(workingDirectory) + if err != nil { + return err } - return ensureWindowsWorkingDirectory(workingDirectory) + workingDirectory = serviceWorkingDirectory + return ensureWindowsWorkingDirectory(serviceWorkingDirectory) } type windowsService struct{} diff --git a/experimental/boxdd/cmd_service_windows.go b/experimental/boxdd/cmd_service_windows.go index d8ff89448..42a580f03 100644 --- a/experimental/boxdd/cmd_service_windows.go +++ b/experimental/boxdd/cmd_service_windows.go @@ -3,7 +3,6 @@ package main import ( "errors" "os" - "path/filepath" "strings" "time" @@ -65,19 +64,24 @@ func serviceInstall() error { if err != nil { return E.Cause(err, "get executable path") } - if !strings.EqualFold(filepath.Clean(commandServiceFlagWorkingDirectory), filepath.Clean(defaultServiceWorkingDirectory)) { - return E.New("the Windows service working directory must be ", defaultServiceWorkingDirectory) + serviceWorkingDirectory, err := resolveWindowsServiceWorkingDirectory(commandServiceFlagWorkingDirectory) + if err != nil { + return E.Cause(err, "validate working directory") } executablePath, err = secureWindowsInstallation(executablePath, commandServiceFlagAllowUnsafeInstallation) if err != nil { return E.Cause(err, "secure installation") } + err = ensureWindowsWorkingDirectory(serviceWorkingDirectory) + if err != nil { + return E.Cause(err, "secure working directory") + } manager, err := mgr.Connect() if err != nil { return E.Cause(err, "connect to service manager") } defer manager.Disconnect() - arguments := []string{"run", "--working-directory", defaultServiceWorkingDirectory} + arguments := []string{"run", "--working-directory", serviceWorkingDirectory} config := mgr.Config{ DisplayName: serviceDisplayName, Description: serviceDescriptionText, @@ -132,11 +136,6 @@ func serviceInstall() error { rollback() return E.Cause(err, "secure service") } - err = ensureWindowsWorkingDirectory(defaultServiceWorkingDirectory) - if err != nil { - rollback() - return E.Cause(err, "secure working directory") - } err = eventlog.InstallAsEventCreate(serviceName, eventlog.Error|eventlog.Warning|eventlog.Info) if err != nil && !strings.Contains(err.Error(), "already exists") { rollback() diff --git a/experimental/boxdd/security_windows.go b/experimental/boxdd/security_windows.go index 7fea1d175..6be631942 100644 --- a/experimental/boxdd/security_windows.go +++ b/experimental/boxdd/security_windows.go @@ -336,6 +336,40 @@ func validateFixedNTFSVolume(path string) (string, error) { return filepath.Clean(volumePath), nil } +func resolveWindowsServiceWorkingDirectory(path string) (string, error) { + if path == "" { + return "", E.New("missing daemon working directory") + } + absolutePath, err := filepath.Abs(path) + if err != nil { + return "", E.Cause(err, "resolve daemon working directory") + } + cleanPath := filepath.Clean(absolutePath) + parentPath := filepath.Dir(cleanPath) + parentAttributes, err := windowsFileAttributes(parentPath) + if err != nil { + return "", E.Cause(err, "query daemon working directory parent") + } + if parentAttributes&windows.FILE_ATTRIBUTE_DIRECTORY == 0 { + return "", E.New("daemon working directory parent is not a directory") + } + if parentAttributes&windows.FILE_ATTRIBUTE_REPARSE_POINT != 0 { + return "", E.New("daemon working directory parent is a reparse point") + } + volumeRoot, err := validateFixedNTFSVolume(parentPath) + if err != nil { + return "", E.Cause(err, "validate daemon working directory volume") + } + if strings.EqualFold(cleanPath, filepath.Clean(volumeRoot)) { + return "", E.New("daemon working directory must not be a volume root") + } + err = validateInstallationAncestors(parentPath, volumeRoot, true) + if err != nil { + return "", E.Cause(err, "validate daemon working directory ancestors") + } + return cleanPath, nil +} + func validateInstallationAncestors(path string, volumeRoot string, validatePermissions bool) error { currentPath := filepath.Clean(path) cleanVolumeRoot := filepath.Clean(volumeRoot)