fix(auth): harden diagnostic cleanup races
This commit is contained in:
@@ -6,15 +6,20 @@ import (
|
||||
"context"
|
||||
"io"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
const finalTerminationBound = 2 * time.Second
|
||||
const processTreeTerminationBound = 2 * time.Second
|
||||
const createNewProcessGroup = 0x00000200
|
||||
|
||||
var windowsSystemDirectory = windows.GetSystemDirectory
|
||||
|
||||
func configureProcess(command *exec.Cmd) {
|
||||
command.SysProcAttr = &syscall.SysProcAttr{CreationFlags: createNewProcessGroup}
|
||||
}
|
||||
@@ -23,23 +28,22 @@ func terminateProcess(command *exec.Cmd, done <-chan error) error {
|
||||
if command.Process == nil {
|
||||
return nil
|
||||
}
|
||||
treeErr := terminateWindowsProcessTree(command.Process.Pid)
|
||||
_ = command.Process.Kill()
|
||||
select {
|
||||
case <-done:
|
||||
if treeErr != nil {
|
||||
return ErrProcessReap
|
||||
}
|
||||
return nil
|
||||
case <-time.After(finalTerminationBound):
|
||||
return ErrProcessReap
|
||||
}
|
||||
return terminateWindowsProcess(
|
||||
done,
|
||||
func() error { return terminateWindowsProcessTree(command.Process.Pid) },
|
||||
command.Process.Kill,
|
||||
finalTerminationBound,
|
||||
)
|
||||
}
|
||||
|
||||
func terminateWindowsProcessTree(pid int) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), processTreeTerminationBound)
|
||||
defer cancel()
|
||||
command := windowsTreeKillCommand(ctx, pid)
|
||||
taskkillPath, err := systemTaskkillPath()
|
||||
if err != nil {
|
||||
return ErrProcessReap
|
||||
}
|
||||
command := windowsTreeKillCommand(ctx, taskkillPath, pid)
|
||||
command.Stdout = io.Discard
|
||||
command.Stderr = io.Discard
|
||||
if err := command.Run(); err != nil {
|
||||
@@ -51,6 +55,23 @@ func terminateWindowsProcessTree(pid int) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func windowsTreeKillCommand(ctx context.Context, pid int) *exec.Cmd {
|
||||
return exec.CommandContext(ctx, "taskkill.exe", "/PID", strconv.Itoa(pid), "/T", "/F")
|
||||
// systemTaskkillPath resolves taskkill through Windows' protected system-directory API, never PATH.
|
||||
func systemTaskkillPath() (string, error) {
|
||||
systemDirectory, err := windowsSystemDirectory()
|
||||
if err != nil {
|
||||
return "", ErrProcessReap
|
||||
}
|
||||
systemDirectory = filepath.Clean(systemDirectory)
|
||||
if systemDirectory == "." || !filepath.IsAbs(systemDirectory) {
|
||||
return "", ErrProcessReap
|
||||
}
|
||||
taskkillPath := filepath.Join(systemDirectory, "taskkill.exe")
|
||||
if !filepath.IsAbs(taskkillPath) || filepath.Dir(taskkillPath) != systemDirectory {
|
||||
return "", ErrProcessReap
|
||||
}
|
||||
return taskkillPath, nil
|
||||
}
|
||||
|
||||
func windowsTreeKillCommand(ctx context.Context, taskkillPath string, pid int) *exec.Cmd {
|
||||
return exec.CommandContext(ctx, taskkillPath, "/PID", strconv.Itoa(pid), "/T", "/F")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user