windows: use Go-managed pointer list for ProcThreadAttributeList

It turns out that if you write Go pointers to Go memory, the Go compiler
must be involved so that it generates various calls to the GC in the
process. Letting Windows write Go pointers to Go memory violated this.

We fix this by having all the Windows-managed memory be just a boring
[]byte blob. Then, in order to prevent the GC from prematurely cleaning
up the pointers referenced by that []byte blob, or in the future moving
memory and attempting to fix up pointers, we copy the data to the
Windows heap and then maintain a little array of pointers that have been
used. Every time the Update function is called with a new pointer, we
make a copy and append it to the list.  Then, on Delete, we free the
pointers from the Windows heap.

Updates golang/go#44900.

Change-Id: I42340a93fd9f6b8d10340634cf833fd4559a5f4f
Reviewed-on: https://go-review.googlesource.com/c/sys/+/300369
Trust: Jason A. Donenfeld <Jason@zx2c4.com>
Run-TryBot: Jason A. Donenfeld <Jason@zx2c4.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Ian Lance Taylor <iant@golang.org>
This commit is contained in:
Jason A. Donenfeld
2021-05-21 09:01:06 +00:00
parent e8d321eab0
commit 6ca3eb03df
3 changed files with 47 additions and 51 deletions
+38 -9
View File
@@ -9,6 +9,8 @@ package windows
import ( import (
errorspkg "errors" errorspkg "errors"
"unsafe" "unsafe"
"golang.org/x/sys/internal/unsafeheader"
) )
// EscapeArg rewrites command line argument s as prescribed // EscapeArg rewrites command line argument s as prescribed
@@ -135,8 +137,8 @@ func FullPath(name string) (path string, err error) {
} }
} }
// NewProcThreadAttributeList allocates a new ProcThreadAttributeList, with the requested maximum number of attributes. // NewProcThreadAttributeList allocates a new ProcThreadAttributeListContainer, with the requested maximum number of attributes.
func NewProcThreadAttributeList(maxAttrCount uint32) (*ProcThreadAttributeList, error) { func NewProcThreadAttributeList(maxAttrCount uint32) (*ProcThreadAttributeListContainer, error) {
var size uintptr var size uintptr
err := initializeProcThreadAttributeList(nil, maxAttrCount, 0, &size) err := initializeProcThreadAttributeList(nil, maxAttrCount, 0, &size)
if err != ERROR_INSUFFICIENT_BUFFER { if err != ERROR_INSUFFICIENT_BUFFER {
@@ -145,10 +147,9 @@ func NewProcThreadAttributeList(maxAttrCount uint32) (*ProcThreadAttributeList,
} }
return nil, err return nil, err
} }
const psize = unsafe.Sizeof(uintptr(0))
// size is guaranteed to be ≥1 by InitializeProcThreadAttributeList. // size is guaranteed to be ≥1 by InitializeProcThreadAttributeList.
al := (*ProcThreadAttributeList)(unsafe.Pointer(&make([]unsafe.Pointer, (size+psize-1)/psize)[0])) al := &ProcThreadAttributeListContainer{data: (*ProcThreadAttributeList)(unsafe.Pointer(&make([]byte, size)[0]))}
err = initializeProcThreadAttributeList(al, maxAttrCount, 0, &size) err = initializeProcThreadAttributeList(al.data, maxAttrCount, 0, &size)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -156,11 +157,39 @@ func NewProcThreadAttributeList(maxAttrCount uint32) (*ProcThreadAttributeList,
} }
// Update modifies the ProcThreadAttributeList using UpdateProcThreadAttribute. // Update modifies the ProcThreadAttributeList using UpdateProcThreadAttribute.
func (al *ProcThreadAttributeList) Update(attribute uintptr, flags uint32, value unsafe.Pointer, size uintptr, prevValue unsafe.Pointer, returnedSize *uintptr) error { // Note that the value passed to this function will be copied into memory
return updateProcThreadAttribute(al, flags, attribute, value, size, prevValue, returnedSize) // allocated by LocalAlloc, the contents of which should not contain any
// Go-managed pointers, even if the passed value itself is a Go-managed
// pointer.
func (al *ProcThreadAttributeListContainer) Update(attribute uintptr, value unsafe.Pointer, size uintptr) error {
alloc, err := LocalAlloc(LMEM_FIXED, uint32(size))
if err != nil {
return err
}
var src, dst []byte
hdr := (*unsafeheader.Slice)(unsafe.Pointer(&src))
hdr.Data = value
hdr.Cap = int(size)
hdr.Len = int(size)
hdr = (*unsafeheader.Slice)(unsafe.Pointer(&dst))
hdr.Data = unsafe.Pointer(alloc)
hdr.Cap = int(size)
hdr.Len = int(size)
copy(dst, src)
al.heapAllocations = append(al.heapAllocations, alloc)
return updateProcThreadAttribute(al.data, 0, attribute, unsafe.Pointer(alloc), size, nil, nil)
} }
// Delete frees ProcThreadAttributeList's resources. // Delete frees ProcThreadAttributeList's resources.
func (al *ProcThreadAttributeList) Delete() { func (al *ProcThreadAttributeListContainer) Delete() {
deleteProcThreadAttributeList(al) deleteProcThreadAttributeList(al.data)
for i := range al.heapAllocations {
LocalFree(Handle(al.heapAllocations[i]))
}
al.heapAllocations = nil
}
// List returns the actual ProcThreadAttributeList to be passed to StartupInfoEx.
func (al *ProcThreadAttributeListContainer) List() *ProcThreadAttributeList {
return al.data
} }
-34
View File
@@ -18,7 +18,6 @@ import (
"strings" "strings"
"syscall" "syscall"
"testing" "testing"
"time"
"unsafe" "unsafe"
"golang.org/x/sys/windows" "golang.org/x/sys/windows"
@@ -506,39 +505,6 @@ func TestNTStatusConversion(t *testing.T) {
} }
} }
func TestProcThreadAttributeListPointers(t *testing.T) {
list, err := windows.NewProcThreadAttributeList(1)
if err != nil {
t.Errorf("unable to create ProcThreadAttributeList: %v", err)
}
done := make(chan struct{})
fds := make([]syscall.Handle, 20)
runtime.SetFinalizer(&fds[0], func(*syscall.Handle) {
close(done)
})
err = list.Update(windows.PROC_THREAD_ATTRIBUTE_HANDLE_LIST, 0, unsafe.Pointer(&fds[0]), uintptr(len(fds))*unsafe.Sizeof(fds[0]), nil, nil)
if err != nil {
list.Delete()
t.Errorf("unable to update ProcThreadAttributeList: %v", err)
return
}
runtime.GC()
runtime.GC()
select {
case <-done:
t.Error("ProcThreadAttributeList was garbage collected unexpectedly")
default:
}
list.Delete()
runtime.GC()
runtime.GC()
select {
case <-done:
case <-time.After(time.Second):
t.Error("ProcThreadAttributeList was not garbage collected after a second")
}
}
func TestPEBFilePath(t *testing.T) { func TestPEBFilePath(t *testing.T) {
peb := windows.RtlGetCurrentPeb() peb := windows.RtlGetCurrentPeb()
if peb == nil || peb.Ldr == nil { if peb == nil || peb.Ldr == nil {
+9 -8
View File
@@ -909,14 +909,15 @@ type StartupInfoEx struct {
// ProcThreadAttributeList is a placeholder type to represent a PROC_THREAD_ATTRIBUTE_LIST. // ProcThreadAttributeList is a placeholder type to represent a PROC_THREAD_ATTRIBUTE_LIST.
// //
// To create a *ProcThreadAttributeList, use NewProcThreadAttributeList, and // To create a *ProcThreadAttributeList, use NewProcThreadAttributeList, update
// free its memory using ProcThreadAttributeList.Delete. // it with ProcThreadAttributeListContainer.Update, free its memory using
type ProcThreadAttributeList struct { // ProcThreadAttributeListContainer.Delete, and access the list itself using
// This is of type unsafe.Pointer, not of type byte or uintptr, because // ProcThreadAttributeListContainer.List.
// the contents of it is mostly a list of pointers, and in most cases, type ProcThreadAttributeList struct{}
// that's a list of pointers to Go-allocated objects. In order to keep
// the GC from collecting these objects, we declare this as unsafe.Pointer. type ProcThreadAttributeListContainer struct {
_ [1]unsafe.Pointer data *ProcThreadAttributeList
heapAllocations []uintptr
} }
type ProcessInformation struct { type ProcessInformation struct {