Skip to content

Commit cdfdb06

Browse files
authored
[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: ``` diff --git a/compiler-rt/lib/tsan/rtl/tsan_rtl.cpp b/compiler-rt/lib/tsan/rtl/tsan_rtl.cpp index feee566..352db9aa7c 100644 --- a/compiler-rt/lib/tsan/rtl/tsan_rtl.cpp +++ b/compiler-rt/lib/tsan/rtl/tsan_rtl.cpp @@ -392,7 +392,9 @@ report_mtx(MutexTypeReport), nreported(), thread_registry([](Tid tid) -> ThreadContextBase* { - return new (Alloc(sizeof(ThreadContext))) ThreadContext(tid); + void* ptr = Alloc(sizeof(ThreadContext)); + internal_memset(ptr, 0xde, sizeof(ThreadContext)); + return new (ptr) ThreadContext(tid); }), racy_mtx(MutexTypeRacy), racy_stacks(), ``` The fix is trivial: local_head should be zero-initialized.
1 parent c7ca704 commit cdfdb06

File tree

2 files changed

+5
-1
lines changed

2 files changed

+5
-1
lines changed

compiler-rt/lib/tsan/go/test.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,10 @@ int main(void) {
9191
__tsan_go_start(thr0, &thr1, (char*)&barfoo + 1);
9292
void *thr2 = 0;
9393
__tsan_go_start(thr0, &thr2, (char*)&barfoo + 1);
94+
// Goroutine that exits without a single event.
95+
void *thr3 = 0;
96+
__tsan_go_start(thr0, &thr3, (char*)&barfoo + 1);
97+
__tsan_go_end(thr3);
9498
__tsan_func_exit(thr0);
9599
__tsan_func_enter(thr1, (char*)&foobar + 1);
96100
__tsan_func_enter(thr1, (char*)&foobar + 1);

compiler-rt/lib/tsan/rtl/tsan_trace.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ struct Trace {
190190
Mutex mtx;
191191
IList<TraceHeader, &TraceHeader::trace_parts, TracePart> parts;
192192
// First node non-queued into ctx->trace_part_recycle.
193-
TracePart* local_head;
193+
TracePart* local_head = nullptr;
194194
// Final position in the last part for finished threads.
195195
Event* final_pos = nullptr;
196196
// Number of trace parts allocated on behalf of this trace specifically.

0 commit comments

Comments
 (0)