Commit 6fd467ee authored by Austin Clements's avatar Austin Clements

runtime: ensure thread handle is valid in profileloop1

On Windows, there is currently a race between unminit closing the
thread's handle and profileloop1 suspending the thread using its
handle. If another handle reuses the same handle value, this can lead
to unpredictable results.

To fix this, we protect the thread handle with a lock and duplicate it
under this lock in profileloop1 before using it.

This is going to become a much bigger problem with non-cooperative
preemption (#10958, #24543), which uses the same basic mechanism as
profileloop1.

Change-Id: I9d62b83051df8c03f3363344438e37781a69ce16
Reviewed-on: https://go-review.googlesource.com/c/go/+/207779
Run-TryBot: Austin Clements <austin@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: default avatarCherry Zhang <cherryyz@google.com>
Reviewed-by: default avatarAlex Brainman <alex.brainman@gmail.com>
parent 91bb1d73
...@@ -143,7 +143,8 @@ func tstart_stdcall(newm *m) ...@@ -143,7 +143,8 @@ func tstart_stdcall(newm *m)
func ctrlhandler() func ctrlhandler()
type mOS struct { type mOS struct {
thread uintptr // thread handle; accessed atomically threadLock mutex // protects "thread" and prevents closing
thread uintptr // thread handle
waitsema uintptr // semaphore for parking on locks waitsema uintptr // semaphore for parking on locks
resumesema uintptr // semaphore to indicate suspend/resume resumesema uintptr // semaphore to indicate suspend/resume
...@@ -814,7 +815,11 @@ func sigblock() { ...@@ -814,7 +815,11 @@ func sigblock() {
func minit() { func minit() {
var thandle uintptr var thandle uintptr
stdcall7(_DuplicateHandle, currentProcess, currentThread, currentProcess, uintptr(unsafe.Pointer(&thandle)), 0, 0, _DUPLICATE_SAME_ACCESS) stdcall7(_DuplicateHandle, currentProcess, currentThread, currentProcess, uintptr(unsafe.Pointer(&thandle)), 0, 0, _DUPLICATE_SAME_ACCESS)
atomic.Storeuintptr(&getg().m.thread, thandle)
mp := getg().m
lock(&mp.threadLock)
mp.thread = thandle
unlock(&mp.threadLock)
// Query the true stack base from the OS. Currently we're // Query the true stack base from the OS. Currently we're
// running on a small assumed stack. // running on a small assumed stack.
...@@ -847,9 +852,11 @@ func minit() { ...@@ -847,9 +852,11 @@ func minit() {
// Called from dropm to undo the effect of an minit. // Called from dropm to undo the effect of an minit.
//go:nosplit //go:nosplit
func unminit() { func unminit() {
tp := &getg().m.thread mp := getg().m
stdcall1(_CloseHandle, *tp) lock(&mp.threadLock)
*tp = 0 stdcall1(_CloseHandle, mp.thread)
mp.thread = 0
unlock(&mp.threadLock)
} }
// Calling stdcall on os stack. // Calling stdcall on os stack.
...@@ -1018,17 +1025,25 @@ func profileloop1(param uintptr) uint32 { ...@@ -1018,17 +1025,25 @@ func profileloop1(param uintptr) uint32 {
stdcall2(_WaitForSingleObject, profiletimer, _INFINITE) stdcall2(_WaitForSingleObject, profiletimer, _INFINITE)
first := (*m)(atomic.Loadp(unsafe.Pointer(&allm))) first := (*m)(atomic.Loadp(unsafe.Pointer(&allm)))
for mp := first; mp != nil; mp = mp.alllink { for mp := first; mp != nil; mp = mp.alllink {
thread := atomic.Loaduintptr(&mp.thread) lock(&mp.threadLock)
// Do not profile threads blocked on Notes, // Do not profile threads blocked on Notes,
// this includes idle worker threads, // this includes idle worker threads,
// idle timer thread, idle heap scavenger, etc. // idle timer thread, idle heap scavenger, etc.
if thread == 0 || mp.profilehz == 0 || mp.blocked { if mp.thread == 0 || mp.profilehz == 0 || mp.blocked {
unlock(&mp.threadLock)
continue continue
} }
// mp may exit between the load above and the // Acquire our own handle to the thread.
// SuspendThread, so be careful. var thread uintptr
stdcall7(_DuplicateHandle, currentProcess, mp.thread, currentProcess, uintptr(unsafe.Pointer(&thread)), 0, 0, _DUPLICATE_SAME_ACCESS)
unlock(&mp.threadLock)
// mp may exit between the DuplicateHandle
// above and the SuspendThread. The handle
// will remain valid, but SuspendThread may
// fail.
if int32(stdcall1(_SuspendThread, thread)) == -1 { if int32(stdcall1(_SuspendThread, thread)) == -1 {
// The thread no longer exists. // The thread no longer exists.
stdcall1(_CloseHandle, thread)
continue continue
} }
if mp.profilehz != 0 && !mp.blocked { if mp.profilehz != 0 && !mp.blocked {
...@@ -1037,6 +1052,7 @@ func profileloop1(param uintptr) uint32 { ...@@ -1037,6 +1052,7 @@ func profileloop1(param uintptr) uint32 {
profilem(mp, thread) profilem(mp, thread)
} }
stdcall1(_ResumeThread, thread) stdcall1(_ResumeThread, thread)
stdcall1(_CloseHandle, thread)
} }
} }
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment