aboutsummaryrefslogtreecommitdiff
path: root/src/runtime/race/race_linux_arm64.patch
diff options
context:
space:
mode:
Diffstat (limited to 'src/runtime/race/race_linux_arm64.patch')
-rw-r--r--src/runtime/race/race_linux_arm64.patch63
1 files changed, 63 insertions, 0 deletions
diff --git a/src/runtime/race/race_linux_arm64.patch b/src/runtime/race/race_linux_arm64.patch
new file mode 100644
index 0000000000..04e515981a
--- /dev/null
+++ b/src/runtime/race/race_linux_arm64.patch
@@ -0,0 +1,63 @@
+From cdfdb06c9155080ec97d6e4f4dd90b6e7cefb0ca Mon Sep 17 00:00:00 2001
+From: Michael Pratt <michael@prattmic.com>
+Date: Fri, 12 Dec 2025 16:31:44 +1100
+Subject: [PATCH] [TSan] Zero-initialize Trace.local_head
+
+Trace.local_head is currently uninitialized when Trace is created. It is
+first initialized when the first event is added to the trace, via the
+first call to TraceSwitchPartImpl.
+
+However, ThreadContext::OnFinished uses local_head, assuming that it is
+initialized. If it has not been initialized, we have undefined behavior,
+likely crashing if the contents are garbage. The allocator (Alloc)
+reuses previously allocations, so the contents of the uninitialized
+memory are arbitrary.
+
+In a C/C++ TSAN binary it is likely very difficult for a thread to start
+and exit without a single event inbetween. For Go programs, code running
+in the Go runtime itself is not TSan-instrumented, so goroutines that
+exclusively run runtime code (such as GC workers) can quite reasonably
+have no TSan events.
+
+The addition of such a goroutine to the Go test.c is sufficient to
+trigger this case, though for reliable failure (segfault) I've found it
+necessary to poison the ThreadContext allocation like so:
+
+(Example patch redacted because patch tries to apply this as a real
+patch. See full commit at
+https://github.com/llvm/llvm-project/commit/cdfdb06c9155080ec97d6e4f4dd90b6e7cefb0ca).
+
+The fix is trivial: local_head should be zero-initialized.
+---
+ compiler-rt/lib/tsan/go/test.c | 4 ++++
+ compiler-rt/lib/tsan/rtl/tsan_trace.h | 2 +-
+ 2 files changed, 5 insertions(+), 1 deletion(-)
+
+diff --git a/compiler-rt/lib/tsan/go/test.c b/compiler-rt/lib/tsan/go/test.c
+index d328ab1b331d7..fcd396227a4ab 100644
+--- a/compiler-rt/lib/tsan/go/test.c
++++ b/compiler-rt/lib/tsan/go/test.c
+@@ -91,6 +91,10 @@ int main(void) {
+ __tsan_go_start(thr0, &thr1, (char*)&barfoo + 1);
+ void *thr2 = 0;
+ __tsan_go_start(thr0, &thr2, (char*)&barfoo + 1);
++ // Goroutine that exits without a single event.
++ void *thr3 = 0;
++ __tsan_go_start(thr0, &thr3, (char*)&barfoo + 1);
++ __tsan_go_end(thr3);
+ __tsan_func_exit(thr0);
+ __tsan_func_enter(thr1, (char*)&foobar + 1);
+ __tsan_func_enter(thr1, (char*)&foobar + 1);
+diff --git a/compiler-rt/lib/tsan/rtl/tsan_trace.h b/compiler-rt/lib/tsan/rtl/tsan_trace.h
+index 01bb7b34f43a2..1e791ff765fec 100644
+--- a/compiler-rt/lib/tsan/rtl/tsan_trace.h
++++ b/compiler-rt/lib/tsan/rtl/tsan_trace.h
+@@ -190,7 +190,7 @@ struct Trace {
+ Mutex mtx;
+ IList<TraceHeader, &TraceHeader::trace_parts, TracePart> parts;
+ // First node non-queued into ctx->trace_part_recycle.
+- TracePart* local_head;
++ TracePart* local_head = nullptr;
+ // Final position in the last part for finished threads.
+ Event* final_pos = nullptr;
+ // Number of trace parts allocated on behalf of this trace specifically.