Repository: CTSRD-CHERI/cheribsd
Pull request #2402: rtld-elf: Set bounds for dlsym(3) of a TLS symbol on Morello
Author: login: jrtc27
Created: 2025-05-05T01:33:46Z | Merged: 2025-05-07T21:02:03Z

CHERI-RISC-V (like CHERI-MIPS) does not currently set bounds for references to TLS symbols, so dlsym(3) also did not do so. However, Morello's ABI does set bounds (albeit dynamically whenever taking a reference), so dlsym(3) should match and set bounds.


Diff:
diff --git a/bin/cheribsdtest/cheribsdtest.h b/bin/cheribsdtest/cheribsdtest.h
index 2292ec12c07c..2340f14aa1c4 100644
--- a/bin/cheribsdtest/cheribsdtest.h
+++ b/bin/cheribsdtest/cheribsdtest.h
@@ -253,6 +253,14 @@ _cheribsdtest_check_cap_eq(void *__capability a, void *__capability b,
 #define CHERIBSDTEST_CHECK_EQ_CAP(a, b)	\
 	_cheribsdtest_check_cap_eq(a, b, __STRING(a), __STRING(b))
 
+#ifdef __CHERI_PURE_CAPABILITY__
+#define	CHERIBSDTEST_CHECK_EQ_PTR(a, b)	\
+	CHERIBSDTEST_CHECK_EQ_CAP(a, b)
+#else
+#define	CHERIBSDTEST_CHECK_EQ_PTR(a, b)	\
+	CHERIBSDTEST_CHECK_EQ(void *, "%p", a, b, __STRING(a), __STRING(b))
+#endif
+
 static inline void
 _cheribsdtest_check_cap_bounds_precise(void *__capability c,
     size_t expected_len)
diff --git a/bin/cheribsdtest/cheribsdtest_tls.c b/bin/cheribsdtest/cheribsdtest_tls.c
index 95c3c775ce34..4b7deb9d861c 100644
--- a/bin/cheribsdtest/cheribsdtest_tls.c
+++ b/bin/cheribsdtest/cheribsdtest_tls.c
@@ -38,6 +38,10 @@
 
 #include <cheri/cheri.h>
 
+#ifdef CHERIBSD_DYNAMIC_TESTS
+#include <cheribsdtest_dynamic.h>
+#include <dlfcn.h>
+#endif
 #include <string.h>
 
 #include "cheribsdtest.h"
@@ -115,3 +119,19 @@ CHERIBSDTEST(tls_align_4k, "Test alignment of TLS 4K array")
 		    "expected %d)", alignment, expected);
 	cheribsdtest_success();
 }
