authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2026-01-06 11:05:50+00:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2026-01-06 11:15:33+00:00
log073ef0f393ff4297e8d48a72f30a605c2d272289
treefd179193b6bd11d0eb981f3add5923f9fca3a22f
parent111165513156d5732d85e5ccb52b9d8bded41ffa
signaturelock-open Commit is signed but in an unrecognized format.

std.Thread: mask all signals before unmapping stack

As the comment explains, if a signal were to arrive between a detached thread's `munmap` and `exit` calls, the signal handler would immediately trigger SIGSEGV due to the stack being unmapped. To solve this, we need to block all signals before entering this logic. The musl implementation which this logic was ported from does this exact thing; that logic was just lost when porting. Notably, this would lead to a crash with no stack trace, because the SIGSEGV handler would itself crash due to the missing stack.

1 files changed, 4 insertions(+), 0 deletions(-)

lib/std/Thread.zig+4
...@@ -1224,6 +1224,10 @@ const LinuxThreadImpl = struct {...@@ -1224,6 +1224,10 @@ const LinuxThreadImpl = struct {
1224 /// Ported over from musl libc's pthread detached implementation:1224 /// Ported over from musl libc's pthread detached implementation:
1225 /// https://github.com/ifduyue/musl/search?q=__unmapself1225 /// https://github.com/ifduyue/musl/search?q=__unmapself
1226 fn freeAndExit(self: *ThreadCompletion) noreturn {1226 fn freeAndExit(self: *ThreadCompletion) noreturn {
1227 // If a signal were delivered between SYS_munmap and SYS_exit, any installed signal
1228 // handler would immediately segfault due to the stack being unmapped. To avoid this,
1229 // we need to mask all signals before entering the inline asm.
1230 posix.sigprocmask(std.posix.SIG.BLOCK, &std.os.linux.sigfillset(), null);
1227 switch (target.cpu.arch) {1231 switch (target.cpu.arch) {
1228 .x86 => asm volatile (1232 .x86 => asm volatile (
1229 \\ movl $91, %%eax # SYS_munmap1233 \\ movl $91, %%eax # SYS_munmap