Skip to content

Commit 6fd467e

Browse files
committed
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 <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Cherry Zhang <[email protected]> Reviewed-by: Alex Brainman <[email protected]>
1 parent 91bb1d7 commit 6fd467e

File tree

1 file changed

+25
-9
lines changed

1 file changed

+25
-9
lines changed

src/runtime/os_windows.go

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,8 @@ func tstart_stdcall(newm *m)
143143
func ctrlhandler()
144144

145145
type mOS struct {
146-
thread uintptr // thread handle; accessed atomically
146+
threadLock mutex // protects "thread" and prevents closing
147+
thread uintptr // thread handle
147148

148149
waitsema uintptr // semaphore for parking on locks
149150
resumesema uintptr // semaphore to indicate suspend/resume
@@ -814,7 +815,11 @@ func sigblock() {
814815
func minit() {
815816
var thandle uintptr
816817
stdcall7(_DuplicateHandle, currentProcess, currentThread, currentProcess, uintptr(unsafe.Pointer(&thandle)), 0, 0, _DUPLICATE_SAME_ACCESS)
817-
atomic.Storeuintptr(&getg().m.thread, thandle)
818+
819+
mp := getg().m
820+
lock(&mp.threadLock)
821+
mp.thread = thandle
822+
unlock(&mp.threadLock)
818823

819824
// Query the true stack base from the OS. Currently we're
820825
// running on a small assumed stack.
@@ -847,9 +852,11 @@ func minit() {
847852
// Called from dropm to undo the effect of an minit.
848853
//go:nosplit
849854
func unminit() {
850-
tp := &getg().m.thread
851-
stdcall1(_CloseHandle, *tp)
852-
*tp = 0
855+
mp := getg().m
856+
lock(&mp.threadLock)
857+
stdcall1(_CloseHandle, mp.thread)
858+
mp.thread = 0
859+
unlock(&mp.threadLock)
853860
}
854861

855862
// Calling stdcall on os stack.
@@ -1018,17 +1025,25 @@ func profileloop1(param uintptr) uint32 {
10181025
stdcall2(_WaitForSingleObject, profiletimer, _INFINITE)
10191026
first := (*m)(atomic.Loadp(unsafe.Pointer(&allm)))
10201027
for mp := first; mp != nil; mp = mp.alllink {
1021-
thread := atomic.Loaduintptr(&mp.thread)
1028+
lock(&mp.threadLock)
10221029
// Do not profile threads blocked on Notes,
10231030
// this includes idle worker threads,
10241031
// idle timer thread, idle heap scavenger, etc.
1025-
if thread == 0 || mp.profilehz == 0 || mp.blocked {
1032+
if mp.thread == 0 || mp.profilehz == 0 || mp.blocked {
1033+
unlock(&mp.threadLock)
10261034
continue
10271035
}
1028-
// mp may exit between the load above and the
1029-
// SuspendThread, so be careful.
1036+
// Acquire our own handle to the thread.
1037+
var thread uintptr
1038+
stdcall7(_DuplicateHandle, currentProcess, mp.thread, currentProcess, uintptr(unsafe.Pointer(&thread)), 0, 0, _DUPLICATE_SAME_ACCESS)
1039+
unlock(&mp.threadLock)
1040+
// mp may exit between the DuplicateHandle
1041+
// above and the SuspendThread. The handle
1042+
// will remain valid, but SuspendThread may
1043+
// fail.
10301044
if int32(stdcall1(_SuspendThread, thread)) == -1 {
10311045
// The thread no longer exists.
1046+
stdcall1(_CloseHandle, thread)
10321047
continue
10331048
}
10341049
if mp.profilehz != 0 && !mp.blocked {
@@ -1037,6 +1052,7 @@ func profileloop1(param uintptr) uint32 {
10371052
profilem(mp, thread)
10381053
}
10391054
stdcall1(_ResumeThread, thread)
1055+
stdcall1(_CloseHandle, thread)
10401056
}
10411057
}
10421058
}

0 commit comments

Comments
 (0)