diff --git a/cmd/drover/uac_windows.go b/cmd/drover/uac_windows.go index 093c20d..8515cdd 100644 --- a/cmd/drover/uac_windows.go +++ b/cmd/drover/uac_windows.go @@ -3,7 +3,9 @@ package main import ( + "fmt" "os" + "strings" "syscall" "unsafe" @@ -59,15 +61,23 @@ func ReElevate(args []string) error { if err != nil { return err } - verb, _ := syscall.UTF16PtrFromString("runas") - exePtr, _ := syscall.UTF16PtrFromString(exe) + verb, err := syscall.UTF16PtrFromString("runas") + if err != nil { + return fmt.Errorf("encode verb: %w", err) + } + exePtr, err := syscall.UTF16PtrFromString(exe) + if err != nil { + return fmt.Errorf("encode exe: %w", err) + } var paramsPtr *uint16 if len(args) > 0 { - // Quote each arg in case of spaces. + // Quote each arg in case of spaces, and escape internal quotes. quoted := make([]string, len(args)) for i, a := range args { - quoted[i] = `"` + a + `"` + // Escape any internal quotes with backslash (MSVC argv convention). + escaped := strings.ReplaceAll(a, "\"", "\\\"") + quoted[i] = `"` + escaped + `"` } joined := "" for i, q := range quoted { @@ -76,11 +86,20 @@ func ReElevate(args []string) error { } joined += q } - paramsPtr, _ = syscall.UTF16PtrFromString(joined) + paramsPtr, err = syscall.UTF16PtrFromString(joined) + if err != nil { + return fmt.Errorf("encode params: %w", err) + } } - cwd, _ := os.Getwd() - cwdPtr, _ := syscall.UTF16PtrFromString(cwd) + cwd, err := os.Getwd() + if err != nil { + return fmt.Errorf("get cwd: %w", err) + } + cwdPtr, err := syscall.UTF16PtrFromString(cwd) + if err != nil { + return fmt.Errorf("encode cwd: %w", err) + } // SW_NORMAL = 1 return windows.ShellExecute(0, verb, exePtr, paramsPtr, cwdPtr, 1) diff --git a/cmd/drover/uac_windows_test.go b/cmd/drover/uac_windows_test.go index 0d1de09..1f72680 100644 --- a/cmd/drover/uac_windows_test.go +++ b/cmd/drover/uac_windows_test.go @@ -16,12 +16,12 @@ func TestCmdNeedsAdmin_NoAdminFlags(t *testing.T) { args []string needsAdm bool }{ - {[]string{}, true}, // bare drover.exe → GUI mode → needs admin - {[]string{"check"}, false}, // diagnostic only, no driver + {[]string{}, true}, // bare drover.exe → GUI mode → needs admin + {[]string{"check"}, false}, // diagnostic only, no driver {[]string{"check", "--host", "x"}, false}, {[]string{"--version"}, false}, {[]string{"version"}, false}, - {[]string{"update"}, false}, // self-update doesn't need driver + {[]string{"update"}, false}, // self-update doesn't need driver } for _, c := range cases { got := CmdNeedsAdmin(c.args)