Repository navigation
Add stress test for pointer-to-uintptr ioctl arguments moved by a GC stack shrink - #216
aeijdenberg-canva wants to merge 1 commit into
Conversation
…stack shrink Before creack#215, ptsname/unlockpt/Getsize/Setsize computed uintptr(unsafe.Pointer(&n)) and passed the integer through ioctl to Syscall. The conversion is only safe inside the Syscall argument list (unsafe.Pointer rule 4); done earlier, a GC stack shrink between the conversion and the syscall moves n and the kernel writes TIOCGPTN's result into the old stack. n keeps its zero value, Open() names /dev/pts/0 and opens another pty's slave, and Start's TIOCSCTTY fails with EPERM. Seen in production at ~1 in 25k pty opens; every capture was slave=/dev/pts/0 with a different real index. v1.1.19 had the same conversion but ioctl was inlined straight into Syscall, so nothing could move the stack in between. v1.1.20 made ioctl call f.Fd() (frames plus an fcntl syscall) after the conversion, which gave the GC somewhere to shrink the stack. Within Open() only a shrink can do it: os.OpenFile has already pushed the high-water mark past ptsname's frames, so growth never lands in the window. TestOpenUnderGCStackShrink keeps goroutines shrink-eligible while hammering the GC and checks every pair against a TIOCGPTN read done the safe way. It fails within milliseconds on v1.1.20 through v1.1.24 and passes on master. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
+1, we hit this in production too: Geotab's gia CLI on Linux, Go 1.27.2, built against v1.1.24. Rarely, Our repro is a GC loop plus 16 goroutines × 2000 @creack, could you tag a release that includes #215? v1.1.24 is still the latest tag, so everyone on a tagged version is exposed. Thanks! |
Can confirm that #215 is not latent. We see this when using buildkite/agent when built against v1.1.24 in a very small % of builds.
Here is a test showing it (this passes on
master, but fails when applied tov1.1.24):on v1.1.24:
Longer info
Before #215, ptsname/unlockpt/Getsize/Setsize computed uintptr(unsafe.Pointer(&n)) and passed the integer through ioctl to Syscall. The conversion is only safe inside the Syscall argument list (unsafe.Pointer rule 4); done earlier, a GC stack shrink between the conversion and the syscall moves n and the kernel writes TIOCGPTN's result into the old stack. n keeps its zero value, Open() names /dev/pts/0 and opens another pty's slave, and Start's TIOCSCTTY fails with EPERM. Seen in production at ~1 in 25k pty opens; every capture was slave=/dev/pts/0 with a different real index.
v1.1.19 had the same conversion but ioctl was inlined straight into Syscall, so nothing could move the stack in between. v1.1.20 made ioctl call f.Fd() (frames plus an fcntl syscall) after the conversion, which gave the GC somewhere to shrink the stack. Within Open() only a shrink can do it: os.OpenFile has already pushed the high-water mark past ptsname's frames, so growth never lands in the window.
TestOpenUnderGCStackShrink keeps goroutines shrink-eligible while hammering the GC and checks every pair against a TIOCGPTN read done the safe way. It fails within milliseconds on v1.1.20 through v1.1.24 and passes on master.
With thanks to Claude Fable.