Skip to content

Commit 19f96e7

Browse files
committed
syscall: introduce SysProcAttr.ParentProcess on Windows
This allows users to specify which process should be used as the parent process when creating a new process. Note that this doesn't just trivially pass the handle onward to PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, because inherited handles must be valid in the parent process, so if we're changing the destination process, then we must also change the origin of the parent handles. And, the StartProcess function must clean up these handles successfully when exiting, regardless of where the duplication happened. So, we take care in this commit to use DuplicateHandle for both duplicating and for closing the inherited handles. The test was taken originally from CL 288272 and adjusted for use here. Fixes #44011. Change-Id: Ib3b132028dcab1aded3dc0e65126c8abebfa35eb Reviewed-on: https://go-review.googlesource.com/c/go/+/288300 Trust: Jason A. Donenfeld <[email protected]> Trust: Alex Brainman <[email protected]> Reviewed-by: Alex Brainman <[email protected]>
1 parent 3146166 commit 19f96e7

File tree

2 files changed

+87
-3
lines changed

2 files changed

+87
-3
lines changed

src/syscall/exec_windows.go

+14-3
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,7 @@ type SysProcAttr struct {
243243
ThreadAttributes *SecurityAttributes // if set, applies these security attributes as the descriptor for the main thread of the new process
244244
NoInheritHandles bool // if set, each inheritable handle in the calling process is not inherited by the new process
245245
AdditionalInheritedHandles []Handle // a list of additional handles, already marked as inheritable, that will be inherited by the new process
246+
ParentProcess Handle // if non-zero, the new process regards the process given by this handle as its parent process, and AdditionalInheritedHandles, if set, should exist in this parent process
246247
}
247248

248249
var zeroProcAttr ProcAttr
@@ -312,18 +313,22 @@ func StartProcess(argv0 string, argv []string, attr *ProcAttr) (pid int, handle
312313
}
313314

314315
p, _ := GetCurrentProcess()
316+
parentProcess := p
317+
if sys.ParentProcess != 0 {
318+
parentProcess = sys.ParentProcess
319+
}
315320
fd := make([]Handle, len(attr.Files))
316321
for i := range attr.Files {
317322
if attr.Files[i] > 0 {
318-
err := DuplicateHandle(p, Handle(attr.Files[i]), p, &fd[i], 0, true, DUPLICATE_SAME_ACCESS)
323+
err := DuplicateHandle(p, Handle(attr.Files[i]), parentProcess, &fd[i], 0, true, DUPLICATE_SAME_ACCESS)
319324
if err != nil {
320325
return 0, 0, err
321326
}
322-
defer CloseHandle(Handle(fd[i]))
327+
defer DuplicateHandle(parentProcess, fd[i], 0, nil, 0, false, DUPLICATE_CLOSE_SOURCE)
323328
}
324329
}
325330
si := new(_STARTUPINFOEXW)
326-
si.ProcThreadAttributeList, err = newProcThreadAttributeList(1)
331+
si.ProcThreadAttributeList, err = newProcThreadAttributeList(2)
327332
if err != nil {
328333
return 0, 0, err
329334
}
@@ -334,6 +339,12 @@ func StartProcess(argv0 string, argv []string, attr *ProcAttr) (pid int, handle
334339
si.Flags |= STARTF_USESHOWWINDOW
335340
si.ShowWindow = SW_HIDE
336341
}
342+
if sys.ParentProcess != 0 {
343+
err = updateProcThreadAttribute(si.ProcThreadAttributeList, 0, _PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, uintptr(unsafe.Pointer(&sys.ParentProcess)), unsafe.Sizeof(sys.ParentProcess), 0, nil)
344+
if err != nil {
345+
return 0, 0, err
346+
}
347+
}
337348
si.StdInput = fd[0]
338349
si.StdOutput = fd[1]
339350
si.StdErr = fd[2]

src/syscall/exec_windows_test.go

+73
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,14 @@
55
package syscall_test
66

77
import (
8+
"fmt"
9+
"io/ioutil"
10+
"os"
11+
"os/exec"
12+
"path/filepath"
813
"syscall"
914
"testing"
15+
"time"
1016
)
1117

1218
func TestEscapeArg(t *testing.T) {
@@ -41,3 +47,70 @@ func TestEscapeArg(t *testing.T) {
4147
}
4248
}
4349
}
50+
51+
func TestChangingProcessParent(t *testing.T) {
52+
if os.Getenv("GO_WANT_HELPER_PROCESS") == "parent" {
53+
// in parent process
54+
55+
// Parent does nothign. It is just used as a parent of a child process.
56+
time.Sleep(time.Minute)
57+
os.Exit(0)
58+
}
59+
60+
if os.Getenv("GO_WANT_HELPER_PROCESS") == "child" {
61+
// in child process
62+
dumpPath := os.Getenv("GO_WANT_HELPER_PROCESS_FILE")
63+
if dumpPath == "" {
64+
fmt.Fprintf(os.Stderr, "Dump file path cannot be blank.")
65+
os.Exit(1)
66+
}
67+
err := os.WriteFile(dumpPath, []byte(fmt.Sprintf("%d", os.Getppid())), 0644)
68+
if err != nil {
69+
fmt.Fprintf(os.Stderr, "Error writing dump file: %v", err)
70+
os.Exit(2)
71+
}
72+
os.Exit(0)
73+
}
74+
75+
// run parent process
76+
77+
parent := exec.Command(os.Args[0], "-test.run=TestChangingProcessParent")
78+
parent.Env = append(os.Environ(), "GO_WANT_HELPER_PROCESS=parent")
79+
err := parent.Start()
80+
if err != nil {
81+
t.Fatal(err)
82+
}
83+
defer func() {
84+
parent.Process.Kill()
85+
parent.Wait()
86+
}()
87+
88+
// run child process
89+
90+
const _PROCESS_CREATE_PROCESS = 0x0080
91+
const _PROCESS_DUP_HANDLE = 0x0040
92+
childDumpPath := filepath.Join(t.TempDir(), "ppid.txt")
93+
ph, err := syscall.OpenProcess(_PROCESS_CREATE_PROCESS|_PROCESS_DUP_HANDLE|syscall.PROCESS_QUERY_INFORMATION,
94+
false, uint32(parent.Process.Pid))
95+
if err != nil {
96+
t.Fatal(err)
97+
}
98+
defer syscall.CloseHandle(ph)
99+
100+
child := exec.Command(os.Args[0], "-test.run=TestChangingProcessParent")
101+
child.Env = append(os.Environ(),
102+
"GO_WANT_HELPER_PROCESS=child",
103+
"GO_WANT_HELPER_PROCESS_FILE="+childDumpPath)
104+
child.SysProcAttr = &syscall.SysProcAttr{ParentProcess: ph}
105+
childOutput, err := child.CombinedOutput()
106+
if err != nil {
107+
t.Errorf("child failed: %v: %v", err, string(childOutput))
108+
}
109+
childOutput, err = ioutil.ReadFile(childDumpPath)
110+
if err != nil {
111+
t.Fatalf("reading child ouput failed: %v", err)
112+
}
113+
if got, want := string(childOutput), fmt.Sprintf("%d", parent.Process.Pid); got != want {
114+
t.Fatalf("child output: want %q, got %q", want, got)
115+
}
116+
}

0 commit comments

Comments
 (0)