Description
TLS in a shared object needs to call __tls_get_addr
(on most arches). A shared object loaded by dlopen will need dynamic TLS allocation if statis TLS surplus is used up (by default 1664 bytes). (On FreeBSD the surplus is 128 bytes. On musl there is no.)
msan/tsan runtimes intercept __tls_get_addr
so that dynamic TLS blocks can be recorded.
In newer glibc (since 2.19), ld.so calls malloc
and memset(..., 0, ...)
to allocate and initialize a dynamic TLS block.
The malloc
call is interposable but the memset
call is internal.
msan needs to unpoison the malloc
region. Otherwise, it may incorrectly consider the zeroed region as poisoned,
if the malloc
result reuses a previously poisoned allocation. Subsequently a false positive may be reported.
This issue has caused at least https://swiftshader-review.googlesource.com/c/SwiftShader/+/49548 and https://bugs.chromium.org/p/chromium/issues/detail?id=1211047
tsan needs to unpoison the malloc
region as well. Otherwise it may report a spurious data race.
For lsan, the interceptor is useful but not necessary.
First, the Linux InitializePlatformSpecificModules
implementation ignores leaks from the dynamic loader.
Second, allocations called by __tls_get_addr
are suppressed by a built-in rule leak:*tls_get_addr
in kStdSuppressions
(https://reviews.llvm.org/D32377).
Currently we have the following code in lib/sanitizer_common/sanitizer_tls_get_addr.cpp
//// it is important to skip static TLS blocks. Static TLS blocks are handled separately at thread creation time.
//// For one thing, this is important for performance reasons.
} else if (tls_beg >= static_tls_begin && tls_beg < static_tls_end) {
// This is the static TLS block which was initialized / unpoisoned at thread
// creation.
VReport(2, "__tls_get_addr: static tls: %p\n", tls_beg);
tls_size = 0;
} else if ((tls_beg % 4096) == sizeof(Glibc_2_19_tls_header)) {
//////// remark: this is ineffective on upstream glibc>=2.19
// We may want to check gnu_get_libc_version().
Glibc_2_19_tls_header *header = (Glibc_2_19_tls_header *)tls_beg - 1;
tls_size = header->size;
tls_beg = header->start;
VReport(2, "__tls_get_addr: glibc >=2.19 suspected; tls={%p %p}\n",
tls_beg, tls_size);
} else {
VReport(2, "__tls_get_addr: Can't guess glibc version\n");
// This may happen inside the DTOR of main thread, so just ignore it.
tls_size = 0;
}
This code had been committed when glibc 2.19 was not released. The official glibc 2.19 does not use this layout (the async-signal-safe TLS patch was reverted). This code is still useful but only effective for Google's grte branch.
In the long term we need some Thread properties API. There is recently a restarted discussion https://www.openwall.com/lists/libc-coord/2021/05/21/1. However, the proposal may take a long time to be finalized and it will take years for Linux distributions to have a glibc with the implemented API.
When implementing a fix, would be good to consider how that can work with aarch64.
If approaches like compiler instrumentation don't work, the worst is to implement clang -mtls-dialect=trad
(which will call __tls_get_addr
) and advice that aarch64 users use that option if they need dynamic TLS (dlopen & static TLS surplus is insufficient) ;-)
On aarch64, toolchains default to TLSDESC. TLSDESC doesn't provide an interposable symbol like __tls_get_addr
.
Activity