mirror of
https://github.com/golang/sys.git
synced 2026-02-08 11:46:04 +03:00
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>
179 lines
4.6 KiB
Go
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
|
|
}
|