+
+#ifdef CHERIBSD_DYNAMIC_TESTS
+CHERIBSDTEST(tls_dlsym, "Test dlsym(3) for TLS matches direct reference")
+{
+	int *cheribsdtest_dynamic_tls_var_dlsym;
+
+	cheribsdtest_dynamic_tls_var_dlsym = dlsym(RTLD_DEFAULT,
+	    "cheribsdtest_dynamic_tls_var");
+	if (cheribsdtest_dynamic_tls_var_dlsym == NULL)
+		cheribsdtest_failure_errx("dlsym(3) failed: %s", dlerror());
+
+	CHERIBSDTEST_CHECK_EQ_PTR(cheribsdtest_dynamic_tls_var_dlsym,
+	    &cheribsdtest_dynamic_tls_var);
+	cheribsdtest_success();
+}
+#endif
diff --git a/lib/libc/gen/dlfcn.c b/lib/libc/gen/dlfcn.c
index 8d869ebc8a44..d62d0824638c 100644
--- a/lib/libc/gen/dlfcn.c
+++ b/lib/libc/gen/dlfcn.c
@@ -265,7 +265,7 @@ dl_iterate_phdr(int (*callback)(struct dl_phdr_info *, size_t, void *) __unused,
 		return (1);
 	_once(&dl_phdr_info_once, dl_init_phdr_info);
 	ti.ti_module = 1;
-	ti.ti_offset = 0;
+	ti.ti_offset = -TLS_DTV_OFFSET;
 	mutex_lock(&dl_phdr_info_lock);
 	phdr_info.dlpi_tls_data = __tls_get_addr(&ti);
 	ret = callback(&phdr_info, sizeof(phdr_info), data);
diff --git a/lib/libc/gen/tls.c b/lib/libc/gen/tls.c
index efb3f38363b0..f59c20f67f29 100644
--- a/lib/libc/gen/tls.c
+++ b/lib/libc/gen/tls.c
@@ -133,7 +133,8 @@ ___libc_tls_get_addr(void *vti)
  *   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
@@ -149,8 +150,6 @@ ___libc_tls_get_addr(void *vti)
  *     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.
  */
 
 /*
@@ -254,7 +253,7 @@ __libc_allocate_tls(void *oldtcb, size_t tcbsize, size_t tcbalign)
 
 		/* Adjust the DTV. */
 		dtv = tcb[0];
-		dtv[2] = (intptr_t)(tls + TLS_DTV_OFFSET);
+		dtv[2] = (intptr_t)tls;
 	} else {
 		dtv = tls_malloc(3 * sizeof(void *));
 		if (dtv == NULL) {
@@ -265,7 +264,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] = (intptr_t)(tls + TLS_DTV_OFFSET);
+		dtv[2] = (intptr_t)tls;
 
 		if (libc_tls_init_size > 0)
 			memcpy(tls, libc_tls_init, libc_tls_init_size);
diff --git a/lib/libcheribsdtest_dynamic/Makefile b/lib/libcheribsdtest_dynamic/Makefile
index d757047b73c1..c42dbcf66164 100644
--- a/lib/libcheribsdtest_dynamic/Makefile
+++ b/lib/libcheribsdtest_dynamic/Makefile
@@ -3,7 +3,8 @@ PRIVATELIB=
 MAN=	# No manpage; this is internal.
 
 SRCS=	cheribsdtest_dynamic_fptr.c					\
-	cheribsdtest_dynamic_identity_cap.c
+	cheribsdtest_dynamic_identity_cap.c				\
+	cheribsdtest_dynamic_tls.c
 
 .if ${MACHINE_CPUARCH} == "aarch64"
 SRCS+=	cheribsdtest_dynamic_ifunc.c
diff --git a/lib/libcheribsdtest_dynamic/cheribsdtest_dynamic.h b/lib/libcheribsdtest_dynamic/cheribsdtest_dynamic.h
index 70796ed97f9e..e602e6edd4d9 100644
--- a/lib/libcheribsdtest_dynamic/cheribsdtest_dynamic.h
+++ b/lib/libcheribsdtest_dynamic/cheribsdtest_dynamic.h
@@ -34,4 +34,6 @@ void * __capability cheribsdtest_dynamic_identity_cap(void * __capability cap);
 
 int cheribsdtest_dynamic_ifunc(void);
 
+extern _Thread_local int cheribsdtest_dynamic_tls_var;
+
 #endif
diff --git a/lib/libcheribsdtest_dynamic/cheribsdtest_dynamic_tls.c b/lib/libcheribsdtest_dynamic/cheribsdtest_dynamic_tls.c
new file mode 100644
index 000000000000..4d9de2cd02a7
--- /dev/null
+++ b/lib/libcheribsdtest_dynamic/cheribsdtest_dynamic_tls.c
@@ -0,0 +1,32 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2025 Jessica Clarke
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/types.h>
+
+#include "cheribsdtest_dynamic.h"
+
+_Thread_local int cheribsdtest_dynamic_tls_var;
diff --git a/libexec/rtld-elf/map_object.c b/libexec/rtld-elf/map_object.c
index adc032b095b6..ecd56af1816f 100644
--- a/libexec/rtld-elf/map_object.c
+++ b/libexec/rtld-elf/map_object.c
@@ -76,7 +76,8 @@ phdr_in_zero_page(const Elf_Ehdr *hdr)
  * for the shared object.  Returns NULL on failure.
  */
 Obj_Entry *
-map_object(int fd, const char *path, const struct stat *sb, const char* main_path)
+map_object(int fd, const char *path, const struct stat *sb, bool ismain,
+    const char *main_path)
 {
     Obj_Entry *obj;
     Elf_Ehdr *hdr;
@@ -366,8 +367,12 @@ map_object(int fd, const char *path, const struct stat *sb, const char* main_pat
     if (phinterp != NULL)
 	obj->interp = (const char *)(obj->relocbase + phinterp->p_vaddr);
     if (phtls != NULL) {
-	tls_dtv_generation++;
-	obj->tlsindex = ++tls_max_index;
+	if (ismain)
+	    obj->tlsindex = 1;
+	else {
+	    tls_dtv_generation++;
+	    obj->tlsindex = ++tls_max_index;
+	}
 	obj->tlssize = phtls->p_memsz;
 	obj->tlsalign = phtls->p_align;
 	obj->tlspoffset = phtls->p_offset;
diff --git a/libexec/rtld-elf/powerpc/reloc.c b/libexec/rtld-elf/powerpc/reloc.c
index bc1dc94808c1..693b66f521d6 100644
--- a/libexec/rtld-elf/powerpc/reloc.c
+++ b/libexec/rtld-elf/powerpc/reloc.c
@@ -845,10 +845,8 @@ void*
 __tls_get_addr(tls_index* ti)
 {
 	uintptr_t **dtvp;
-	char *p;
 
 	dtvp = &_tcb_get()->tcb_dtv;
-	p = tls_get_addr_common(dtvp, ti->ti_module, ti->ti_offset);
-
-	return (p + TLS_DTV_OFFSET);
+	return (tls_get_addr_common(dtvp, ti->ti_module, ti->ti_offset +
+	    TLS_DTV_OFFSET));
 }
diff --git a/libexec/rtld-elf/powerpc64/reloc.c b/libexec/rtld-elf/powerpc64/reloc.c
index 82db2cfd72fb..4712f2ab4102 100644
--- a/libexec/rtld-elf/powerpc64/reloc.c
+++ b/libexec/rtld-elf/powerpc64/reloc.c
@@ -765,10 +765,8 @@ void*
 __tls_get_addr(tls_index* ti)
 {
 	uintptr_t **dtvp;
-	char *p;
 
 	dtvp = &_tcb_get()->tcb_dtv;
-	p = tls_get_addr_common(dtvp, ti->ti_module, ti->ti_offset);
-
-	return (p + TLS_DTV_OFFSET);
+	return (tls_get_addr_common(dtvp, ti->ti_module, ti->ti_offset +
+	    TLS_DTV_OFFSET));
 }
diff --git a/libexec/rtld-elf/riscv/reloc.c b/libexec/rtld-elf/riscv/reloc.c
index c6c7de982436..c8f8b375eb62 100644
--- a/libexec/rtld-elf/riscv/reloc.c
+++ b/libexec/rtld-elf/riscv/reloc.c
@@ -590,10 +590,8 @@ void *
 __tls_get_addr(tls_index* ti)
 {
 	uintptr_t **dtvp;
-	void *p;
 
 	dtvp = &_tcb_get()->tcb_dtv;
-	p = tls_get_addr_common(dtvp, ti->ti_module, ti->ti_offset);
-
-	return ((char*)p + TLS_DTV_OFFSET);
+	return (tls_get_addr_common(dtvp, ti->ti_module, ti->ti_offset +
+	    TLS_DTV_OFFSET));
 }
diff --git a/libexec/rtld-elf/rtld.c b/libexec/rtld-elf/rtld.c
index 998ea94a8ffb..07836982b16a 100644
--- a/libexec/rtld-elf/rtld.c
+++ b/libexec/rtld-elf/rtld.c
@@ -551,7 +551,7 @@ _rtld(Elf_Addr *sp, func_ptr_type *exit_proc, Obj_Entry **objp)
 {
     Elf_Auxinfo *aux_info[AT_COUNT], *auxp;
 #ifndef __CHERI_PURE_CAPABILITY__
-    Elf_Auxinfo *aux, *auxpf;
+    Elf_Auxinfo *aux, *auxpf, auxtmp;
 #endif
     Objlist_Entry *entry;
     Obj_Entry *last_interposer, *obj, *preload_tail;
@@ -738,7 +738,12 @@ _rtld(Elf_Addr *sp, func_ptr_type *exit_proc, Obj_Entry **objp)
 		dbg("move aux from %p to %p", auxpf, aux);
 		/* XXXKIB insert place for AT_EXECPATH if not present */
 		for (;; auxp++, auxpf++) {
-		    *auxp = *auxpf;
+		    /*
+		     * NB: Use a temporary since *auxpf and
+		     * *auxp overlap if rtld_argc is 1
+		     */
+		    auxtmp = *auxpf;
+		    *auxp = auxtmp;
 		    if (auxp->a_type == AT_NULL)
 			    break;
 		}
@@ -871,7 +876,7 @@ _rtld(Elf_Addr *sp, func_ptr_type *exit_proc, Obj_Entry **objp)
      */
     if (fd != -1) {	/* Load the main program. */
 	dbg("loading main program");
-	obj_main = map_object(fd, argv0, NULL, _PATH_RTLD);
+	obj_main = map_object(fd, argv0, NULL, true, _PATH_RTLD);
 	close(fd);
 	if (obj_main == NULL)
 	    rtld_die();
@@ -3349,7 +3354,7 @@ do_load_object(int fd, const char *name, char *path, struct stat *sbp,
     dbg("loading \"%s\"", printable_path(path));
     if (obj_main)
 	main_path = obj_main->path;
-    obj = map_object(fd, printable_path(path), sbp, printable_path(main_path));
+    obj = map_object(fd, printable_path(path), sbp, false, printable_path(main_path));
     if (obj == NULL)
         return (NULL);
 
@@ -4599,8 +4604,12 @@ do_dlsym(void *handle, const char *name, void *retaddr, const Ver_Entry *ve,
 	    dbg("dlsym(%s) is ifunc. Resolved to: " PTR_FMT, name, sym);
 	} else if (ELF_ST_TYPE(def->st_info) == STT_TLS) {
 	    ti.ti_module = defobj->tlsindex;
-	    ti.ti_offset = def->st_value;
+	    ti.ti_offset = def->st_value - TLS_DTV_OFFSET;
 	    sym = __tls_get_addr(&ti);
+	    /* CHERI-RISC-V ABI does not yet set TLS bounds; mirror in dlsym */
+#if !defined(__riscv) && defined(__CHERI_PURE_CAPABILITY__)
+	    sym = cheri_setbounds(sym, def->st_size);
+#endif
 	    dbg("dlsym(%s) is TLS. Resolved to: " PTR_FMT, name, sym);
 	} else {
 	    sym = make_data_pointer(def, defobj);
@@ -4816,7 +4825,7 @@ rtld_fill_dl_phdr_info(const Obj_Entry *obj, struct dl_phdr_info *phdr_info)
 	phdr_info->dlpi_tls_modid = obj->tlsindex;
 	dtvp = &_tcb_get()->tcb_dtv;
 	phdr_info->dlpi_tls_data = (char *)tls_get_addr_slow(dtvp,
-	    obj->tlsindex, 0, true) + TLS_DTV_OFFSET;
+	    obj->tlsindex, 0, true);
 	phdr_info->dlpi_adds = obj_loads;
 	phdr_info->dlpi_subs = obj_loads - obj_count;
 }
diff --git a/libexec/rtld-elf/rtld.h b/libexec/rtld-elf/rtld.h
index 631bdba887bf..bf1a6f986e71 100644
--- a/libexec/rtld-elf/rtld.h
+++ b/libexec/rtld-elf/rtld.h
@@ -527,7 +527,8 @@ symname(const Obj_Entry* obj, size_t r_symndx) {
 	return strtab_value(obj, obj->symtab[r_symndx].st_name);
 }
 const char *rtld_strerror(int);
-Obj_Entry *map_object(int, const char *, const struct stat *, const char *);
+Obj_Entry *map_object(int, const char *, const struct stat *, bool,
+    const char *);
 void *xcalloc(size_t, size_t);
 void *xmalloc(size_t);
 char *xstrdup(const char *);


Reviews:
user: login: bsdjhb
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-06T17:53:49Z
user: login: jrtc27
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-06T17:59:21Z
user: login: bsdjhb
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-07T13:54:14Z
user: login: bsdjhb
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-07T13:55:02Z
user: login: bsdjhb
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-05-07T13:55:27Z

Review comments:
diff_hunk: @@ -4601,6 +4601,10 @@ do_dlsym(void *handle, const char *name, void *retaddr, const Ver_Entry *ve,
 	    ti.ti_module = defobj->tlsindex;
 	    ti.ti_offset = def->st_value;
 	    sym = __tls_get_addr(&ti);
+	    /* CHERI-RISC-V ABI does not set TLS bounds; mirror in dlsym */
+#if !defined(__riscv) && defined(__CHERI_PURE_CAPABILITY__)
path: libexec/rtld-elf/rtld.c
user: login: bsdjhb
body: Hmm, should this be `#if defined(__aarch64__)`?
created_at: 2025-05-06T17:53:49Z
author_association: COLLABORATOR
diff_hunk: @@ -4601,6 +4601,10 @@ do_dlsym(void *handle, const char *name, void *retaddr, const Ver_Entry *ve,
 	    ti.ti_module = defobj->tlsindex;
 	    ti.ti_offset = def->st_value;
 	    sym = __tls_get_addr(&ti);
+	    /* CHERI-RISC-V ABI does not set TLS bounds; mirror in dlsym */
+#if !defined(__riscv) && defined(__CHERI_PURE_CAPABILITY__)
path: libexec/rtld-elf/rtld.c
user: login: jrtc27
body: It's a bit academic, but the aspiration here was that CHERI-RISC-V would be the last architecture to ever have such a deficient ABI (and CHERI-MIPS isn't supported any more). That is, if and when CHERI-RISC-V's ABI is changed, the LHS here is to be removed, rather than adding `|| defined(__riscv)`.
created_at: 2025-05-06T17:59:21Z
author_association: MEMBER
diff_hunk: @@ -4601,6 +4601,10 @@ do_dlsym(void *handle, const char *name, void *retaddr, const Ver_Entry *ve,
 	    ti.ti_module = defobj->tlsindex;
 	    ti.ti_offset = def->st_value;
 	    sym = __tls_get_addr(&ti);
+	    /* CHERI-RISC-V ABI does not set TLS bounds; mirror in dlsym */
+#if !defined(__riscv) && defined(__CHERI_PURE_CAPABILITY__)
path: libexec/rtld-elf/rtld.c
user: login: bsdjhb
body: Ah, ok, I meant to ask that question (in essence) of what is the optimal ABI, and agree with using what you have.
created_at: 2025-05-07T13:54:14Z
author_association: COLLABORATOR
diff_hunk: @@ -4601,6 +4601,10 @@ do_dlsym(void *handle, const char *name, void *retaddr, const Ver_Entry *ve,
 	    ti.ti_module = defobj->tlsindex;
 	    ti.ti_offset = def->st_value;
 	    sym = __tls_get_addr(&ti);
+	    /* CHERI-RISC-V ABI does not set TLS bounds; mirror in dlsym */
+#if !defined(__riscv) && defined(__CHERI_PURE_CAPABILITY__)
path: libexec/rtld-elf/rtld.c
user: login: bsdjhb
body: Maybe tweak the comment to be "does not yet set TLS bounds;"?
created_at: 2025-05-07T13:55:02Z
author_association: COLLABORATOR

PR comments:
user: login: jrtc27
created_at: 2025-05-06T17:36:04Z
body: RISC-V failures are https://reviews.freebsd.org/D50183
author_association: MEMBER
user: login: jrtc27
created_at: 2025-05-06T22:35:32Z
body: Now includes backports of my upstream fixes (and one minor improvement) that, among other things, should fix this new dlsym test.
author_association: MEMBER