1
0
mirror of https://github.com/golang/go synced 2024-11-21 22:24:40 -07:00

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
This commit is contained in:
Zeke Lu 2022-07-05 00:55:01 +08:00
parent ceda93ed67
commit f0e594bdb3
3 changed files with 162 additions and 2 deletions

View File

@ -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}

View File

@ -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]
}

View File

@ -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