git: d04c93a2adcc - main - libc: Don't bias DTV entries by TLS_DTV_OFFSET

From: Jessica Clarke <jrtc27_at_FreeBSD.org>
Date: Tue, 06 May 2025 22:15:29 UTC
The branch main has been updated by jrtc27:

URL: https://cgit.FreeBSD.org/src/commit/?id=d04c93a2adccb4c3a17f7391126a9246326e3fea

commit d04c93a2adccb4c3a17f7391126a9246326e3fea
Author:     Jessica Clarke <jrtc27@FreeBSD.org>
AuthorDate: 2025-05-06 22:14:50 +0000
Commit:     Jessica Clarke <jrtc27@FreeBSD.org>
CommitDate: 2025-05-06 22:14:50 +0000

    libc: Don't bias DTV entries by TLS_DTV_OFFSET
    
    PowerPC and RISC-V have a non-zero TLS_DTV_OFFSET. The intent behind
    this in the design is that DTV entries are biased by this, as are (in
    the other direction) the DTPOFF/DTPREL entries in the GOT. However, this
    is pretty pointless in practice, and both FreeBSD and glibc's run-time
    linkers don't bother to bias DTV entries, instead just adding the bias
    back on at the end in __tls_get_addr. In libc we also have a minimal
    implementation of this for statically-linked binaries, which is only in
    practice used for code compiled with -fPIC (not -fPIE) that is also
    linked without TLS relaxation support. PowerPC supports linker
    relaxation for TLS sequences, so this likely never gets hit there, but
    RISC-V does not, and so easily does if you compile an executable with
    -fPIC. In this implementation we add TLS_DTV_OFFSET both to the DTV
    entries in __libc_allocate_tls and to the result of __tls_get_addr,
    meaning that any TLS accesses using the General Dynamic model in static
    binaries on RISC-V end up off by 0x800.
    
    Historically this also did not matter as __tls_get_addr was a stub that
    always returned NULL, so although 6e16d0bc4376 ("Rework alignment
    handling in __libc_allocate_tls() for Variant I of TLS layout.") added
    this DTV implementation, nothing actually read the entries. However, now
    it's a real implementation, and dl_iterate_phdr also now relies on it,
    it does matter.
    
    Fix this by not biasing the DTV entries, just like RTLD. We could
    instead stop adding TLS_DTV_OFFSET in __tls_get_addr, but being
    consistent between libc and RTLD seems better.
    
    (Note this also applies to MIPS on stable/13)
    
    Reviewed by:    kib
    Fixes:          ca46b5698e8a ("libc: implement __tls_get_addr() for static binaries")
    MFC after:      1 week
    Differential Revision:  https://reviews.freebsd.org/D50181
---
 lib/libc/gen/tls.c | 9 ++++-----
 1 file changed, 4 insertions(+), 5 deletions(-)

diff --git a/lib/libc/gen/tls.c b/lib/libc/gen/tls.c
index 30b4fc583c98..8d453eccb801 100644
--- a/lib/libc/gen/tls.c
+++ b/lib/libc/gen/tls.c
@@ -151,7 +151,8 @@ libc_free_aligned(void *ptr)
  *   where TP points (with bias) to TLS and TCB immediately precedes TLS without
  *   any alignment gap[4]. Only TLS should be aligned.  The TCB[0] points to DTV
  *   vector and DTV values are biased by constant value (TLS_DTV_OFFSET) from
- *   real addresses[5].
+ *   real addresses. However, like RTLD, we don't actually bias the DTV values,
+ *   instead we compensate in __tls_get_addr for ti_offset's bias.
  *
  * [1] Ulrich Drepper: ELF Handling for Thread-Local Storage
  *     www.akkadia.org/drepper/tls.pdf
@@ -167,8 +168,6 @@ libc_free_aligned(void *ptr)
  *     but we must follow this rule due to suboptimal _tcb_set()
  *     (aka <ARCH>_SET_TP) implementation. This function doesn't expect TP but
  *     TCB as argument.
- *
- * [5] I'm not able to validate "values are biased" assertions.
  */
 
 /*
@@ -272,7 +271,7 @@ __libc_allocate_tls(void *oldtcb, size_t tcbsize, size_t tcbalign)
 
 		/* Adjust the DTV. */
 		dtv = tcb[0];
-		dtv[2] = (Elf_Addr)(tls + TLS_DTV_OFFSET);
+		dtv[2] = (Elf_Addr)tls;
 	} else {
 		dtv = __je_bootstrap_malloc(3 * sizeof(Elf_Addr));
 		if (dtv == NULL) {
@@ -283,7 +282,7 @@ __libc_allocate_tls(void *oldtcb, size_t tcbsize, size_t tcbalign)
 		tcb[0] = dtv;
 		dtv[0] = 1;		/* Generation. */
 		dtv[1] = 1;		/* Segments count. */
-		dtv[2] = (Elf_Addr)(tls + TLS_DTV_OFFSET);
+		dtv[2] = (Elf_Addr)tls;
 
 		if (libc_tls_init_size > 0)
 			memcpy(tls, libc_tls_init, libc_tls_init_size);