[PATCH] elf: Add la_objsearch2 with lmid parameter for rtld-audit [BZ #34093]
Frédéric Bérat
fberat@redhat.com
Wed Sep 16 13:18:14 GMT 2026
The la_objsearch callback in the rtld-audit interface lacks the target
link map namespace ID (Lmid_t), making it difficult for auditing tools to
make namespace-specific object redirection or ABI compatibility decisions.
This change introduces la_objsearch2:
char *la_objsearch2 (const char *name, uintptr_t *cookie, Lmid_t lmid,
unsigned int flag);
If an auditing module exports la_objsearch2, the dynamic linker calls it
passing the destination Lmid_t. If only la_objsearch is exported, ld.so
falls back to la_objsearch for full backward compatibility.
Because la_objsearch2 is fully backwards-compatible (auditor DSOs can
supply both la_objsearch and la_objsearch2), LAV_CURRENT is kept
unchanged.
The test cases (tst-audit29 and tst-auditmod29) verify:
1. Dual-export compatibility (supplying both la_objsearch and la_objsearch2).
2. Preference of la_objsearch2 over la_objsearch.
3. Verification of exact Lmid_t against RTLD_DI_LMID via dlinfo across
LM_ID_BASE (0), LM_ID_NEWLM, and an existing namespace.
---
elf/Makefile | 6 ++
elf/dl-audit.c | 13 +++-
elf/dl-load.c | 35 ++++-----
elf/link.h | 2 +
elf/rtld.c | 5 +-
elf/tst-audit29.c | 150 +++++++++++++++++++++++++++++++++++++
elf/tst-auditmod29.c | 68 +++++++++++++++++
support/Makefile | 1 +
support/xdlfcn.h | 1 +
support/xdlinfo.c | 27 +++++++
sysdeps/generic/ldsodefs.h | 8 +-
11 files changed, 290 insertions(+), 26 deletions(-)
create mode 100644 elf/tst-audit29.c
create mode 100644 elf/tst-auditmod29.c
create mode 100644 support/xdlinfo.c
diff --git a/elf/Makefile b/elf/Makefile
index 4d1032bb2a..719a085a54 100644
--- a/elf/Makefile
+++ b/elf/Makefile
@@ -428,6 +428,7 @@ tests += \
tst-audit25a \
tst-audit25b \
tst-audit28 \
+ tst-audit29 \
tst-auditmany \
tst-auxobj \
tst-auxobj-dlopen \
@@ -962,6 +963,7 @@ modules-names += \
tst-auditmod24d \
tst-auditmod25 \
tst-auditmod28 \
+ tst-auditmod29 \
tst-auditmod9a \
tst-auditmod9b \
tst-auxvalmod \
@@ -2859,6 +2861,10 @@ $(objpfx)tst-audit28.out: $(objpfx)tst-auditmod28.so
$(objpfx)tst-auditmod28.so: $(libsupport)
tst-audit28-ENV = LD_AUDIT=$(objpfx)tst-auditmod28.so
+$(objpfx)tst-audit29.out: $(objpfx)tst-auditmod29.so \
+ $(objpfx)tst-audit18mod.so
+tst-audit29-ARGS = -- $(host-test-program-cmd)
+
# tst-sonamemove links against an older implementation of the library.
LDFLAGS-tst-sonamemove-linkmod1.so = \
-Wl,--version-script=tst-sonamemove-linkmod1.map \
diff --git a/elf/dl-audit.c b/elf/dl-audit.c
index aadc8ecc79..9ae7cdc4c6 100644
--- a/elf/dl-audit.c
+++ b/elf/dl-audit.c
@@ -51,7 +51,8 @@ _dl_audit_activity_nsid (Lmid_t nsid, int action)
}
const char *
-_dl_audit_objsearch (const char *name, struct link_map *l, unsigned int code)
+_dl_audit_objsearch (const char *name, struct link_map *l, Lmid_t nsid,
+ unsigned int code)
{
if (l == NULL || l->l_auditing || code == 0)
return name;
@@ -59,9 +60,15 @@ _dl_audit_objsearch (const char *name, struct link_map *l, unsigned int code)
struct audit_ifaces *afct = GLRO(dl_audit);
for (unsigned int cnt = 0; cnt < GLRO(dl_naudit); ++cnt)
{
- if (afct->objsearch != NULL)
+ struct auditstate *state = link_map_audit_state (l, cnt);
+ if (afct->objsearch2 != NULL)
+ {
+ name = afct->objsearch2 (name, &state->cookie, nsid, code);
+ if (name == NULL)
+ return NULL;
+ }
+ else if (afct->objsearch != NULL)
{
- struct auditstate *state = link_map_audit_state (l, cnt);
name = afct->objsearch (name, &state->cookie, code);
if (name == NULL)
return NULL;
diff --git a/elf/dl-load.c b/elf/dl-load.c
index 1621cb1bde..650fc992f2 100644
--- a/elf/dl-load.c
+++ b/elf/dl-load.c
@@ -1533,7 +1533,7 @@ print_search_path (struct r_search_path_elem **list,
In that case, FD is consumed for both successful and error returns. */
static int
open_verify (const char *name, int fd,
- struct filebuf *fbp, struct link_map *loader,
+ struct filebuf *fbp, struct link_map *loader, Lmid_t nsid,
int whatcode, int mode, bool *found_other_class, bool free_name)
{
/* This is the expected ELF header. */
@@ -1567,7 +1567,7 @@ open_verify (const char *name, int fd,
if (__glibc_unlikely (GLRO(dl_naudit) > 0))
{
const char *original_name = name;
- name = _dl_audit_objsearch (name, loader, whatcode);
+ name = _dl_audit_objsearch (name, loader, nsid, whatcode);
if (name == NULL)
return -1;
@@ -1732,8 +1732,8 @@ open_verify (const char *name, int fd,
static int
open_path (const char *name, size_t namelen, int mode,
struct r_search_path_struct *sps, char **realname,
- struct filebuf *fbp, struct link_map *loader, int whatcode,
- bool *found_other_class)
+ struct filebuf *fbp, struct link_map *loader, Lmid_t nsid,
+ int whatcode, bool *found_other_class)
{
struct r_search_path_elem **dirs = sps->dirs;
int fd = -1;
@@ -1795,7 +1795,7 @@ open_path (const char *name, size_t namelen, int mode,
if (__glibc_unlikely (GLRO(dl_debug_mask) & DL_DEBUG_LIBS))
_dl_debug_printf (" trying file=%s\n", buf);
- fd = open_verify (buf, -1, fbp, loader, whatcode, mode,
+ fd = open_verify (buf, -1, fbp, loader, nsid, whatcode, mode,
found_other_class, false);
if (this_dir->status[cnt] == unknown)
{
@@ -1967,7 +1967,7 @@ _dl_map_new_object (struct link_map *loader, const char *name,
if (__glibc_unlikely (GLRO(dl_naudit) > 0))
{
const char *before = name;
- name = _dl_audit_objsearch (name, loader, LA_SER_ORIG);
+ name = _dl_audit_objsearch (name, loader, nsid, LA_SER_ORIG);
if (name == NULL)
{
fd = -1;
@@ -2005,7 +2005,7 @@ _dl_map_new_object (struct link_map *loader, const char *name,
{
fd = open_path (name, namelen, mode,
&l->l_rpath_dirs,
- &realname, &fb, loader, LA_SER_RUNPATH,
+ &realname, &fb, loader, nsid, LA_SER_RUNPATH,
&found_other_class);
if (fd != -1)
break;
@@ -2021,8 +2021,8 @@ _dl_map_new_object (struct link_map *loader, const char *name,
"RPATH"))
fd = open_path (name, namelen, mode,
&main_map->l_rpath_dirs,
- &realname, &fb, loader ?: main_map, LA_SER_RUNPATH,
- &found_other_class);
+ &realname, &fb, loader ?: main_map, nsid,
+ LA_SER_RUNPATH, &found_other_class);
/* Also try DT_RUNPATH in the executable for LD_AUDIT dlopen
call. */
@@ -2035,7 +2035,7 @@ _dl_map_new_object (struct link_map *loader, const char *name,
if (cache_rpath (main_map, &l_rpath_dirs,
DT_RUNPATH, "RUNPATH"))
fd = open_path (name, namelen, mode, &l_rpath_dirs,
- &realname, &fb, loader ?: main_map,
+ &realname, &fb, loader ?: main_map, nsid,
LA_SER_RUNPATH, &found_other_class);
}
}
@@ -2044,7 +2044,7 @@ _dl_map_new_object (struct link_map *loader, const char *name,
if (fd == -1 && __rtld_env_path_list.dirs != (void *) -1)
fd = open_path (name, namelen, mode, &__rtld_env_path_list,
&realname, &fb,
- loader ?: GL(dl_ns)[LM_ID_BASE]._ns_loaded,
+ loader ?: GL(dl_ns)[LM_ID_BASE]._ns_loaded, nsid,
LA_SER_LIBPATH, &found_other_class);
/* Look at the RUNPATH information for this binary. */
@@ -2052,7 +2052,7 @@ _dl_map_new_object (struct link_map *loader, const char *name,
&& cache_rpath (loader, &loader->l_runpath_dirs,
DT_RUNPATH, "RUNPATH"))
fd = open_path (name, namelen, mode,
- &loader->l_runpath_dirs, &realname, &fb, loader,
+ &loader->l_runpath_dirs, &realname, &fb, loader, nsid,
LA_SER_RUNPATH, &found_other_class);
#ifdef USE_LDCONFIG
@@ -2102,8 +2102,8 @@ _dl_map_new_object (struct link_map *loader, const char *name,
{
fd = open_verify (cached, -1,
&fb, loader ?: GL(dl_ns)[nsid]._ns_loaded,
- LA_SER_CONFIG, mode, &found_other_class,
- false);
+ nsid, LA_SER_CONFIG, mode,
+ &found_other_class, false);
if (__glibc_likely (fd != -1))
realname = cached;
else
@@ -2119,7 +2119,8 @@ _dl_map_new_object (struct link_map *loader, const char *name,
|| __glibc_likely (!(l->l_flags_1 & DF_1_NODEFLIB)))
&& __rtld_search_dirs.dirs != (void *) -1)
fd = open_path (name, namelen, mode, &__rtld_search_dirs,
- &realname, &fb, l, LA_SER_DEFAULT, &found_other_class);
+ &realname, &fb, l, nsid, LA_SER_DEFAULT,
+ &found_other_class);
/* Add another newline when we are tracing the library loading. */
if (__glibc_unlikely (GLRO(dl_debug_mask) & DL_DEBUG_LIBS))
@@ -2136,8 +2137,8 @@ _dl_map_new_object (struct link_map *loader, const char *name,
else
{
fd = open_verify (realname, -1, &fb,
- loader ?: GL(dl_ns)[nsid]._ns_loaded, 0, mode,
- &found_other_class, true);
+ loader ?: GL(dl_ns)[nsid]._ns_loaded, nsid, 0,
+ mode, &found_other_class, true);
if (__glibc_unlikely (fd == -1))
free (realname);
}
diff --git a/elf/link.h b/elf/link.h
index 8a06075113..463a879963 100644
--- a/elf/link.h
+++ b/elf/link.h
@@ -193,6 +193,8 @@ extern unsigned int la_version (unsigned int __version);
extern void la_activity (uintptr_t *__cookie, unsigned int __flag);
extern char *la_objsearch (const char *__name, uintptr_t *__cookie,
unsigned int __flag);
+extern char *la_objsearch2 (const char *__name, uintptr_t *__cookie,
+ Lmid_t __lmid, unsigned int __flag);
extern unsigned int la_objopen (struct link_map *__map, Lmid_t __lmid,
uintptr_t *__cookie);
extern void la_preinit (uintptr_t *__cookie);
diff --git a/elf/rtld.c b/elf/rtld.c
index 324393d606..e80b602959 100644
--- a/elf/rtld.c
+++ b/elf/rtld.c
@@ -963,7 +963,7 @@ ERROR: audit interface '%s' requires version %d (maximum supported version %d);
return;
}
- enum { naudit_ifaces = 8 };
+ enum { naudit_ifaces = 9 };
union
{
struct audit_ifaces ifaces;
@@ -983,7 +983,8 @@ ERROR: audit interface '%s' requires version %d (maximum supported version %d);
#define STRING(s) __STRING (s)
"la_" STRING (ARCH_LA_PLTENTER) "\0"
"la_" STRING (ARCH_LA_PLTEXIT) "\0"
- "la_objclose\0";
+ "la_objclose\0"
+ "la_objsearch2\0";
unsigned int cnt = 0;
const char *cp = audit_iface_names;
do
diff --git a/elf/tst-audit29.c b/elf/tst-audit29.c
new file mode 100644
index 0000000000..7f6049c48c
--- /dev/null
+++ b/elf/tst-audit29.c
@@ -0,0 +1,150 @@
+/* Test la_objsearch2 audit callback with Lmid_t and dlinfo(RTLD_DI_LMID).
+ Copyright (C) 2026 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <https://www.gnu.org/licenses/>. */
+
+#include <getopt.h>
+#include <gnu/lib-names.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <dlfcn.h>
+#include <support/capture_subprocess.h>
+#include <support/check.h>
+#include <support/xdlfcn.h>
+#include <support/xstdio.h>
+
+static int restart;
+#define CMDLINE_OPTIONS \
+ { "restart", no_argument, &restart, 1 },
+
+static int
+handle_restart (void)
+{
+ /* 1. Base namespace (LM_ID_BASE / 0) check. */
+ void *h_base = xdlopen (LIBC_SO, RTLD_LAZY | RTLD_NOLOAD);
+ Lmid_t lmid_base = 0xdeadbeef;
+ xdlinfo (h_base, RTLD_DI_LMID, &lmid_base);
+ TEST_COMPARE (lmid_base, LM_ID_BASE);
+ printf ("SCENARIO: base_lmid=%ld\n", (long int) lmid_base);
+ xdlclose (h_base);
+
+ /* 2. New namespace (LM_ID_NEWLM) check. */
+ void *h_new = xdlmopen (LM_ID_NEWLM, LIBC_SO, RTLD_NOW);
+ Lmid_t lmid_new = 0xdeadbeef;
+ xdlinfo (h_new, RTLD_DI_LMID, &lmid_new);
+ TEST_VERIFY (lmid_new != LM_ID_BASE);
+ printf ("SCENARIO: newlm_lmid=%ld\n", (long int) lmid_new);
+
+ /* 3. Existing namespace check (lmid_new). */
+ void *h_existing = xdlmopen (lmid_new, "tst-audit18mod.so", RTLD_NOW);
+ Lmid_t lmid_existing = 0xdeadbeef;
+ xdlinfo (h_existing, RTLD_DI_LMID, &lmid_existing);
+ TEST_COMPARE (lmid_existing, lmid_new);
+ printf ("SCENARIO: existing_lmid=%ld\n", (long int) lmid_existing);
+
+ xdlclose (h_existing);
+ xdlclose (h_new);
+ return 0;
+}
+
+static int
+do_test (int argc, char *argv[])
+{
+ if (restart)
+ return handle_restart ();
+
+ char *spargv[9];
+ int i = 0;
+ for (; i < argc - 1; i++)
+ spargv[i] = argv[i + 1];
+ spargv[i++] = (char *) "--direct";
+ spargv[i++] = (char *) "--restart";
+ spargv[i] = NULL;
+
+ setenv ("LD_AUDIT", "tst-auditmod29.so", 1);
+ struct support_capture_subprocess result
+ = support_capture_subprogram (spargv[0], spargv, NULL);
+ support_capture_subprocess_check (&result, "tst-audit29", 0,
+ sc_allow_stdout | sc_allow_stderr);
+
+ bool found_objsearch2_base = false;
+ bool found_objsearch2_newlm = false;
+ bool found_legacy_objsearch = false;
+
+ long int base_lmid = -1;
+ long int newlm_lmid = -1;
+ long int existing_lmid = -1;
+
+ /* Parse stdout from restart for scenario lmid values. */
+ FILE *out_stdout = xfmemopen (result.out.buffer, result.out.length, "r");
+ char *line = NULL;
+ size_t line_len = 0;
+ while (xgetline (&line, &line_len, out_stdout))
+ {
+ if (sscanf (line, "SCENARIO: base_lmid=%ld", &base_lmid) == 1)
+ continue;
+ if (sscanf (line, "SCENARIO: newlm_lmid=%ld", &newlm_lmid) == 1)
+ continue;
+ if (sscanf (line, "SCENARIO: existing_lmid=%ld", &existing_lmid) == 1)
+ continue;
+ }
+ free (line);
+ line = NULL;
+ line_len = 0;
+ xfclose (out_stdout);
+
+ TEST_COMPARE (base_lmid, (long int) LM_ID_BASE);
+ TEST_VERIFY (newlm_lmid > (long int) LM_ID_BASE);
+ TEST_COMPARE (existing_lmid, newlm_lmid);
+
+ /* Parse stderr from restart for la_objsearch2 calls. */
+ FILE *out_stderr = xfmemopen (result.err.buffer, result.err.length, "r");
+ while (xgetline (&line, &line_len, out_stderr))
+ {
+ if (strstr (line, "la_objsearch:") != NULL)
+ found_legacy_objsearch = true;
+
+ long int lmid = -1;
+ if (strstr (line, "la_objsearch2:") != NULL)
+ {
+ char *p = strstr (line, "lmid=");
+ if (p != NULL && sscanf (p, "lmid=%ld", &lmid) == 1)
+ {
+ if (lmid == base_lmid)
+ found_objsearch2_base = true;
+ if (lmid == newlm_lmid)
+ found_objsearch2_newlm = true;
+ }
+ }
+ }
+ free (line);
+ xfclose (out_stderr);
+
+ /* Verify la_objsearch2 preference over legacy la_objsearch. */
+ TEST_VERIFY (!found_legacy_objsearch);
+
+ /* Verify la_objsearch2 received correct lmid across all scenarios. */
+ TEST_VERIFY (found_objsearch2_base);
+ TEST_VERIFY (found_objsearch2_newlm);
+
+ return 0;
+}
+
+#define TEST_FUNCTION_ARGV do_test
+#include <support/test-driver.c>
diff --git a/elf/tst-auditmod29.c b/elf/tst-auditmod29.c
new file mode 100644
index 0000000000..021d8f05b1
--- /dev/null
+++ b/elf/tst-auditmod29.c
@@ -0,0 +1,68 @@
+/* Auditor module testing la_objsearch2 with Lmid_t parameter.
+ Copyright (C) 2026 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <https://www.gnu.org/licenses/>. */
+
+#include <stdio.h>
+#include <link.h>
+
+unsigned int
+la_version (unsigned int version)
+{
+ fprintf (stderr, "la_version: %u\n", version);
+ return LAV_CURRENT;
+}
+
+char *
+la_objsearch (const char *name, uintptr_t *cookie, unsigned int flag)
+{
+ fprintf (stderr, "la_objsearch: name=%s flag=%u\n", name, flag);
+ return (char *) name;
+}
+
+char *
+la_objsearch2 (const char *name, uintptr_t *cookie, Lmid_t lmid,
+ unsigned int flag)
+{
+ fprintf (stderr, "la_objsearch2: name=%s lmid=%ld flag=%u\n", name, (long int) lmid, flag);
+ return (char *) name;
+}
+
+void
+la_activity (uintptr_t *cookie, unsigned int flag)
+{
+ fprintf (stderr, "la_activity: %u\n", flag);
+}
+
+unsigned int
+la_objopen (struct link_map *map, Lmid_t lmid, uintptr_t *cookie)
+{
+ fprintf (stderr, "la_objopen: lmid=%ld\n", (long int) lmid);
+ return LA_FLG_BINDTO | LA_FLG_BINDFROM;
+}
+
+unsigned int
+la_objclose (uintptr_t *cookie)
+{
+ fprintf (stderr, "la_objclose\n");
+ return 0;
+}
+
+void
+la_preinit (uintptr_t *cookie)
+{
+ fprintf (stderr, "la_preinit\n");
+}
diff --git a/support/Makefile b/support/Makefile
index 737aa38759..bc1fd444db 100644
--- a/support/Makefile
+++ b/support/Makefile
@@ -151,6 +151,7 @@ libsupport-routines = \
xconnect \
xcopy_file_range \
xdlfcn \
+ xdlinfo \
xdlmopen \
xdup \
xdup2 \
diff --git a/support/xdlfcn.h b/support/xdlfcn.h
index 54f61adb93..53aa67cfd5 100644
--- a/support/xdlfcn.h
+++ b/support/xdlfcn.h
@@ -28,6 +28,7 @@ void *xdlopen (const char *filename, int flags);
void *xdlmopen (Lmid_t lmid, const char *filename, int flags);
void *xdlsym (void *handle, const char *symbol);
void *xdlvsym (void *handle, const char *symbol, const char *version);
+void xdlinfo (void *handle, int request, void *arg);
void xdlclose (void *handle);
__END_DECLS
diff --git a/support/xdlinfo.c b/support/xdlinfo.c
new file mode 100644
index 0000000000..5ca8e38e07
--- /dev/null
+++ b/support/xdlinfo.c
@@ -0,0 +1,27 @@
+/* dlinfo with error checking.
+ Copyright (C) 2026 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <https://www.gnu.org/licenses/>. */
+
+#include <support/check.h>
+#include <support/xdlfcn.h>
+
+void
+xdlinfo (void *handle, int request, void *arg)
+{
+ if (dlinfo (handle, request, arg) != 0)
+ FAIL_EXIT1 ("error: dlinfo: %s\n", dlerror ());
+}
diff --git a/sysdeps/generic/ldsodefs.h b/sysdeps/generic/ldsodefs.h
index 305ca6e0df..93407995a3 100644
--- a/sysdeps/generic/ldsodefs.h
+++ b/sysdeps/generic/ldsodefs.h
@@ -260,6 +260,7 @@ struct audit_ifaces
#endif
};
unsigned int (*objclose) (uintptr_t *);
+ char *(*objsearch2) (const char *, uintptr_t *, Lmid_t, unsigned int);
struct audit_ifaces *next;
};
@@ -1380,11 +1381,10 @@ link_map_audit_state (struct link_map *l, size_t index)
}
}
-/* Call the la_objsearch from the audit modules from the link map L. If
- ORIGNAME is non NULL, it is updated with the previous name prior calling
- la_objsearch. */
+/* Call la_objsearch2 / la_objsearch from the audit modules for the link map L
+ and namespace NSID. */
const char *_dl_audit_objsearch (const char *name, struct link_map *l,
- unsigned int code)
+ Lmid_t nsid, unsigned int code)
attribute_hidden;
/* Call the la_activity from the audit modules from the link map L and issues
base-commit: e1643c8df34ee38eedb48dad108f56c18f895aca
--
2.55.0
More information about the Libc-alpha
mailing list