ui: elevate Windows debug-bundle reveal to bypass SystemTemp ACL

The daemon runs as SYSTEM and writes the bundle into C:\Windows\SystemTemp,
whose ACL denies the logged-in user, so a plain 'explorer /select' could not
open it. The Windows reveal now elevates via ShellExecuteW with the runas verb,
falling back to an unelevated reveal of the parent dir on decline.
This commit is contained in:
Zoltán Papp
2026-06-16 15:34:09 +02:00
parent 44709983f7
commit 1f69cd189e
4 changed files with 76 additions and 14 deletions
+1 -13
View File
@@ -5,9 +5,6 @@ package services
import (
"context"
"fmt"
"os/exec"
"path/filepath"
"runtime"
"strings"
"time"
@@ -85,16 +82,7 @@ func (s *Debug) RevealFile(_ context.Context, path string) error {
if path == "" {
return fmt.Errorf("empty path")
}
var cmd *exec.Cmd
switch runtime.GOOS {
case "darwin":
cmd = exec.Command("open", "-R", path)
case "windows":
cmd = exec.Command("explorer", "/select,"+path)
default:
cmd = exec.Command("xdg-open", filepath.Dir(path))
}
return cmd.Start()
return revealFile(path)
}
// RegisterUILog reports the GUI log path to the daemon for bundle collection;
+20
View File
@@ -0,0 +1,20 @@
//go:build !android && !ios && !freebsd && !js && !windows
package services
import (
"os/exec"
"path/filepath"
"runtime"
)
// revealFile opens the OS file manager focused on path.
func revealFile(path string) error {
var cmd *exec.Cmd
if runtime.GOOS == "darwin" {
cmd = exec.Command("open", "-R", path)
} else {
cmd = exec.Command("xdg-open", filepath.Dir(path))
}
return cmd.Start()
}
@@ -0,0 +1,54 @@
package services
import (
"fmt"
"os/exec"
"path/filepath"
"unsafe"
"golang.org/x/sys/windows"
)
// SW_SHOWNORMAL for ShellExecuteW's nShowCmd.
const swShowNormal = 1
var (
shell32 = windows.NewLazySystemDLL("shell32.dll")
procShellExecute = shell32.NewProc("ShellExecuteW")
)
// revealFile opens Explorer focused on path. The debug bundle is written by the
// daemon (running as SYSTEM) into C:\Windows\SystemTemp, whose ACL denies the
// logged-in user. A plain "explorer /select" can't traverse it, so we elevate
// via the ShellExecuteW "runas" verb (UAC prompt) — the elevated Explorer can
// read the folder and highlight the file.
func revealFile(path string) error {
verb, err := windows.UTF16PtrFromString("runas")
if err != nil {
return fmt.Errorf("encode verb: %w", err)
}
file, err := windows.UTF16PtrFromString("explorer.exe")
if err != nil {
return fmt.Errorf("encode file: %w", err)
}
params, err := windows.UTF16PtrFromString("/select," + path)
if err != nil {
return fmt.Errorf("encode params: %w", err)
}
// ShellExecuteW returns an HINSTANCE; a value <=32 is an error code.
ret, _, _ := procShellExecute.Call(
0,
uintptr(unsafe.Pointer(verb)),
uintptr(unsafe.Pointer(file)),
uintptr(unsafe.Pointer(params)),
0,
swShowNormal,
)
if ret <= 32 {
// Elevation declined or failed: fall back to an unelevated reveal of the
// parent directory so the user at least lands near the bundle.
return exec.Command("explorer", filepath.Dir(path)).Start() //nolint:gosec
}
return nil
}