diff --git a/process/killtree_other.go b/process/killtree_other.go new file mode 100644 index 0000000..90714ce --- /dev/null +++ b/process/killtree_other.go @@ -0,0 +1,10 @@ +//go:build !windows + +// 非 Windows 无进程树兜底(killProcess 仅 Windows 分支调用)。 +package process + +import "errors" + +func killProcessTree(root uint32) error { + return errors.New("kill tree 仅支持 Windows") +} diff --git a/process/killtree_windows.go b/process/killtree_windows.go new file mode 100644 index 0000000..922c42c --- /dev/null +++ b/process/killtree_windows.go @@ -0,0 +1,78 @@ +//go:build windows + +// 进程树强杀(编程方式, Windows API)——2026-10-09 起替代原 taskkill /F /T 命令行兜底 +// (产品红线: 系统操作禁拼 cmd 命令字符串)。 +package process + +import ( + "fmt" + "unsafe" + + "golang.org/x/sys/windows" +) + +// killProcessTree 按 PID 定向结束进程及其全部后代: +// Toolhelp32 快照枚举全进程父子关系 → 收集后代(防父子环) → 逐个 OpenProcess+TerminateProcess。 +func killProcessTree(root uint32) error { + parentOf, err := snapshotParentMap() + if err != nil { + return err + } + var pids []uint32 + visited := map[uint32]bool{} + var visit func(pid uint32) + visit = func(pid uint32) { + if visited[pid] { + return + } + visited[pid] = true + pids = append(pids, pid) + for child, parent := range parentOf { + if parent == pid { + visit(child) + } + } + } + visit(root) + + var firstErr error + for _, pid := range pids { + h, err := windows.OpenProcess(windows.PROCESS_TERMINATE, false, pid) + if err != nil { + if firstErr == nil { + firstErr = fmt.Errorf("pid=%d OpenProcess: %w", pid, err) + } + continue + } + if err := windows.TerminateProcess(h, 1); err != nil { + if firstErr == nil { + firstErr = fmt.Errorf("pid=%d TerminateProcess: %w", pid, err) + } + } + _ = windows.CloseHandle(h) + } + return firstErr +} + +// snapshotParentMap 全进程快照: pid → 父 pid。 +func snapshotParentMap() (map[uint32]uint32, error) { + snap, err := windows.CreateToolhelp32Snapshot(windows.TH32CS_SNAPPROCESS, 0) + if err != nil { + return nil, fmt.Errorf("CreateToolhelp32Snapshot: %w", err) + } + defer windows.CloseHandle(snap) + + parentOf := map[uint32]uint32{} + var pe windows.ProcessEntry32 + pe.Size = uint32(unsafe.Sizeof(pe)) + if err := windows.Process32First(snap, &pe); err != nil { + return nil, fmt.Errorf("Process32First: %w", err) + } + for { + parentOf[pe.ProcessID] = pe.ParentProcessID + if err := windows.Process32Next(snap, &pe); err != nil { + break + } + } + return parentOf, nil +} diff --git a/process/process_http.go b/process/process_http.go index e4b6788..c2ec0cd 100644 --- a/process/process_http.go +++ b/process/process_http.go @@ -7,10 +7,8 @@ package process import ( "encoding/json" "net/http" - "os/exec" "runtime" "sort" - "strconv" "github.com/shirou/gopsutil/v3/process" @@ -85,7 +83,8 @@ func listProcesses(w http.ResponseWriter, _ *http.Request) { } // killProcess 结束进程: {pid: 必填, name: 可选校验(防 pid 复用误杀)}。 -// Windows 上 gopsutil 权限不足时回退 taskkill /F /T(连子进程树强杀)。 +// Windows 上 gopsutil 权限不足时回退进程树强杀(Windows API: Toolhelp32 快照+TerminateProcess, +// 2026-10-09 起替代原 taskkill /F /T 命令行兜底——产品红线禁命令行查杀)。 func killProcess(w http.ResponseWriter, r *http.Request) { var req struct { Pid int32 `json:"pid"` @@ -111,8 +110,8 @@ func killProcess(w http.ResponseWriter, r *http.Request) { fail(w, "结束进程失败: "+err.Error()) return } - if out, e := exec.Command("taskkill", "/F", "/T", "/PID", strconv.Itoa(int(req.Pid))).CombinedOutput(); e != nil { - fail(w, "结束进程失败: "+err.Error()+"; taskkill: "+string(out)) + if e := killProcessTree(uint32(req.Pid)); e != nil { + fail(w, "结束进程失败: "+err.Error()+"; 进程树终止: "+e.Error()) return } }