Skip to content

TSan CHECK failure in ThreadRegistry::ConsumeThreadUserId on AArch64 when intercepting pthread_detach() #156829

@Katze719

Description

@Katze719

Component: ThreadSanitizer (compiler-rt)

Toolchain & versions

Clang: Ubuntu clang version 19.1.7 (++20250804090312+cd708029e0b2-1~exp1~20250804210325.79)
Target triple used for build: aarch64-linux-gnu
Linker: lld-19
TSan runtime: libclang_rt.tsan-aarch64.{a,so} from LLVM 19 packages

Runtime environment

  • Hardware: Raspberry Pi Compute Module 4 (ARMv8, AArch64)
  • Kernel: Linux 5.15, configured with CONFIG_ARM64_VA_BITS_48=y (/proc/config confirms ARM64_VA_BITS=48)
  • Page size: 4096
  • Distro/userspace: Yocto
  • ASLR: kernel.randomize_va_space=2
  • vm.mmap_rnd_bits: tested values 30, 28, 27, 26 (no change)
  • ulimit -v: unlimited
  • Execution is native on AArch64

Build Flags

-fsanitize=thread -fPIE -pie -g -O1

What happens
Running the instrumented app and invoking a specific code path that calls into a closed-source shared library (which internally creates and manages threads) reliably aborts with a TSan CHECK:

ThreadSanitizer: CHECK failed: sanitizer_thread_registry.cpp:348
"((t)) != (0)" (0x0, 0x0) (tid=3351)
    #0 __tsan::CheckUnwind() crtstuff.c (some+0x528840) (BuildId: 50d2cc61e1c5...)
    #1 __sanitizer::CheckFailed(char const*, int, char const*, unsigned long long, unsigned long long)
       crtstuff.c (some+0x499844) (BuildId: 50d2cc61e1c5...)
    #2 __sanitizer::ThreadRegistry::ConsumeThreadUserId(unsigned long)
       crtstuff.c (some+0x498598) (BuildId: 50d2cc61e1c5...)
    #3 pthread_detach <null> (some+0x4b74c0) (BuildId: 50d2cc61e1c5...)
    #4 PtUtilsLib::Thread::WaitUntilSignaled()  (application frame)
    ...

With TSAN_OPTIONS=verbosity=2 I also see (right before the CHECK) repeated lines like:

__tls_get_addr: DTLS_Destroy 0x...
__tls_get_addr: DTLS_Destroy 0x...
  • so the detach seems to happen during thread exit / a TLS destructor path.

Minimal reproducer?
I don’t have the library’s sources. However, the symptom is consistent with either:

  • a double pthread_detach (or detach after join), or
  • a detach in a TLS destructor after TSan has already "consumed" the thread id, or
  • a thread created in non-instrumented code such that TSan never registered it, but later intercepts pthread_detach on its pthread_t.

A synthetic mini test that double-detaches does not crash glibc (second call returns EINVAL), so a TSan CHECK here seems unexpected:

// build: clang -fsanitize=thread -fPIE -pie -O1 -g dd.c -o dd
#include <pthread.h>
#include <stdio.h>
void* fn(void*) { return NULL; }
int main() {
  pthread_t th;
  int rc = pthread_create(&th, 0, fn, 0);
  if (rc) return rc;
  int rc1 = pthread_detach(th);   // expected 0
  int rc2 = pthread_detach(th);   // expected EINVAL; with TSan I hit the CHECK above
  printf("detach rc1=%d rc2=%d\n", rc1, rc2);
  return 0;
}

the example will result in:

# TSAN_OPTIONS="verbosity=2" ./dd
==4014==Installed the sigaction for signal 11
==4014==Installed the sigaction for signal 7
==4014==Installed the sigaction for signal 8
==4014==Using llvm-symbolizer found at: /usr/bin/llvm-symbolizer-19
***** Running under ThreadSanitizer v3 (pid 4014) *****
ThreadSanitizer: growing heap block allocator: 0 out of 262144*4096
ThreadSanitizer: CHECK failed: sanitizer_thread_registry.cpp:348 "((t)) != (0)" (0x0, 0x0) (tid=4014)
    #0 __tsan::CheckUnwind() <null> (dd+0xdcbc0) (BuildId: 01c0d8ea751ed81d4f79412b065be84f2c38f375)
    #1 __sanitizer::CheckFailed(char const*, int, char const*, unsigned long long, unsigned long long) <null> (dd+0x4dc44) (BuildId: 01c0d8ea751ed81d4f79412b065be84f2c38f375)
    #2 __sanitizer::ThreadRegistry::ConsumeThreadUserId(unsigned long) <null> (dd+0x4c998) (BuildId: 01c0d8ea751ed81d4f79412b065be84f2c38f375)
    #3 pthread_detach <null> (dd+0x6b840) (BuildId: 01c0d8ea751ed81d4f79412b065be84f2c38f375)
    #4 main /dd.c:9:13 (dd+0xfae14) (BuildId: 01c0d8ea751ed81d4f79412b065be84f2c38f375)
    #5 <null> <null> (libc.so.6+0x284c0) (BuildId: a62e1f95f9de1a57d5b80979eedf2c7a742e3b42)
    #6 __libc_start_main <null> (libc.so.6+0x28594) (BuildId: a62e1f95f9de1a57d5b80979eedf2c7a742e3b42)
    #7 _start <null> (dd+0x385ac) (BuildId: 01c0d8ea751ed81d4f79412b065be84f2c38f375)

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions