Follow @Openwall on Twitter for new release announcements and other news
[<prev] [day] [month] [year] [list]
Message-Id: <20251017-dlopen-use-rpath-of-caller-dso-v1-1-46c69eda1473@iscas.ac.cn>
Date: Fri, 17 Oct 2025 18:50:52 +0800
From: Vivian Wang <wangruikang@...as.ac.cn>
To: musl@...ts.openwall.com
Cc: matthewcroughan <matt@...ughan.sh>
Subject: [PATCH] ldso: Use rpath of dso of caller in dlopen

Grab the return address using an arch-specific wrapper dlopen calling a
generic __dlopen (analogous to dlsym and __dlsym), and use it to find
the dso to use as needed_by for load_library in __dlopen. This way, when
a dso calls dlopen, the library is searched from *this* dso's rpath.

This feature is used by shared libraries that dlopen on demand other
shared libraries found in nonstandard paths.

This makes the behavior of DT_RUNPATH match glibc better. Also, since we
already use this behavior with libraries loaded with DT_NEEDED, adding
support for dlopen makes it more consistent.

By coincidence, both __dlsym and __dlopen take three arguments, the last
of which is the return address. Therefore all of the arch-specific
src/ldso/*/dlopen.s is just the corresponding dlsym.s with "dlsym"
replaced by "dlopen".

---
I am not subscribed to the mailing list, so please cc on reply. Thanks.

I have primarily tested this on aarch64. I'm not sure whether this
approach makes sense. Please advise.

One observation is that musl falls back to using rpath entries from the
entire needed_by chain, so this change in behavior shouldn't break
existing working use cases.
---
 ldso/dynlink.c                | 75 ++++++++++++++++++++++---------------------
 src/internal/dynlink.h        |  1 +
 src/ldso/__dlopen.c           | 10 ++++++
 src/ldso/aarch64/dlopen.s     |  6 ++++
 src/ldso/arm/dlopen.s         |  8 +++++
 src/ldso/dlopen.c             |  8 ++---
 src/ldso/i386/dlopen.s        | 11 +++++++
 src/ldso/loongarch64/dlopen.s |  7 ++++
 src/ldso/m68k/dlopen.s        | 12 +++++++
 src/ldso/microblaze/dlopen.s  |  6 ++++
 src/ldso/mips/dlopen.s        | 17 ++++++++++
 src/ldso/mips64/dlopen.s      | 17 ++++++++++
 src/ldso/mipsn32/dlopen.s     | 17 ++++++++++
 src/ldso/or1k/dlopen.s        |  6 ++++
 src/ldso/powerpc/dlopen.s     |  8 +++++
 src/ldso/powerpc64/dlopen.s   | 11 +++++++
 src/ldso/riscv32/dlopen.s     |  6 ++++
 src/ldso/riscv64/dlopen.s     |  6 ++++
 src/ldso/s390x/dlopen.s       |  6 ++++
 src/ldso/sh/dlopen.s          | 11 +++++++
 src/ldso/x32/dlopen.s         |  7 ++++
 src/ldso/x86_64/dlopen.s      |  7 ++++
 22 files changed, 221 insertions(+), 42 deletions(-)

diff --git a/ldso/dynlink.c b/ldso/dynlink.c
index 715948f4..996d505a 100644
--- a/ldso/dynlink.c
+++ b/ldso/dynlink.c
@@ -2096,9 +2096,42 @@ static void prepare_lazy(struct dso *p)
 	lazy_head = p;
 }
 
-void *dlopen(const char *file, int mode)
+static void *addr2dso(size_t a)
+{
+	struct dso *p;
+	size_t i;
+	if (DL_FDPIC) for (p=head; p; p=p->next) {
+		i = count_syms(p);
+		if (a-(size_t)p->funcdescs < i*sizeof(*p->funcdescs))
+			return p;
+	}
+	for (p=head; p; p=p->next) {
+		if (DL_FDPIC && p->loadmap) {
+			for (i=0; i<p->loadmap->nsegs; i++) {
+				if (a-p->loadmap->segs[i].p_vaddr
+				    < p->loadmap->segs[i].p_memsz)
+					return p;
+			}
+		} else {
+			Phdr *ph = p->phdr;
+			size_t phcnt = p->phnum;
+			size_t entsz = p->phentsize;
+			size_t base = (size_t)p->base;
+			for (; phcnt--; ph=(void *)((char *)ph+entsz)) {
+				if (ph->p_type != PT_LOAD) continue;
+				if (a-base-ph->p_vaddr < ph->p_memsz)
+					return p;
+			}
+			if (a-(size_t)p->map < p->map_len)
+				return 0;
+		}
+	}
+	return 0;
+}
+
+void *__dlopen(const char *file, int mode, void *ra)
 {
-	struct dso *volatile p, *orig_tail, *orig_syms_tail, *orig_lazy_head, *next;
+	struct dso *volatile p, *needed_by, *orig_tail, *orig_syms_tail, *orig_lazy_head, *next;
 	struct tls_module *orig_tls_tail;
 	size_t orig_tls_cnt, orig_tls_offset, orig_tls_align;
 	size_t i;
@@ -2108,6 +2141,9 @@ void *dlopen(const char *file, int mode)
 
 	if (!file) return head;
 
+	needed_by = addr2dso((size_t)ra);
+	if (!needed_by) needed_by = head;
+
 	pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cs);
 	pthread_rwlock_wrlock(&lock);
 	__inhibit_ptc();
@@ -2160,7 +2196,7 @@ void *dlopen(const char *file, int mode)
 		tail->next = 0;
 		p = 0;
 		goto end;
-	} else p = load_library(file, head);
+	} else p = load_library(file, needed_by);
 
 	if (!p) {
 		error(noload ?
@@ -2231,39 +2267,6 @@ hidden int __dl_invalid_handle(void *h)
 	return 1;
 }
 
-static void *addr2dso(size_t a)
-{
-	struct dso *p;
-	size_t i;
-	if (DL_FDPIC) for (p=head; p; p=p->next) {
-		i = count_syms(p);
-		if (a-(size_t)p->funcdescs < i*sizeof(*p->funcdescs))
-			return p;
-	}
-	for (p=head; p; p=p->next) {
-		if (DL_FDPIC && p->loadmap) {
-			for (i=0; i<p->loadmap->nsegs; i++) {
-				if (a-p->loadmap->segs[i].p_vaddr
-				    < p->loadmap->segs[i].p_memsz)
-					return p;
-			}
-		} else {
-			Phdr *ph = p->phdr;
-			size_t phcnt = p->phnum;
-			size_t entsz = p->phentsize;
-			size_t base = (size_t)p->base;
-			for (; phcnt--; ph=(void *)((char *)ph+entsz)) {
-				if (ph->p_type != PT_LOAD) continue;
-				if (a-base-ph->p_vaddr < ph->p_memsz)
-					return p;
-			}
-			if (a-(size_t)p->map < p->map_len)
-				return 0;
-		}
-	}
-	return 0;
-}
-
 static void *do_dlsym(struct dso *p, const char *s, void *ra)
 {
 	int use_deps = 0;
diff --git a/src/internal/dynlink.h b/src/internal/dynlink.h
index 40c743e2..ce718927 100644
--- a/src/internal/dynlink.h
+++ b/src/internal/dynlink.h
@@ -106,6 +106,7 @@ struct fdpic_dummy_loadmap {
 typedef void (*stage2_func)(unsigned char *, size_t *);
 
 hidden void *__dlsym(void *restrict, const char *restrict, void *restrict);
+hidden void *__dlopen(const char *, int, void *restrict);
 
 hidden void __dl_seterr(const char *, ...);
 hidden int __dl_invalid_handle(void *);
diff --git a/src/ldso/__dlopen.c b/src/ldso/__dlopen.c
new file mode 100644
index 00000000..e8e381f3
--- /dev/null
+++ b/src/ldso/__dlopen.c
@@ -0,0 +1,10 @@
+#include <dlfcn.h>
+#include "dynlink.h"
+
+static void *stub_dlopen(const char *file, int mode, void *restrict ra)
+{
+	__dl_seterr("Dynamic loading not supported");
+	return 0;
+}
+
+weak_alias(stub_dlopen, __dlopen);
diff --git a/src/ldso/aarch64/dlopen.s b/src/ldso/aarch64/dlopen.s
new file mode 100644
index 00000000..a925317c
--- /dev/null
+++ b/src/ldso/aarch64/dlopen.s
@@ -0,0 +1,6 @@
+.global dlopen
+.hidden __dlopen
+.type dlopen,%function
+dlopen:
+	mov x2,x30
+	b __dlopen
diff --git a/src/ldso/arm/dlopen.s b/src/ldso/arm/dlopen.s
new file mode 100644
index 00000000..d6754b17
--- /dev/null
+++ b/src/ldso/arm/dlopen.s
@@ -0,0 +1,8 @@
+.syntax unified
+.text
+.global dlopen
+.hidden __dlopen
+.type dlopen,%function
+dlopen:
+	mov r2,lr
+	b __dlopen
diff --git a/src/ldso/dlopen.c b/src/ldso/dlopen.c
index 69372a22..888d8bdd 100644
--- a/src/ldso/dlopen.c
+++ b/src/ldso/dlopen.c
@@ -1,10 +1,6 @@
-#include <dlfcn.h>
 #include "dynlink.h"
 
-static void *stub_dlopen(const char *file, int mode)
+void *dlopen(const char *file, int mode)
 {
-	__dl_seterr("Dynamic loading not supported");
-	return 0;
+	return __dlopen(file, mode, 0);
 }
-
-weak_alias(stub_dlopen, dlopen);
diff --git a/src/ldso/i386/dlopen.s b/src/ldso/i386/dlopen.s
new file mode 100644
index 00000000..ec98a52c
--- /dev/null
+++ b/src/ldso/i386/dlopen.s
@@ -0,0 +1,11 @@
+.text
+.global dlopen
+.hidden __dlopen
+.type dlopen,@function
+dlopen:
+	push (%esp)
+	push 12(%esp)
+	push 12(%esp)
+	call __dlopen
+	add $12,%esp
+	ret
diff --git a/src/ldso/loongarch64/dlopen.s b/src/ldso/loongarch64/dlopen.s
new file mode 100644
index 00000000..aa014d37
--- /dev/null
+++ b/src/ldso/loongarch64/dlopen.s
@@ -0,0 +1,7 @@
+.global dlopen
+.hidden __dlopen
+.type   dlopen,@function
+dlopen:
+	move      $a2, $ra
+	la.global $t0, __dlopen
+	jr        $t0
diff --git a/src/ldso/m68k/dlopen.s b/src/ldso/m68k/dlopen.s
new file mode 100644
index 00000000..5d839ca2
--- /dev/null
+++ b/src/ldso/m68k/dlopen.s
@@ -0,0 +1,12 @@
+.text
+.global dlopen
+.hidden __dlopen
+.type dlopen,@function
+dlopen:
+	move.l (%sp),-(%sp)
+	move.l 12(%sp),-(%sp)
+	move.l 12(%sp),-(%sp)
+	lea __dlopen-.-8,%a1
+	jsr (%pc,%a1)
+	add.l #12,%sp
+	rts
diff --git a/src/ldso/microblaze/dlopen.s b/src/ldso/microblaze/dlopen.s
new file mode 100644
index 00000000..940b88c9
--- /dev/null
+++ b/src/ldso/microblaze/dlopen.s
@@ -0,0 +1,6 @@
+.global dlopen
+.hidden __dlopen
+.type   dlopen,@function
+dlopen:
+	brid    __dlopen
+	add     r7, r15, r0
diff --git a/src/ldso/mips/dlopen.s b/src/ldso/mips/dlopen.s
new file mode 100644
index 00000000..791ee8d8
--- /dev/null
+++ b/src/ldso/mips/dlopen.s
@@ -0,0 +1,17 @@
+.set noreorder
+.global dlopen
+.hidden __dlopen
+.type dlopen,@function
+dlopen:
+	lui $gp, %hi(_gp_disp)
+	addiu $gp, %lo(_gp_disp)
+	addu $gp, $gp, $25
+	move $6, $ra
+	lw $25, %call16(__dlopen)($gp)
+	addiu $sp, $sp, -16
+	sw $ra, 12($sp)
+	jalr $25
+	nop
+	lw $ra, 12($sp)
+	jr $ra
+	addiu $sp, $sp, 16
diff --git a/src/ldso/mips64/dlopen.s b/src/ldso/mips64/dlopen.s
new file mode 100644
index 00000000..95c2b11c
--- /dev/null
+++ b/src/ldso/mips64/dlopen.s
@@ -0,0 +1,17 @@
+.set	noreorder
+.global	dlopen
+.hidden	__dlopen
+.type	dlopen,@function
+dlopen:
+	lui	$3, %hi(%neg(%gp_rel(dlopen)))
+	daddiu	$3, $3, %lo(%neg(%gp_rel(dlopen)))
+	daddu	$3, $3, $25
+	move	$6, $ra
+	ld	$25, %got_disp(__dlopen)($3)
+	daddiu	$sp, $sp, -32
+	sd	$ra, 24($sp)
+	jalr	$25
+	nop
+	ld	$ra, 24($sp)
+	jr	$ra
+	daddiu	$sp, $sp, 32
diff --git a/src/ldso/mipsn32/dlopen.s b/src/ldso/mipsn32/dlopen.s
new file mode 100644
index 00000000..cccd92a7
--- /dev/null
+++ b/src/ldso/mipsn32/dlopen.s
@@ -0,0 +1,17 @@
+.set	noreorder
+.global	dlopen
+.hidden	__dlopen
+.type	dlopen,@function
+dlopen:
+	lui	$3, %hi(%neg(%gp_rel(dlopen)))
+	addiu	$3, $3, %lo(%neg(%gp_rel(dlopen)))
+	addu	$3, $3, $25
+	move	$6, $ra
+	lw	$25, %got_disp(__dlopen)($3)
+	addiu	$sp, $sp, -32
+	sd	$ra, 16($sp)
+	jalr	$25
+	nop
+	ld	$ra, 16($sp)
+	jr	$ra
+	addiu	$sp, $sp, 32
diff --git a/src/ldso/or1k/dlopen.s b/src/ldso/or1k/dlopen.s
new file mode 100644
index 00000000..2e7ae870
--- /dev/null
+++ b/src/ldso/or1k/dlopen.s
@@ -0,0 +1,6 @@
+.global dlopen
+.hidden __dlopen
+.type   dlopen,@function
+dlopen:
+	l.j	__dlopen
+	 l.ori	r5, r9, 0
diff --git a/src/ldso/powerpc/dlopen.s b/src/ldso/powerpc/dlopen.s
new file mode 100644
index 00000000..49d52a5f
--- /dev/null
+++ b/src/ldso/powerpc/dlopen.s
@@ -0,0 +1,8 @@
+	.text
+	.global dlopen
+	.hidden __dlopen
+	.type   dlopen,@function
+dlopen:
+	mflr    5                      # The return address is arg3.
+	b       __dlopen
+	.size   dlopen, .-dlopen
diff --git a/src/ldso/powerpc64/dlopen.s b/src/ldso/powerpc64/dlopen.s
new file mode 100644
index 00000000..3bd43fba
--- /dev/null
+++ b/src/ldso/powerpc64/dlopen.s
@@ -0,0 +1,11 @@
+	.text
+	.global dlopen
+	.hidden __dlopen
+	.type   dlopen,@function
+dlopen:
+	addis   2, 12, .TOC.-dlopen@ha
+	addi    2,  2, .TOC.-dlopen@l
+	.localentry dlopen,.-dlopen
+	mflr    5                      # The return address is arg3.
+	b       __dlopen
+	.size   dlopen, .-dlopen
diff --git a/src/ldso/riscv32/dlopen.s b/src/ldso/riscv32/dlopen.s
new file mode 100644
index 00000000..308d4e8d
--- /dev/null
+++ b/src/ldso/riscv32/dlopen.s
@@ -0,0 +1,6 @@
+.global dlopen
+.hidden __dlopen
+.type dlopen, %function
+dlopen:
+	mv a2, ra
+	tail __dlopen
diff --git a/src/ldso/riscv64/dlopen.s b/src/ldso/riscv64/dlopen.s
new file mode 100644
index 00000000..308d4e8d
--- /dev/null
+++ b/src/ldso/riscv64/dlopen.s
@@ -0,0 +1,6 @@
+.global dlopen
+.hidden __dlopen
+.type dlopen, %function
+dlopen:
+	mv a2, ra
+	tail __dlopen
diff --git a/src/ldso/s390x/dlopen.s b/src/ldso/s390x/dlopen.s
new file mode 100644
index 00000000..ff2bb284
--- /dev/null
+++ b/src/ldso/s390x/dlopen.s
@@ -0,0 +1,6 @@
+	.global dlopen
+	.hidden __dlopen
+	.type   dlopen,@function
+dlopen:
+	lgr %r4, %r14
+	jg __dlopen
diff --git a/src/ldso/sh/dlopen.s b/src/ldso/sh/dlopen.s
new file mode 100644
index 00000000..349c71d8
--- /dev/null
+++ b/src/ldso/sh/dlopen.s
@@ -0,0 +1,11 @@
+.text
+.global dlopen
+.hidden __dlopen
+.type   dlopen, @function
+dlopen:
+	mov.l L1, r0
+1:	braf  r0
+	 sts pr, r6
+
+.align 2
+L1:	.long __dlopen@...-(1b+4-.)
diff --git a/src/ldso/x32/dlopen.s b/src/ldso/x32/dlopen.s
new file mode 100644
index 00000000..8196ab54
--- /dev/null
+++ b/src/ldso/x32/dlopen.s
@@ -0,0 +1,7 @@
+.text
+.global dlopen
+.hidden __dlopen
+.type dlopen,@function
+dlopen:
+	mov (%rsp),%rdx
+	jmp __dlopen
diff --git a/src/ldso/x86_64/dlopen.s b/src/ldso/x86_64/dlopen.s
new file mode 100644
index 00000000..8196ab54
--- /dev/null
+++ b/src/ldso/x86_64/dlopen.s
@@ -0,0 +1,7 @@
+.text
+.global dlopen
+.hidden __dlopen
+.type dlopen,@function
+dlopen:
+	mov (%rsp),%rdx
+	jmp __dlopen

---
base-commit: 1b76ff0767d01df72f692806ee5adee13c67ef88
change-id: 20251016-dlopen-use-rpath-of-caller-dso-e78fd9e573d0

Best regards,
-- 
Vivian "dramforever" Wang

Powered by blists - more mailing lists

Confused about mailing lists and their use? Read about mailing lists on Wikipedia and check out these guidelines on proper formatting of your messages.