[PATCH] elf: Add la_objsearch2 with lmid parameter for rtld-audit [BZ #34093]
DJ Delorie
dj@redhat.com
Tue Sep 22 03:06:39 GMT 2026
One minor patch needed to support/xdlinfo.c
Otherwise OK.
I think the "nsid" parameter needs to be more strongly documented as
being the nsid where the object will be loaded into, not the nsid of
the object doing the search, but given we don't have any documentation
yet...
LGTM with the one patch
Reviewed-by: DJ Delorie <dj@redhat.com>
On Wed, Sep 16, 2026 at 9:19 AM Frédéric Bérat <fberat@redhat.com> wrote:
> 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.
What is the compatibility rule for this? Does the auditor need to export
functions, and if the old one is ever called, assume the new one is not
supported? If so, I think this needs to be documented somehow.
> 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.
> diff --git a/elf/Makefile b/elf/Makefile
> + tst-audit29 \
> + tst-auditmod29 \
ok
> @@ -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)
> +
Ok
> diff --git a/elf/dl-audit.c b/elf/dl-audit.c
> 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)
Ok.
> 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;
Ok.
> diff --git a/elf/dl-load.c b/elf/dl-load.c
> 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)
Ok.
> 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;
Ok.
> 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)
Ok.
> 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)
Ok.
> 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);
Ok.
> {
> 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);
Ok.
> 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);
Ok.
> 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);
Ok.
> 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);
Ok.
> 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);
Ok.
> {
> 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);
Ok.
> 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);
Ok.
> 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);
Ok.
> diff --git a/elf/link.h b/elf/link.h
> 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);
Ok.
> diff --git a/elf/rtld.c b/elf/rtld.c
>
> - enum { naudit_ifaces = 8 };
> + enum { naudit_ifaces = 9 };
Ok.
> #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";
Ok.
> diff --git a/elf/tst-audit29.c b/elf/tst-audit29.c
> +/* 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>
Ok.
> +static int restart;
> +#define CMDLINE_OPTIONS \
> + { "restart", no_argument, &restart, 1 },
Ok.
> +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;
> +}
Ok.
> +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;
Ok.
> + 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;
> +}
Ok.
> +#define TEST_FUNCTION_ARGV do_test
> +#include <support/test-driver.c>
Ok.
> diff --git a/elf/tst-auditmod29.c b/elf/tst-auditmod29.c
> +/* 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>
Ok.
> +unsigned int
> +la_version (unsigned int version)
> +{
> + fprintf (stderr, "la_version: %u\n", version);
> + return LAV_CURRENT;
> +}
Ok.
> +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;
> +}
Ok.
> +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;
> +}
Ok.
> +void
> +la_activity (uintptr_t *cookie, unsigned int flag)
> +{
> + fprintf (stderr, "la_activity: %u\n", flag);
> +}
Ok, but we don't really need this one, or any more in this file.
> +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;
> +}
Ok.
> +unsigned int
> +la_objclose (uintptr_t *cookie)
> +{
> + fprintf (stderr, "la_objclose\n");
> + return 0;
> +}
Ok.
> +void
> +la_preinit (uintptr_t *cookie)
> +{
> + fprintf (stderr, "la_preinit\n");
> +}
Ok.
> diff --git a/support/Makefile b/support/Makefile
> xdlfcn \
> + xdlinfo \
> xdlmopen \
Ok.
> xdup2 \
> diff --git a/support/xdlfcn.h b/support/xdlfcn.h
> 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);
Ok.
> diff --git a/support/xdlinfo.c b/support/xdlinfo.c
> +/* 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>
Ok.
> +void
> +xdlinfo (void *handle, int request, void *arg)
> +{
> + if (dlinfo (handle, request, arg) != 0)
> + FAIL_EXIT1 ("error: dlinfo: %s\n", dlerror ());
> +}
This needs to be "< 0" as positive values are valid successful return
codes.
> diff --git a/sysdeps/generic/ldsodefs.h b/sysdeps/generic/ldsodefs.h
> unsigned int (*objclose) (uintptr_t *);
> + char *(*objsearch2) (const char *, uintptr_t *, Lmid_t, unsigned int);
Ok.
> -/* 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;
I mentioned before that the comment was confusing but valid. I don't
know if it should remain, or assume that the manual will cover it.
More information about the Libc-alpha
mailing list