git: ed059dbfe2ef - main - rtld: free heap donelists abandoned by the lookup restart
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
Date: Thu, 29 Oct 2026 15:56:19 UTC
The branch main has been updated by kib:
URL: https://cgit.FreeBSD.org/src/commit/?id=ed059dbfe2ef85ea79c5bc2834e75b2a33bb8a61
commit ed059dbfe2ef85ea79c5bc2834e75b2a33bb8a61
Author: Konstantin Belousov <kib@FreeBSD.org>
AuthorDate: 2026-10-25 17:57:40 +0000
Commit: Konstantin Belousov <kib@FreeBSD.org>
CommitDate: 2026-10-29 15:55:37 +0000
rtld: free heap donelists abandoned by the lookup restart
A symbol lookup done under the read-locked rtld_bind_lock is restarted
with the write lock when it needs to load filtees. The restart is done
by siglongjmp() from lock_restart_for_upgrade(), which skips
donelist_free() for the heap donelists of the unwound frames. Only
do_dlsym() freed one such donelist, tracked by SymLook.donelist_mem.
The donelist of symlook_default() called from _rtld_bind(), the
donelist of get_program_var_addr() called for dlopen(RTLD_TRACE), and
the donelists of nested symlook_obj_load_filtees() calls were leaked.
Link the heap donelists allocated under the read lock into the list
owned by RtldLockState, which also owns the sigjmp_buf, instead of the
symlook request.
Fixes: 1e370f038778 ("rtld: stop using unbound alloca()")
Sponsored by: The FreeBSD Foundation
MFC after: 1 week
---
libexec/rtld-elf/rtld.c | 73 +++++++++++++++++++++++++++++++-------------
libexec/rtld-elf/rtld.h | 19 +++++++++---
libexec/rtld-elf/rtld_lock.c | 1 +
3 files changed, 67 insertions(+), 26 deletions(-)
diff --git a/libexec/rtld-elf/rtld.c b/libexec/rtld-elf/rtld.c
index 551e25fb58a5..a98a01b50766 100644
--- a/libexec/rtld-elf/rtld.c
+++ b/libexec/rtld-elf/rtld.c
@@ -1039,35 +1039,64 @@ _rtld(Elf_Addr *sp, func_ptr_type *exit_proc, Obj_Entry **objp)
* alloca() and we want that to occur within the scope of the caller.
*/
#define DLP_ALLOCA_LIMIT 100 /* 800 bytes on LP64 */
-#define donelist_init(_DLP, _REQ) do { \
+#define donelist_init(_DLP, _LOCKSTATE) do { \
DoneList *_dlp = _DLP; \
_dlp->num_alloc = obj_count; \
- _dlp->req = NULL; \
if (_dlp->num_alloc > DLP_ALLOCA_LIMIT) { \
- donelist_alloc(_dlp, _REQ); \
+ donelist_alloc(_dlp, _LOCKSTATE); \
} else { \
_dlp->objs = alloca(_dlp->num_alloc * sizeof(_dlp->objs[0])); \
+ _dlp->mem = NULL; \
} \
_dlp->num_used = 0; \
} while (0)
+/*
+ * A lookup done under the read-locked rtld_bind_lock might be
+ * restarted by lock_restart_for_upgrade(), which unwinds the stack
+ * with siglongjmp() and skips donelist_free(). Link the heap
+ * memory into the list owned by the lockstate, so that
+ * donelist_free_abandoned() frees it after the restart.
+ */
static void
-donelist_alloc(DoneList *dlp, SymLook *req)
+donelist_alloc(DoneList *dlp, RtldLockState *lockstate)
{
- dlp->objs = xcalloc(dlp->num_alloc, sizeof(dlp->objs[0]));
- if (req != NULL && req->donelist_mem == NULL) {
- req->donelist_mem = dlp->objs;
- dlp->req = req;
+ struct donelist_mem *dm;
+
+ dm = xmalloc(sizeof(*dm) + dlp->num_alloc * sizeof(dm->objs[0]));
+ dlp->objs = dm->objs;
+ dlp->mem = dm;
+ if (lockstate != NULL && lockstate_rlocked(lockstate)) {
+ TAILQ_INSERT_HEAD(&lockstate->donelists, dm, next);
+ dlp->lockstate = lockstate;
+ } else {
+ dlp->lockstate = NULL;
}
}
static void
donelist_free(DoneList *dlp)
{
- if (dlp->num_alloc > DLP_ALLOCA_LIMIT)
- free(dlp->objs);
- if (dlp->req != NULL)
- dlp->req->donelist_mem = NULL;
+ struct donelist_mem *dm;
+ RtldLockState *lockstate;
+
+ dm = dlp->mem;
+ if (dm != NULL) {
+ lockstate = dlp->lockstate;
+ if (lockstate != NULL)
+ TAILQ_REMOVE(&lockstate->donelists, dm, next);
+ }
+ free(dm);
+}
+
+static void
+donelist_free_abandoned(RtldLockState *lockstate)
+{
+ struct donelist_mem *dm, *dm1;
+
+ TAILQ_FOREACH_SAFE(dm, &lockstate->donelists, next, dm1)
+ free(dm);
+ TAILQ_INIT(&lockstate->donelists);
}
void *
@@ -1094,8 +1123,10 @@ _rtld_bind(Obj_Entry *obj, Elf_Size reloff)
lockstate_init(&lockstate);
relock:
rlock_acquire(rtld_bind_lock, &lockstate);
- if (sigsetjmp(lockstate.env, 0) != 0)
+ if (sigsetjmp(lockstate.env, 0) != 0) {
lock_upgrade(rtld_bind_lock, &lockstate);
+ donelist_free_abandoned(&lockstate);
+ }
if (obj->pltrel)
rel = (const Elf_Rel *)((const char *)obj->pltrel + reloff);
else
@@ -3952,8 +3983,10 @@ rtld_dlopen(const char *name, int fd, int mode)
if (ld_tracing != NULL) {
lockstate_init(&lockstate);
rlock_acquire(rtld_bind_lock, &lockstate);
- if (sigsetjmp(lockstate.env, 0) != 0)
+ if (sigsetjmp(lockstate.env, 0) != 0) {
lock_upgrade(rtld_bind_lock, &lockstate);
+ donelist_free_abandoned(&lockstate);
+ }
environ = __DECONST(char **,
*get_program_var_addr("environ", &lockstate));
lock_release(rtld_bind_lock, &lockstate);
@@ -4152,8 +4185,7 @@ do_dlsym(void *handle, const char *name, void *retaddr, const Ver_Entry *ve,
rlock_acquire(rtld_bind_lock, &lockstate);
if (sigsetjmp(lockstate.env, 0) != 0) {
lock_upgrade(rtld_bind_lock, &lockstate);
- free(req.donelist_mem);
- req.donelist_mem = NULL;
+ donelist_free_abandoned(&lockstate);
}
if (handle == NULL || handle == RTLD_NEXT || handle == RTLD_DEFAULT ||
handle == RTLD_SELF) {
@@ -4223,7 +4255,7 @@ do_dlsym(void *handle, const char *name, void *retaddr, const Ver_Entry *ve,
return (NULL);
}
- donelist_init(&donelist, &req);
+ donelist_init(&donelist, &lockstate);
if (obj->mainprog) {
/* Handle obtained by dlopen(NULL, ...) implies global
* scope. */
@@ -4795,7 +4827,7 @@ get_program_var_addr(const char *name, RtldLockState *lockstate)
symlook_init(&req, name);
req.lockstate = lockstate;
- donelist_init(&donelist, NULL);
+ donelist_init(&donelist, lockstate);
res = NULL;
if (symlook_global(&req, &donelist) != 0)
goto out;
@@ -4885,7 +4917,7 @@ symlook_default(SymLook *req, const Obj_Entry *refobj)
SymLook req1;
int res;
- donelist_init(&donelist, req);
+ donelist_init(&donelist, req->lockstate);
symlook_init_from_req(&req1, req);
/*
@@ -5024,7 +5056,7 @@ symlook_obj_load_filtees(SymLook *req, SymLook *req1, const Obj_Entry *obj,
flags = (req->flags & SYMLOOK_EARLY) != 0 ? RTLD_LO_EARLY : 0;
load_filtees(__DECONST(Obj_Entry *, obj), flags, req->lockstate);
- donelist_init(&donelist, NULL);
+ donelist_init(&donelist, req->lockstate);
symlook_init_from_req(req1, req);
res = symlook_needed(req1, needed, &donelist);
donelist_free(&donelist);
@@ -6382,7 +6414,6 @@ symlook_init_from_req(SymLook *dst, const SymLook *src)
dst->defobj_out = NULL;
dst->sym_out = NULL;
dst->lockstate = src->lockstate;
- dst->donelist_mem = NULL;
}
static int
diff --git a/libexec/rtld-elf/rtld.h b/libexec/rtld-elf/rtld.h
index bdcfc3dc5ded..bd58116cb97a 100644
--- a/libexec/rtld-elf/rtld.h
+++ b/libexec/rtld-elf/rtld.h
@@ -313,20 +313,30 @@ typedef struct Struct_SymCache {
const Obj_Entry *obj; /* Shared object which defines it */
} SymCache;
+/*
+ * Heap memory for the DoneList array that is too large for alloca().
+ */
+struct donelist_mem {
+ TAILQ_ENTRY(donelist_mem) next;
+ const Obj_Entry *objs[];
+};
+
/*
* This structure provides a reentrant way to keep a list of objects and
* check which ones have already been processed in some way.
*/
typedef struct Struct_DoneList {
- const Obj_Entry **objs; /* Array of object pointers */
- struct Struct_SymLook *req;
- unsigned int num_alloc; /* Allocated size of the array */
- unsigned int num_used; /* Number of array slots used */
+ const Obj_Entry **objs; /* Array of object pointers */
+ struct donelist_mem *mem; /* Heap memory for objs */
+ struct Struct_RtldLockState *lockstate; /* Lockstate listing mem */
+ unsigned int num_alloc; /* Allocated size of the array */
+ unsigned int num_used; /* Number of array slots used */
} DoneList;
struct Struct_RtldLockState {
int lockstate;
sigjmp_buf env;
+ TAILQ_HEAD(, donelist_mem) donelists;
};
struct fill_search_info_args {
@@ -349,7 +359,6 @@ typedef struct Struct_SymLook {
const Obj_Entry *defobj_out;
const Elf_Sym *sym_out;
struct Struct_RtldLockState *lockstate;
- void *donelist_mem;
} SymLook;
enum {
diff --git a/libexec/rtld-elf/rtld_lock.c b/libexec/rtld-elf/rtld_lock.c
index dbb0a9f53287..ae2a15c72652 100644
--- a/libexec/rtld-elf/rtld_lock.c
+++ b/libexec/rtld-elf/rtld_lock.c
@@ -248,6 +248,7 @@ void
lockstate_init(RtldLockState *lockstate)
{
memset(lockstate, 0, sizeof(*lockstate));
+ TAILQ_INIT(&lockstate->donelists);
}
void