From bb9f97e0856dd5c0dcc0c71633b7c15b0587b68b Mon Sep 17 00:00:00 2001 From: Brandon Black Date: Mon, 30 Mar 2026 06:53:39 -0500 Subject: [PATCH 1/3] std.os.linux: add set_tid_address --- lib/std/os/linux.zig | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/std/os/linux.zig b/lib/std/os/linux.zig index 12b3e482b51d41a48cfe836f5e45a1a4b1c368c0..eb4908f3318a4bc8dff8c3bc11dc1b7ed1bc448b 100644 --- a/lib/std/os/linux.zig +++ b/lib/std/os/linux.zig @@ -1584,6 +1584,11 @@ pub fn clone2(flags: u32, child_stack_ptr: usize) usize { return syscall2(.clone, flags, child_stack_ptr); } +/// This call cannot fail, and the return value is the caller's thread id +pub fn set_tid_address(tidptr: ?*pid_t) pid_t { + return @intCast(@as(u32, @truncate(syscall1(.set_tid_address, @intFromPtr(tidptr))))); +} + pub fn close(fd: fd_t) usize { return syscall1(.close, @as(usize, @bitCast(@as(isize, fd)))); } -- 2.54.0 From 614cd66e7e9b7323dd67bf208fda9abf296721b4 Mon Sep 17 00:00:00 2001 From: Brandon Black Date: Mon, 30 Mar 2026 06:56:14 -0500 Subject: [PATCH 2/3] LinuxThreadImpl: clear tidptr during detached exit Fixes: #31714 --- lib/std/Thread.zig | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/std/Thread.zig b/lib/std/Thread.zig index 68510cf5b1ddfca7cb342639bf8f8163e032343e..6be9f06f779bd88664e1b2a55e54c5a90dc06932 100644 --- a/lib/std/Thread.zig +++ b/lib/std/Thread.zig @@ -1147,6 +1147,11 @@ const LinuxThreadImpl = struct { /// Ported over from musl libc's pthread detached implementation: /// https://github.com/ifduyue/musl/search?q=__unmapself fn freeAndExit(self: *ThreadCompletion) noreturn { + // If we do not reset the child_tidptr to null here, the kernel would later write the + // value zero to that address, which is inside the block we're unmapping below, after + // our thread exits. This can sometimes corrupt memory in other mmap blocks from + // unrelated concurrent threads. + _ = linux.set_tid_address(null); // If a signal were delivered between SYS_munmap and SYS_exit, any installed signal // handler would immediately segfault due to the stack being unmapped. To avoid this, // we need to mask all signals before entering the inline asm. -- 2.54.0 From 1226bb9268f20740a69afd3097d40b4d3e371647 Mon Sep 17 00:00:00 2001 From: Brandon Black Date: Mon, 30 Mar 2026 07:01:40 -0500 Subject: [PATCH 3/3] LinuxThreadImpl: be precise in tls.prepareArea arg This isn't causing any functional problem today, but technically `mapped[tls_offset..]` runs past the tls part of `mapped` and into the `Instance` storage, and currently `prepareArea()` memsets its entire argument to zero. It is only the current layout and initialization order of `mapped` that prevents this from being a problem. Being more precise here avoids future footguns if any of that changes. --- lib/std/Thread.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/std/Thread.zig b/lib/std/Thread.zig index 6be9f06f779bd88664e1b2a55e54c5a90dc06932..8f31add6e20892c48bf1872fc6829ca55a4bd357 100644 --- a/lib/std/Thread.zig +++ b/lib/std/Thread.zig @@ -1489,7 +1489,7 @@ const LinuxThreadImpl = struct { } // Prepare the TLS segment and prepare a user_desc struct when needed on x86 - var tls_ptr = linux.tls.prepareArea(mapped[tls_offset..]); + var tls_ptr = linux.tls.prepareArea(mapped[tls_offset..][0..linux.tls.area_desc.size]); var user_desc: if (target.cpu.arch == .x86) linux.user_desc else void = undefined; if (target.cpu.arch == .x86) { defer tls_ptr = @intFromPtr(&user_desc); -- 2.54.0