Files
sys/windows/exec_windows.go
Jason A. Donenfeld 03aa0b5f68 windows: allocate attribute list with LocalAlloc, not individual items
We didn't want to wind up with Go pointers mangled by win32, so we were
previously copying the contents into a LocalAlloc'd blob, and then
adding that to the attribute list. The problem is that recent changes to
the API broke this design convention, to the point where it expects 0x18
byte objects to be added using size 0x8. This seems like an unfortunate
oversight from Microsoft, but there's nothing we can do about it. So we
can work around it by instead LocalAlloc'ing the actual container, and
then using the exact pointer value that's passed into Update.

This commit also adds a test that both makes sure that these functions
actually work, and provokes a UaF that's successfully caught, were you
to comment out the line of this commit that reads `al.pointers =
append(al.pointers, value)`.

Fixes golang/go#50134.

Change-Id: Ib73346d2d6ca3db601cd236596cefb564d9dc8f1
Reviewed-on: https://go-review.googlesource.com/c/sys/+/371276
Trust: Jason Donenfeld <Jason@zx2c4.com>
Run-TryBot: Jason Donenfeld <Jason@zx2c4.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Patrik Nyblom <pnyb@google.com>
Trust: Patrik Nyblom <pnyb@google.com>
Run-TryBot: Patrik Nyblom <pnyb@google.com>
2021-12-13 22:30:07 +00:00

179 lines
4.6 KiB
Go

// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Fork, exec, wait, etc.
package windows
import (
errorspkg "errors"
"unsafe"
)
// EscapeArg rewrites command line argument s as prescribed
// in http://msdn.microsoft.com/en-us/library/ms880421.
// This function returns "" (2 double quotes) if s is empty.
// Alternatively, these transformations are done:
// - every back slash (\) is doubled, but only if immediately
// followed by double quote (");
// - every double quote (") is escaped by back slash (\);
// - finally, s is wrapped with double quotes (arg -> "arg"),
// but only if there is space or tab inside s.
func EscapeArg(s string) string {
if len(s) == 0 {
return "\"\""
}
n := len(s)
hasSpace := false
for i := 0; i < len(s); i++ {
switch s[i] {
case '"', '\\':
n++
case ' ', '\t':
hasSpace = true
}
}
if hasSpace {
n += 2
}
if n == len(s) {
return s
}
qs := make([]byte, n)
j := 0
if hasSpace {
qs[j] = '"'
j++
}
slashes := 0
for i := 0; i < len(s); i++ {
switch s[i] {
default:
slashes = 0
qs[j] = s[i]
case '\\':
slashes++
qs[j] = s[i]
case '"':
for ; slashes > 0; slashes-- {
qs[j] = '\\'
j++
}
qs[j] = '\\'
j++
qs[j] = s[i]
}
j++
}
if hasSpace {
for ; slashes > 0; slashes-- {
qs[j] = '\\'
j++
}
qs[j] = '"'
j++
}
return string(qs[:j])
}
// ComposeCommandLine escapes and joins the given arguments suitable for use as a Windows command line,
// in CreateProcess's CommandLine argument, CreateService/ChangeServiceConfig's BinaryPathName argument,
// or any program that uses CommandLineToArgv.
func ComposeCommandLine(args []string) string {
var commandLine string
for i := range args {
if i > 0 {
commandLine += " "
}
commandLine += EscapeArg(args[i])
}
return commandLine
}
// DecomposeCommandLine breaks apart its argument command line into unescaped parts using CommandLineToArgv,
// as gathered from GetCommandLine, QUERY_SERVICE_CONFIG's BinaryPathName argument, or elsewhere that
// command lines are passed around.
func DecomposeCommandLine(commandLine string) ([]string, error) {
if len(commandLine) == 0 {
return []string{}, nil
}
var argc int32
argv, err := CommandLineToArgv(StringToUTF16Ptr(commandLine), &argc)
if err != nil {
return nil, err
}
defer LocalFree(Handle(unsafe.Pointer(argv)))
var args []string
for _, v := range (*argv)[:argc] {
args = append(args, UTF16ToString((*v)[:]))
}
return args, nil
}
func CloseOnExec(fd Handle) {
SetHandleInformation(Handle(fd), HANDLE_FLAG_INHERIT, 0)
}
// FullPath retrieves the full path of the specified file.
func FullPath(name string) (path string, err error) {
p, err := UTF16PtrFromString(name)
if err != nil {
return "", err
}
n := uint32(100)
for {
buf := make([]uint16, n)
n, err = GetFullPathName(p, uint32(len(buf)), &buf[0], nil)
if err != nil {
return "", err
}
if n <= uint32(len(buf)) {
return UTF16ToString(buf[:n]), nil
}
}
}
// NewProcThreadAttributeList allocates a new ProcThreadAttributeListContainer, with the requested maximum number of attributes.
func NewProcThreadAttributeList(maxAttrCount uint32) (*ProcThreadAttributeListContainer, error) {
var size uintptr
err := initializeProcThreadAttributeList(nil, maxAttrCount, 0, &size)
if err != ERROR_INSUFFICIENT_BUFFER {
if err == nil {
return nil, errorspkg.New("unable to query buffer size from InitializeProcThreadAttributeList")
}
return nil, err
}
alloc, err := LocalAlloc(LMEM_FIXED, uint32(size))
if err != nil {
return nil, err
}
// size is guaranteed to be ≥1 by InitializeProcThreadAttributeList.
al := &ProcThreadAttributeListContainer{data: (*ProcThreadAttributeList)(unsafe.Pointer(alloc))}
err = initializeProcThreadAttributeList(al.data, maxAttrCount, 0, &size)
if err != nil {
return nil, err
}
return al, err
}
// Update modifies the ProcThreadAttributeList using UpdateProcThreadAttribute.
func (al *ProcThreadAttributeListContainer) Update(attribute uintptr, value unsafe.Pointer, size uintptr) error {
al.pointers = append(al.pointers, value)
return updateProcThreadAttribute(al.data, 0, attribute, value, size, nil, nil)
}
// Delete frees ProcThreadAttributeList's resources.
func (al *ProcThreadAttributeListContainer) Delete() {
deleteProcThreadAttributeList(al.data)
LocalFree(Handle(unsafe.Pointer(al.data)))
al.data = nil
al.pointers = nil
}
// List returns the actual ProcThreadAttributeList to be passed to StartupInfoEx.
func (al *ProcThreadAttributeListContainer) List() *ProcThreadAttributeList {
return al.data
}