fix: harden workspace preprocessing contract
This commit is contained in:
@@ -3,9 +3,12 @@
|
||||
package compose
|
||||
|
||||
import (
|
||||
"golang.org/x/sys/windows"
|
||||
"errors"
|
||||
"os/exec"
|
||||
"sync"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
var ownedJobs = struct {
|
||||
@@ -13,23 +16,76 @@ var ownedJobs = struct {
|
||||
m map[*exec.Cmd]windows.Handle
|
||||
}{m: make(map[*exec.Cmd]windows.Handle)}
|
||||
|
||||
// CREATE_SUSPENDED closes the registration race: no child code can create a
|
||||
// descendant until the real process HANDLE has been assigned to the job.
|
||||
func configureOwnedProcess(c *exec.Cmd) {
|
||||
c.SysProcAttr = &windows.SysProcAttr{CreationFlags: windows.CREATE_NEW_PROCESS_GROUP}
|
||||
c.SysProcAttr = &windows.SysProcAttr{CreationFlags: windows.CREATE_NEW_PROCESS_GROUP | windows.CREATE_SUSPENDED}
|
||||
}
|
||||
|
||||
func registerOwnedProcess(c *exec.Cmd) error {
|
||||
h, err := windows.CreateJobObject(nil, nil)
|
||||
if c.Process == nil {
|
||||
return errors.New("owned process has no process handle")
|
||||
}
|
||||
job, err := windows.CreateJobObject(nil, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = windows.AssignProcessToJobObject(h, windows.Handle(c.Process.Pid)); err != nil {
|
||||
windows.CloseHandle(h)
|
||||
closeJob := true
|
||||
defer func() {
|
||||
if closeJob {
|
||||
windows.CloseHandle(job)
|
||||
}
|
||||
}()
|
||||
limits := windows.JOBOBJECT_EXTENDED_LIMIT_INFORMATION{}
|
||||
limits.BasicLimitInformation.LimitFlags = windows.JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE
|
||||
if _, err = windows.SetInformationJobObject(job, windows.JobObjectExtendedLimitInformation, uintptr(unsafe.Pointer(&limits)), uint32(unsafe.Sizeof(limits))); err != nil {
|
||||
return err
|
||||
}
|
||||
// c.Process.Pid is used only to obtain a genuine process HANDLE; a PID is
|
||||
// never passed to AssignProcessToJobObject.
|
||||
process, err := windows.OpenProcess(windows.PROCESS_SET_QUOTA|windows.PROCESS_TERMINATE|windows.PROCESS_QUERY_LIMITED_INFORMATION, false, uint32(c.Process.Pid))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer windows.CloseHandle(process)
|
||||
if err = windows.AssignProcessToJobObject(job, process); err != nil {
|
||||
return err
|
||||
}
|
||||
thread, err := suspendedPrimaryThread(uint32(c.Process.Pid))
|
||||
if err != nil {
|
||||
_ = windows.TerminateJobObject(job, 1)
|
||||
return err
|
||||
}
|
||||
_, resumeErr := windows.ResumeThread(thread)
|
||||
windows.CloseHandle(thread)
|
||||
if resumeErr != nil {
|
||||
_ = windows.TerminateJobObject(job, 1)
|
||||
return resumeErr
|
||||
}
|
||||
ownedJobs.Lock()
|
||||
ownedJobs.m[c] = h
|
||||
ownedJobs.m[c] = job
|
||||
ownedJobs.Unlock()
|
||||
closeJob = false
|
||||
return nil
|
||||
}
|
||||
|
||||
func suspendedPrimaryThread(pid uint32) (windows.Handle, error) {
|
||||
snapshot, err := windows.CreateToolhelp32Snapshot(windows.TH32CS_SNAPTHREAD, 0)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer windows.CloseHandle(snapshot)
|
||||
entry := windows.ThreadEntry32{Size: uint32(unsafe.Sizeof(windows.ThreadEntry32{}))}
|
||||
err = windows.Thread32First(snapshot, &entry)
|
||||
for err == nil {
|
||||
if entry.OwnerProcessID == pid {
|
||||
return windows.OpenThread(windows.THREAD_SUSPEND_RESUME, false, entry.ThreadID)
|
||||
}
|
||||
err = windows.Thread32Next(snapshot, &entry)
|
||||
}
|
||||
return 0, err
|
||||
}
|
||||
|
||||
func releaseOwnedProcess(c *exec.Cmd) {
|
||||
ownedJobs.Lock()
|
||||
h := ownedJobs.m[c]
|
||||
|
||||
Reference in New Issue
Block a user