Skip to content

Detect dynamic TLS allocations for glibc>=2.19 #1409

Open
@MaskRay

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions