Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion go/helpers/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -259,12 +259,14 @@ func execute(path string, args []string, conf *ExecConfig) (ExecResult, error) {
if err := cmd.Start(); err != nil {
return result, fmt.Errorf("cmd.Start: %w", err)
}
killProc, closeProc := manageProcess(cmd.Process)
defer closeProc()

var timer *time.Timer
// Create a timer that will kill the process
if conf.Timeout != nil {
timer = time.AfterFunc(*conf.Timeout, func() {
cmd.Process.Kill()
killProc()
})
}

Expand Down
10 changes: 10 additions & 0 deletions go/helpers/helpers_stub.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,13 @@

package helpers

import "os"

func manageProcess(proc *os.Process) (kill func(), cleanup func()) {
return func() {
if proc != nil {
proc.Kill()
}
}, func() {}
}

54 changes: 54 additions & 0 deletions go/helpers/helpers_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@ package helpers

import (
"fmt"
"os"
"os/exec"
"regexp"
"strconv"
"syscall"
"time"
"unsafe"
Expand Down Expand Up @@ -55,6 +58,57 @@ const (
swpShowWindow = 0x0040
)

func manageProcess(proc *os.Process) (kill func(), cleanup func()) {
defaultKill := func() {
if proc != nil {
exec.Command(os.ExpandEnv("${SystemRoot}\\System32\\taskkill.exe"), "/T", "/F", "/PID", strconv.Itoa(proc.Pid)).Run()
proc.Kill()
}
}
noop := func() {}
if proc == nil {
return defaultKill, noop
}

job, err := windows.CreateJobObject(nil, nil)
if err != nil {
deck.Warningf("unable to create job object: %v", err)
return defaultKill, noop
}
info := windows.JOBOBJECT_EXTENDED_LIMIT_INFORMATION{
BasicLimitInformation: windows.JOBOBJECT_BASIC_LIMIT_INFORMATION{
LimitFlags: windows.JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE,
},
}
if _, err := windows.SetInformationJobObject(
job,
windows.JobObjectExtendedLimitInformation,
uintptr(unsafe.Pointer(&info)),
uint32(unsafe.Sizeof(info)),
); err != nil {
deck.Warningf("unable to set JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE limit flag on job: %v", err)
}
hProc, err := windows.OpenProcess(windows.PROCESS_SET_QUOTA|windows.PROCESS_TERMINATE, false, uint32(proc.Pid))
if err != nil {
windows.CloseHandle(job)
deck.Warningf("unable to open process for job object association: %v", err)
return defaultKill, noop
}
defer windows.CloseHandle(hProc)
if err := windows.AssignProcessToJobObject(job, hProc); err != nil {
windows.CloseHandle(job)
deck.Warningf("unable to associate process with job object: %v", err)
return defaultKill, noop
}
return func() {
if err := windows.TerminateJobObject(job, 1); err != nil {
defaultKill()
}
}, func() {
windows.CloseHandle(job)
}
}

// GetServiceState interrogates local system services and returns their status and configuration.
func GetServiceState(name string) (svc.Status, mgr.Config, error) {
m, err := mgr.Connect()
Expand Down
24 changes: 24 additions & 0 deletions go/helpers/helpers_windows_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@
package helpers

import (
"bytes"
"errors"
"fmt"
"os"
"regexp"
"testing"
"time"
Expand Down Expand Up @@ -104,3 +106,25 @@ func TestHasAdmin(t *testing.T) {
t.Skip("Test running with elevated privileges, skipping check for non-elevated context.")
}
}

func TestExecuteTimeoutKillsChildProcessTree(t *testing.T) {
timeout := 200 * time.Millisecond
var stdout, stderr bytes.Buffer
start := time.Now()
_, err := execute(
os.ExpandEnv("${SystemRoot}\\System32\\cmd.exe"),
[]string{"/c", PsPath, "-NoProfile", "-NonInteractive", "-NoLogo", "-Command", "Start-Sleep -Seconds 30"},
&ExecConfig{
Timeout: &timeout,
WriteStdOut: &stdout,
WriteStdErr: &stderr,
},
)
elapsed := time.Since(start)
if !errors.Is(err, ErrTimeout) {
t.Errorf("execute() error = %v, want %v", err, ErrTimeout)
}
if elapsed >= 15*time.Second {
t.Errorf("execute() elapsed = %v, want < 15s (child process kept pipes open after timeout)", elapsed)
}
}
Loading