From f0e594bdb33de85f40972ef0c329b42f9e7bc3ac Mon Sep 17 00:00:00 2001 From: Zeke Lu Date: Tue, 5 Jul 2022 00:55:01 +0800 Subject: [PATCH] syscall: make use of StartupInfo.Reserved2 on Windows On Windows, some applications (for example, Chromium) use _get_osfhandle to retrieve additional open files inherited from its parent process. This C Runtime function requires the parent process to pass the information about the inherited file descriptors in the field StartupInfo.Reserved2. This field is not in use as of now; and making use of it is supposed to not break anything. The MSDN just states that the field is "Reserved for use by the C Run-time". The implementation is found in these projects: nodejs: https://github.com/nodejs/node/blob/71691e53/deps/uv/src/win/process-stdio.c#L32-L37 ReactOS: https://github.com/reactos/reactos/blob/57c84dd6/sdk/lib/crt/stdio/file.c#L405-L532 --- src/os/exec/exec_windows_test.go | 101 +++++++++++++++++++++++++++++++ src/syscall/exec_windows.go | 59 ++++++++++++++++++ src/syscall/types_windows.go | 4 +- 3 files changed, 162 insertions(+), 2 deletions(-) diff --git a/src/os/exec/exec_windows_test.go b/src/os/exec/exec_windows_test.go index 35ae0b0b8af..04853ef0e72 100644 --- a/src/os/exec/exec_windows_test.go +++ b/src/os/exec/exec_windows_test.go @@ -7,18 +7,21 @@ package exec_test import ( + "bufio" "fmt" "io" "os" "os/exec" "strconv" "strings" + "sync" "syscall" "testing" ) func init() { registerHelperCommand("pipehandle", cmdPipeHandle) + registerHelperCommand("crtpipehandle", cmdCRTPipeHandle) } func cmdPipeHandle(args ...string) { @@ -59,6 +62,104 @@ func TestPipePassing(t *testing.T) { } } +func cmdCRTPipeHandle(args ...string) { + get_osfhandle := syscall.NewLazyDLL("msvcrt.dll").NewProc("_get_osfhandle") + + h3, _, _ := get_osfhandle.Call(3) + if h3 == uintptr(syscall.InvalidHandle) { + fmt.Fprintf(os.Stderr, "_get_osfhandle: pipe 3 is invalid\n") + os.Exit(1) + } + pipe3 := os.NewFile(h3, "in") + defer pipe3.Close() + + h4, _, _ := get_osfhandle.Call(4) + if h4 == uintptr(syscall.InvalidHandle) { + fmt.Fprintf(os.Stderr, "_get_osfhandle: pipe 4 is invalid\n") + os.Exit(1) + } + pipe4 := os.NewFile(h4, "out") + defer pipe4.Close() + + br := bufio.NewReader(pipe3) + line, _, err := br.ReadLine() + if err != nil { + fmt.Fprintf(os.Stderr, "reading pipe failed: %v\n", err) + os.Exit(1) + } + if string(line) == "ping" { + _, err := fmt.Fprintf(pipe4, "%s\n", args[0]) + if err != nil { + fmt.Fprintf(os.Stderr, "writing to pipe failed: %v\n", err) + os.Exit(1) + } + } else { + fmt.Fprintf(os.Stderr, "unexpected content from pipe: %q\n", line) + os.Exit(1) + } +} + +func TestCRTPipePassing(t *testing.T) { + crt := syscall.NewLazyDLL("msvcrt.dll") + if err := crt.Load(); err != nil { + t.Skipf("can't run test due to missing msvcrt.dll: %v", err) + } + + r3, w3, err := os.Pipe() + if err != nil { + t.Errorf("failed to create pipe 3: %v", err) + } + defer func() { + r3.Close() + w3.Close() + }() + + r4, w4, err := os.Pipe() + if err != nil { + t.Errorf("failed to create pipe 4: %v", err) + } + defer func() { + r4.Close() + w4.Close() + }() + + const marker = "pong" + childProc := helperCommand(t, "crtpipehandle", marker) + childProc.SysProcAttr = &syscall.SysProcAttr{ + AdditionalInheritedHandles: []syscall.Handle{ + syscall.Handle(r3.Fd()), + syscall.Handle(w4.Fd()), + }, + } + + var wg sync.WaitGroup + wg.Add(1) + go func() { + output, err := childProc.CombinedOutput() + if err != nil { + t.Errorf("child proc exited: %v. output:\n%s", err, output) + r3.Close() + w4.Close() + } + wg.Done() + }() + + _, err = fmt.Fprint(w3, "ping\n") + if err != nil { + t.Errorf("writing pipe failed: %v", err) + } + + br := bufio.NewReader(r4) + response, _, err := br.ReadLine() + if err != nil { + t.Error(err) + } + if string(response) != marker { + t.Errorf("got %q; want %q", string(response), marker) + } + wg.Wait() +} + func TestNoInheritHandles(t *testing.T) { cmd := exec.Command("cmd", "/c exit 88") cmd.SysProcAttr = &syscall.SysProcAttr{NoInheritHandles: true} diff --git a/src/syscall/exec_windows.go b/src/syscall/exec_windows.go index 92464e089c5..48815e10855 100644 --- a/src/syscall/exec_windows.go +++ b/src/syscall/exec_windows.go @@ -398,6 +398,9 @@ func StartProcess(argv0 string, argv []string, attr *ProcAttr) (pid int, handle if err != nil { return 0, 0, err } + if len(fd) > 3 { + si.CbReserved2, si.Reserved2 = createChildStdioBuffer(fd) + } } pi := new(ProcessInformation) @@ -420,3 +423,59 @@ func StartProcess(argv0 string, argv []string, attr *ProcAttr) (pid int, handle func Exec(argv0 string, argv []string, envv []string) (err error) { return EWINDOWS } + +// The C Run-time (CRT) file descriptor mode flags. See +// https://github.com/nodejs/node/blob/71691e53/deps/uv/src/win/process-stdio.c#L57-L65 +const ( + crt_FD_OPEN uint8 = 1 << iota + crt_FD_EOFLAG + crt_FD_CRLF + crt_FD_PIPE + crt_FD_NOINHERIT + crt_FD_APPEND + crt_FD_DEV + crt_FD_TEXT +) + +// createChildStdioBuffer creates a buffer that describes the handles that will +// be inherited by the child process so that the child process can retrieve the +// inherited handles by calling the CRT function _get_osfhandle. +// +// The buffer has the following layout: +// int number_of_fds +// unsigned char crt_flags[number_of_fds] +// HANDLE os_handle[number_of_fds] +// +// See: +// https://github.com/nodejs/node/blob/71691e53/deps/uv/src/win/process-stdio.c#L32-L37 +// https://github.com/reactos/reactos/blob/57c84dd6/sdk/lib/crt/stdio/file.c#L405-L532 +// https://github.com/reactos/reactos/blob/57c84dd6/sdk/lib/crt/stdio/file.c#L1588-L1599 +func createChildStdioBuffer(fd []Handle) (uint16, *byte) { + count := int32(len(fd)) + var crtFlags uint8 + int32Size := unsafe.Sizeof(count) + flagsSize := unsafe.Sizeof(crtFlags) + handleSize := unsafe.Sizeof(fd[0]) + + size := uint16(int32Size + flagsSize*uintptr(count) + handleSize*uintptr(count)) + buf := make([]byte, size) + + *(*int32)(unsafe.Pointer(&buf[0])) = count + + for i := uintptr(0); i < uintptr(count); i++ { + // (ZekeLu) should it be restrict to report the error for invalid or + // unknown handles? + ft, _ := GetFileType(fd[i]) + switch ft { + case FILE_TYPE_DISK: + crtFlags = crt_FD_OPEN + case FILE_TYPE_PIPE: + crtFlags = crt_FD_OPEN | crt_FD_PIPE + default: + crtFlags = crt_FD_OPEN | crt_FD_DEV + } + *(*uint8)(unsafe.Pointer(&buf[int32Size+flagsSize*i])) = crtFlags + *(*Handle)(unsafe.Pointer(&buf[int32Size+flagsSize*uintptr(count)+handleSize*i])) = fd[i] + } + return size, &buf[0] +} diff --git a/src/syscall/types_windows.go b/src/syscall/types_windows.go index 384b5b4f2c1..b8ab25a33b4 100644 --- a/src/syscall/types_windows.go +++ b/src/syscall/types_windows.go @@ -483,8 +483,8 @@ type StartupInfo struct { FillAttribute uint32 Flags uint32 ShowWindow uint16 - _ uint16 - _ *byte + CbReserved2 uint16 + Reserved2 *byte StdInput Handle StdOutput Handle StdErr Handle