elf: Add la_objsearch2 with lmid parameter for rtld-audit [BZ #34093]
Checks
| Context |
Check |
Description |
| redhat-pt-bot/TryBot-apply_patch |
success
|
Patch applied to master at the time it was sent
|
| linaro-tcwg-bot/tcwg_glibc_build--master-aarch64 |
success
|
Build passed
|
| redhat-pt-bot/TryBot-32bit |
success
|
Build for i686
|
| linaro-tcwg-bot/tcwg_glibc_check--master-aarch64 |
success
|
Test passed
|
| linaro-tcwg-bot/tcwg_glibc_build--master-arm |
success
|
Build passed
|
| linaro-tcwg-bot/tcwg_glibc_check--master-arm |
success
|
Test passed
|
Commit Message
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
base-commit: e1643c8df34ee38eedb48dad108f56c18f895aca
Comments
> -/* 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 think what the part you removed is trying to say, is that if the
shared object you asked for was a symlink (such as libfoo.8.so ->
libfoo.8.1.so) then the original asked-for name (libfoo.8.so) is
what's passed to la_objsearch(), not the linked-to-file
(libfoo.8.1.so) that will be actually opened.
I suspect this info should go in the upcoming documentation instead,
unless it counts as an implementation detail. It should of course be
verified first ;-)
This new API should go in the upcoming documentation too, but it's not
fair to ask for that before either the API or the documentation are in
place ;-)
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.
On 22/09/26 00:06, DJ Delorie wrote:
> 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.
My understanding is the handshake exists for the *forward* direction, where
an auditor exporting only la_objsearch2 running on an older ld.so is silently
never called, and it has no way to detect that (it can add a hack such a global
variable never called, but this add extra complexity).
With a version bump to 3 the auditor can either return 3 (old loaders reject
it) or return min(version, 3) and supply both callbacks. I think this is
exactly how the interface was designed to evolve.
A reason to *not* bump is that we will need to adapat both aarch64
(sysdeps/aarch64/dl-audit-check.h) and loongarch (sysdeps/loongarch/dl-audit-check.h),
since a bump would reject every existing v2 auditor on those targets. But I think
it is fixable, since the aarch64 restriction was about the v1->v2 than changed
the La_aarch64_regs layout, so 'lav >= 2 && lav <= LAV_CURRENT' preserves it.
And I recall from previous conversations that for most audir consumers bumping
the version is not a burden, nor the keep backwards compatbility would be a
hard requirement.
>
>> 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.
Nothing tests the fallback (auditor with only la_objsearch), I think it is worth
to worth to check this as well.
>
>> 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);
This does not trigger a _dl_map_new_object, so no objsearch and thus not
really a 'check' here.
>> + 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;
I think it would better to also math the object name along with the lmid.
>> + }
>> + 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.
On Tue, Sep 22, 2026 at 8:52 AM Adhemerval Zanella Netto
<adhemerval.zanella@linaro.org> wrote:
> > 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.
> My understanding is the handshake exists for the *forward*
> direction, where an auditor exporting only la_objsearch2 running on
> an older ld.so is silently never called, and it has no way to detect
> that (it can add a hack such a global variable never called, but
> this add extra complexity).
>
> With a version bump to 3 the auditor can either return 3 (old
> loaders reject it) or return min(version, 3) and supply both
> callbacks. I think this is exactly how the interface was designed to
> evolve.
Fred's patch doesn't bump the version, though.
And *I* know how to deal with the compatibility, I just wanted to make
sure the patch says something about it so the archives know it was
considered. Also, some documentation for audit writers would be nice,
saying *how* to write a properly backwards compatible auditor that
uses the new callback. It may be as simple as "older ld.so will call
the old callback, handle that case."
I suspect we may get more such requests for new callbacks, and having
a consistent solution and message about how to implement them in a
compatible way is important if we're not bumping LAV_CURRENT each
time.
If we *do* bump LAV_CURRENT, we'd need to document how to deal with
*that* but hopefully by then the manual will have an auditor section
and we can document it there.
> A reason to *not* bump is that we will need to adapat both aarch64
> (sysdeps/aarch64/dl-audit-check.h) and loongarch
> (sysdeps/loongarch/dl-audit-check.h), since a bump would reject
> every existing v2 auditor on those targets. But I think it is
> fixable, since the aarch64 restriction was about the v1->v2 than
> changed the La_aarch64_regs layout, so 'lav >= 2 && lav <=
> LAV_CURRENT' preserves it.
Yes, ld.so could accept either version if we bumped it, and a new
auditor would be rejected on an old ld.so. Or a new auditor could
return LAV_CURRENT-1 and handle the differences itself.
This would of course be easier if all arches had the same LAV_CURRENT.
Next time we need a real bump, we should consider bring them all up to
the same number. Or adding some other #defines to help auditors know
which LAV_CURRENT applies to which features.
I strongly support the idea of this patch. This is a problem that my
users have run into and have asked me for a solution.
I am not sure about the interactions between la_objsearch and
la_objsearch2 though. Most of my users would be fine with a LAV_CURRENT
bump and just change the interface. They will just add the missing
parameter and recompile.
Distros like RHEL which can't bump LAV_CURRENT in the middle of a major
release, I've got a nifty bit of code from a project that I've been
working on which would just need to be adapted to operate inside of
glibc. What it does is:
Assume LA_CURRENT=3
|version=la_version(LA_CURRENT); unsigned objsearch_3params=false;
if(version==2) { if( !audit_lib_has_dwarf(fd)) unload_audit_lib(); else
if( objsearch_has_3_parames(fd)) // this searches the DWARF and counts
the number of parameters objsearch_3params=true; } else if( version!=3 )
unload_audit_module(); ... ||if( objsearch_3params| )
|la_objsearch|( name, cookie, flag);
else
|la_objsearch|( name, cookie, lmid, flag);
The only real trick is the elfutils code which finds the DWARF section
if it exists and then uses the DWARF to count the number of parameters
for the function. I've done this inside of an auditor but I don't know
how to do it inside of the loader. However, I can quickly whip up an
auditor, that loads another auditor library and handles auditors which
expect the older API. I've already got that code handy. I wrote it to
handle the opposite situation where I advance LAV_CURRENT changing the
API faster than you are willing to accept in glibc.
-ben
> 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 <https://inbox.sourceware.org/libc-alpha/20260916131814.201118-1-fberat@redhat.com/#Z31elf:Makefile> | 6 ++
> elf/dl-audit.c <https://inbox.sourceware.org/libc-alpha/20260916131814.201118-1-fberat@redhat.com/#Z31elf:dl-audit.c> | 13 +++-
> elf/dl-load.c <https://inbox.sourceware.org/libc-alpha/20260916131814.201118-1-fberat@redhat.com/#Z31elf:dl-load.c> | 35 ++++-----
> elf/link.h <https://inbox.sourceware.org/libc-alpha/20260916131814.201118-1-fberat@redhat.com/#Z31elf:link.h> | 2 +
> elf/rtld.c <https://inbox.sourceware.org/libc-alpha/20260916131814.201118-1-fberat@redhat.com/#Z31elf:rtld.c> | 5 +-
> elf/tst-audit29.c <https://inbox.sourceware.org/libc-alpha/20260916131814.201118-1-fberat@redhat.com/#Z31elf:tst-audit29.c> | 150 +++++++++++++++++++++++++++++++++++++
> elf/tst-auditmod29.c <https://inbox.sourceware.org/libc-alpha/20260916131814.201118-1-fberat@redhat.com/#Z31elf:tst-auditmod29.c> | 68 +++++++++++++++++
> support/Makefile <https://inbox.sourceware.org/libc-alpha/20260916131814.201118-1-fberat@redhat.com/#Z31support:Makefile> | 1 +
> support/xdlfcn.h <https://inbox.sourceware.org/libc-alpha/20260916131814.201118-1-fberat@redhat.com/#Z31support:xdlfcn.h> | 1 +
> support/xdlinfo.c <https://inbox.sourceware.org/libc-alpha/20260916131814.201118-1-fberat@redhat.com/#Z31support:xdlinfo.c> | 27 +++++++
> sysdeps/generic/ldsodefs.h <https://inbox.sourceware.org/libc-alpha/20260916131814.201118-1-fberat@redhat.com/#Z31sysdeps: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
> <https://inbox.sourceware.org/libc-alpha/20260916131814.201118-1-fberat@redhat.com/#iZ31elf:Makefile>
> --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
> <https://inbox.sourceware.org/libc-alpha/20260916131814.201118-1-fberat@redhat.com/#iZ31elf:dl-audit.c>
> --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
> <https://inbox.sourceware.org/libc-alpha/20260916131814.201118-1-fberat@redhat.com/#iZ31elf:dl-load.c>
> --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
> <https://inbox.sourceware.org/libc-alpha/20260916131814.201118-1-fberat@redhat.com/#iZ31elf:link.h>
> --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
> <https://inbox.sourceware.org/libc-alpha/20260916131814.201118-1-fberat@redhat.com/#iZ31elf:rtld.c>
> --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
> <https://inbox.sourceware.org/libc-alpha/20260916131814.201118-1-fberat@redhat.com/#iZ31elf:tst-audit29.c>
> --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
> <https://inbox.sourceware.org/libc-alpha/20260916131814.201118-1-fberat@redhat.com/#iZ31elf:tst-auditmod29.c>
> --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
> <https://inbox.sourceware.org/libc-alpha/20260916131814.201118-1-fberat@redhat.com/#iZ31support:Makefile>
> --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
> <https://inbox.sourceware.org/libc-alpha/20260916131814.201118-1-fberat@redhat.com/#iZ31support:xdlfcn.h>
> --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
> <https://inbox.sourceware.org/libc-alpha/20260916131814.201118-1-fberat@redhat.com/#iZ31support:xdlinfo.c>
> --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
> <https://inbox.sourceware.org/libc-alpha/20260916131814.201118-1-fberat@redhat.com/#iZ31sysdeps:generic:ldsodefs.h>
> --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
>
> ------------------------------------------------------------------------
On 22/09/26 11:34, DJ Delorie wrote:
> On Tue, Sep 22, 2026 at 8:52 AM Adhemerval Zanella Netto
> <adhemerval.zanella@linaro.org> wrote:
>>> 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.
>
>> My understanding is the handshake exists for the *forward*
>> direction, where an auditor exporting only la_objsearch2 running on
>> an older ld.so is silently never called, and it has no way to detect
>> that (it can add a hack such a global variable never called, but
>> this add extra complexity).
>>
>> With a version bump to 3 the auditor can either return 3 (old
>> loaders reject it) or return min(version, 3) and supply both
>> callbacks. I think this is exactly how the interface was designed to
>> evolve.
>
> Fred's patch doesn't bump the version, though.
>
> And *I* know how to deal with the compatibility, I just wanted to make
> sure the patch says something about it so the archives know it was
> considered. Also, some documentation for audit writers would be nice,
> saying *how* to write a properly backwards compatible auditor that
> uses the new callback. It may be as simple as "older ld.so will call
> the old callback, handle that case."
Agreed, I think it would better to start document this properly. The only
documentation we have for this is from rtld-audit*7) from man-pages [1].
[1] https://man7.org/linux/man-pages/man7/rtld-audit.7.html
>
> I suspect we may get more such requests for new callbacks, and having
> a consistent solution and message about how to implement them in a
> compatible way is important if we're not bumping LAV_CURRENT each
> time.
>
> If we *do* bump LAV_CURRENT, we'd need to document how to deal with
> *that* but hopefully by then the manual will have an auditor section
> and we can document it there.
My take to bump LAV_CURRENT for this specific new interface is la_objsearch2
acts as a superset of la_objsearch; so running an audit library on an
old glibc will not trigger the new callback.
Also, if the audit library does require la_objsearch2 it might not make
sense supplying la_objsearch as well (to make LAV_CURRENT 2 compatible).
In both cases bumping the version seems the correct approach to avoid
runtime surprises.
>
>> A reason to *not* bump is that we will need to adapat both aarch64
>> (sysdeps/aarch64/dl-audit-check.h) and loongarch
>> (sysdeps/loongarch/dl-audit-check.h), since a bump would reject
>> every existing v2 auditor on those targets. But I think it is
>> fixable, since the aarch64 restriction was about the v1->v2 than
>> changed the La_aarch64_regs layout, so 'lav >= 2 && lav <=
>> LAV_CURRENT' preserves it.
>
> Yes, ld.so could accept either version if we bumped it, and a new
> auditor would be rejected on an old ld.so. Or a new auditor could
> return LAV_CURRENT-1 and handle the differences itself.
>
> This would of course be easier if all arches had the same LAV_CURRENT.
> Next time we need a real bump, we should consider bring them all up to
> the same number. Or adding some other #defines to help auditors know
> which LAV_CURRENT applies to which features.
That seems reasonable.
On Wed, Sep 23, 2026 at 2:58 PM Adhemerval Zanella Netto
<adhemerval.zanella@linaro.org> wrote:
> Agreed, I think it would better to start document this properly. The only
> documentation we have for this is from rtld-audit*7) from man-pages [1].
>
> [1] https://man7.org/linux/man-pages/man7/rtld-audit.7.html
And https://inbox.sourceware.org/libc-alpha/1707441f-446e-4d98-8ed4-1eb9d50eae28@redhat.com/T/#t
once it's accepted.
> My take to bump LAV_CURRENT for this specific new interface is la_objsearch2
> acts as a superset of la_objsearch; so running an audit library on an
> old glibc will not trigger the new callback.
Right, but the auditor can detect that by providing both callbacks and
seeing which one gets called.
On 23/09/26 16:09, DJ Delorie wrote:
> On Wed, Sep 23, 2026 at 2:58 PM Adhemerval Zanella Netto
> <adhemerval.zanella@linaro.org> wrote:
>> Agreed, I think it would better to start document this properly. The only
>> documentation we have for this is from rtld-audit*7) from man-pages [1].
>>
>> [1] https://man7.org/linux/man-pages/man7/rtld-audit.7.html
>
> And https://inbox.sourceware.org/libc-alpha/1707441f-446e-4d98-8ed4-1eb9d50eae28@redhat.com/T/#t
> once it's accepted.
Indeed, thanks for working on this.
>
>> My take to bump LAV_CURRENT for this specific new interface is la_objsearch2
>> acts as a superset of la_objsearch; so running an audit library on an
>> old glibc will not trigger the new callback.
>
> Right, but the auditor can detect that by providing both callbacks and
> seeing which one gets called.
Right, but then you move the version check from glibc to the audit module itself.
I am not sure if this is a better semantic.
On Wed, Sep 23, 2026 at 3:55 PM Adhemerval Zanella Netto
<adhemerval.zanella@linaro.org> wrote:
> Right, but then you move the version check from glibc to the audit
> module itself. I am not sure if this is a better semantic.
I think there's a couple of scenarios to consider:
1. Existing auditors will request the old LAV_CURRENT (say, 2). New
glibc will need to support that version. sysdeps/generic has the
right logic, but aarch64 and loongarch will need a range check
(currently they use ==) since the previous version is incompatible.
2. A new auditor that allows but does not require the new function can
return the OLD version (LAV_CURRENT-1) and dynamically adapt to
whatever callbacks happen.
3. An auditor requires the new callback, is built with the new
headers, and will fail to load on old glibc's.
4. A new auditor wants the old callback, but builds with the new
LAV_CURRENT.
I think bumping LAV_CURRENT will work in all cases. Not bumping it
works for 1 and 2 but not 3.
Case 4 requires glibc internals to detect which is requested, which
Fred's patch does.
Case 2 is more flexible for auditor deployers but is trickier to code,
especially as the LAV_CURRENT values are inconsistent across targets.
The only time we MUST bump LAV_CURRENT is when an existing callback
changes in a way that breaks existing auditors.
On Wed, Sep 23, 2026 at 10:27 PM DJ Delorie <dj@redhat.com> wrote:
> On Wed, Sep 23, 2026 at 3:55 PM Adhemerval Zanella Netto
> <adhemerval.zanella@linaro.org> wrote:
> > Right, but then you move the version check from glibc to the audit
> > module itself. I am not sure if this is a better semantic.
>
> I think there's a couple of scenarios to consider:
>
> 1. Existing auditors will request the old LAV_CURRENT (say, 2). New
> glibc will need to support that version. sysdeps/generic has the
> right logic, but aarch64 and loongarch will need a range check
> (currently they use ==) since the previous version is incompatible.
>
> 2. A new auditor that allows but does not require the new function can
> return the OLD version (LAV_CURRENT-1) and dynamically adapt to
> whatever callbacks happen.
>
> 3. An auditor requires the new callback, is built with the new
> headers, and will fail to load on old glibc's.
>
> 4. A new auditor wants the old callback, but builds with the new
> LAV_CURRENT.
>
> I think bumping LAV_CURRENT will work in all cases. Not bumping it
> works for 1 and 2 but not 3.
>
> Case 4 requires glibc internals to detect which is requested, which
> Fred's patch does.
>
> Case 2 is more flexible for auditor deployers but is trickier to code,
> especially as the LAV_CURRENT values are inconsistent across targets.
>
> The only time we MUST bump LAV_CURRENT is when an existing callback
> changes in a way that breaks existing auditors.
>
>
Based on Ben Woodard comment, I could also extend la_obsearch instead of
creating the new interface.
I went for a new interface because I thought it was better/simpler, but I
don't have a strong opinion about it.
Any feedback on that?
* Frederic Berat:
> Based on Ben Woodard comment, I could also extend la_obsearch instead
> of creating the new interface. I went for a new interface because I
> thought it was better/simpler, but I don't have a strong opinion about
> it. Any feedback on that?
What would we put into <link.h>? We can only have one prototype, so
this is going to have a source compatibility impact. Introducing
la_objsearch2 avoids that.
Thanks,
Florian
On 23/09/26 17:27, DJ Delorie wrote:
> On Wed, Sep 23, 2026 at 3:55 PM Adhemerval Zanella Netto
> <adhemerval.zanella@linaro.org> wrote:
>> Right, but then you move the version check from glibc to the audit
>> module itself. I am not sure if this is a better semantic.
>
> I think there's a couple of scenarios to consider:
>
> 1. Existing auditors will request the old LAV_CURRENT (say, 2). New
> glibc will need to support that version. sysdeps/generic has the
> right logic, but aarch64 and loongarch will need a range check
> (currently they use ==) since the previous version is incompatible.
I recall from previous discussion on other audit issues that backward
compatibility was not a constraint, so it should be ok to bump the
LAV_CURRENT at any time if required or when it simplifies deployment.
This was based on the current consumers of these ABI (HPC related tools),
where the audit libraries are built and deployed targetting specific
symbol/glibc versions (and source compatibility is not a burden).
>
> 2. A new auditor that allows but does not require the new function can
> return the OLD version (LAV_CURRENT-1) and dynamically adapt to
> whatever callbacks happen.
>
> 3. An auditor requires the new callback, is built with the new
> headers, and will fail to load on old glibc's.
>
> 4. A new auditor wants the old callback, but builds with the new
> LAV_CURRENT.
Can this really happen? I think for version bump either the new callback
is a superset of the old one or it is a complete new function (and thus
new entrypoints during loading).
The auditor requiring the old callback would meant that it is trying
to provide some backward compatibility.
>
> I think bumping LAV_CURRENT will work in all cases. Not bumping it
> works for 1 and 2 but not 3.
>
> Case 4 requires glibc internals to detect which is requested, which
> Fred's patch does.
>
> Case 2 is more flexible for auditor deployers but is trickier to code,
> especially as the LAV_CURRENT values are inconsistent across targets.
>
> The only time we MUST bump LAV_CURRENT is when an existing callback
> changes in a way that breaks existing auditors.
Agree, but I also think by *not* providing backward compatibility it
simplifies a *lot* the constraints. But I might be wrong with this
assumption, and there is also whether we want to keep source
compatibility.
On Mon, Sep 28, 2026 at 9:05 PM Adhemerval Zanella Netto <
adhemerval.zanella@linaro.org> wrote:
>
>
> On 23/09/26 17:27, DJ Delorie wrote:
> > On Wed, Sep 23, 2026 at 3:55 PM Adhemerval Zanella Netto
> > <adhemerval.zanella@linaro.org> wrote:
> >> Right, but then you move the version check from glibc to the audit
> >> module itself. I am not sure if this is a better semantic.
> >
> > I think there's a couple of scenarios to consider:
> >
> > 1. Existing auditors will request the old LAV_CURRENT (say, 2). New
> > glibc will need to support that version. sysdeps/generic has the
> > right logic, but aarch64 and loongarch will need a range check
> > (currently they use ==) since the previous version is incompatible.
>
> I recall from previous discussion on other audit issues that backward
> compatibility was not a constraint, so it should be ok to bump the
> LAV_CURRENT at any time if required or when it simplifies deployment.
>
> This was based on the current consumers of these ABI (HPC related tools),
> where the audit libraries are built and deployed targetting specific
> symbol/glibc versions (and source compatibility is not a burden).
>
> >
> > 2. A new auditor that allows but does not require the new function can
> > return the OLD version (LAV_CURRENT-1) and dynamically adapt to
> > whatever callbacks happen.
> >
> > 3. An auditor requires the new callback, is built with the new
> > headers, and will fail to load on old glibc's.
> >
> > 4. A new auditor wants the old callback, but builds with the new
> > LAV_CURRENT.
>
> Can this really happen? I think for version bump either the new callback
> is a superset of the old one or it is a complete new function (and thus
> new entrypoints during loading).
>
> The auditor requiring the old callback would meant that it is trying
> to provide some backward compatibility.
>
> >
> > I think bumping LAV_CURRENT will work in all cases. Not bumping it
> > works for 1 and 2 but not 3.
> >
> > Case 4 requires glibc internals to detect which is requested, which
> > Fred's patch does.
> >
> > Case 2 is more flexible for auditor deployers but is trickier to code,
> > especially as the LAV_CURRENT values are inconsistent across targets.
> >
> > The only time we MUST bump LAV_CURRENT is when an existing callback
> > changes in a way that breaks existing auditors.
>
> Agree, but I also think by *not* providing backward compatibility it
> simplifies a *lot* the constraints. But I might be wrong with this
> assumption, and there is also whether we want to keep source
> compatibility.
>
>
After double checking, Florian is right about <link.h>, sticking with
la_objsearch2 seems to be the cleaner way to avoid breaking existing builds.
If we go ahead and bump LAV_CURRENT to 3, here is how I'm planning to
handle it:
- Bump generic LAV_CURRENT to 3 and drop the LoongArch-specific header so
all arches are in sync again.
- Switch the AArch64 and LoongArch version checks to a range (lav >= 2 and
lav >= 3 respectively) so we don't break existing auditors.
Something like:
{
/* Reject broken v1 auditors, but accept v2 and v3+. */
return lav >= 2 && lav <= LAV_CURRENT;
}
- Keep the ld.so fallback to la_objsearch when la_objsearch2 isn't exported.
- Fix the xdlinfo check and update the test case to cover the fallback and
actual library loads as Adhemerval pointed out.
- Try to document all of that.
Does that sound like the right way forward to everyone before I send a v2?
Fred.
On 29/09/26 07:44, Frederic Berat wrote:
>
>
> On Mon, Sep 28, 2026 at 9:05 PM Adhemerval Zanella Netto <adhemerval.zanella@linaro.org <mailto:adhemerval.zanella@linaro.org>> wrote:
>
>
>
> On 23/09/26 17:27, DJ Delorie wrote:
> > On Wed, Sep 23, 2026 at 3:55 PM Adhemerval Zanella Netto
> > <adhemerval.zanella@linaro.org <mailto:adhemerval.zanella@linaro.org>> wrote:
> >> Right, but then you move the version check from glibc to the audit
> >> module itself. I am not sure if this is a better semantic.
> >
> > I think there's a couple of scenarios to consider:
> >
> > 1. Existing auditors will request the old LAV_CURRENT (say, 2). New
> > glibc will need to support that version. sysdeps/generic has the
> > right logic, but aarch64 and loongarch will need a range check
> > (currently they use ==) since the previous version is incompatible.
>
> I recall from previous discussion on other audit issues that backward
> compatibility was not a constraint, so it should be ok to bump the
> LAV_CURRENT at any time if required or when it simplifies deployment.
>
> This was based on the current consumers of these ABI (HPC related tools),
> where the audit libraries are built and deployed targetting specific
> symbol/glibc versions (and source compatibility is not a burden).
>
> >
> > 2. A new auditor that allows but does not require the new function can
> > return the OLD version (LAV_CURRENT-1) and dynamically adapt to
> > whatever callbacks happen.
> >
> > 3. An auditor requires the new callback, is built with the new
> > headers, and will fail to load on old glibc's.
> >
> > 4. A new auditor wants the old callback, but builds with the new
> > LAV_CURRENT.
>
> Can this really happen? I think for version bump either the new callback
> is a superset of the old one or it is a complete new function (and thus
> new entrypoints during loading).
>
> The auditor requiring the old callback would meant that it is trying
> to provide some backward compatibility.
>
> >
> > I think bumping LAV_CURRENT will work in all cases. Not bumping it
> > works for 1 and 2 but not 3.
> >
> > Case 4 requires glibc internals to detect which is requested, which
> > Fred's patch does.
> >
> > Case 2 is more flexible for auditor deployers but is trickier to code,
> > especially as the LAV_CURRENT values are inconsistent across targets.
> >
> > The only time we MUST bump LAV_CURRENT is when an existing callback
> > changes in a way that breaks existing auditors.
>
> Agree, but I also think by *not* providing backward compatibility it
> simplifies a *lot* the constraints. But I might be wrong with this
> assumption, and there is also whether we want to keep source
> compatibility.
>
>
> After double checking, Florian is right about <link.h>, sticking with la_objsearch2 seems to be the cleaner way to avoid breaking existing builds.
>
> If we go ahead and bump LAV_CURRENT to 3, here is how I'm planning to handle it:
> - Bump generic LAV_CURRENT to 3 and drop the LoongArch-specific header so all arches are in sync again.
> - Switch the AArch64 and LoongArch version checks to a range (lav >= 2 and lav >= 3 respectively) so we don't break existing auditors.
> Something like:
> {
> /* Reject broken v1 auditors, but accept v2 and v3+. */
> return lav >= 2 && lav <= LAV_CURRENT;
> }
> - Keep the ld.so fallback to la_objsearch when la_objsearch2 isn't exported.
> - Fix the xdlinfo check and update the test case to cover the fallback and actual library loads as Adhemerval pointed out.
> - Try to document all of that.
>
> Does that sound like the right way forward to everyone before I send a v2?
Sounds reasonable, thanks Frederic.
On Thu, Sep 17, 2026 at 1:58 AM DJ Delorie <dj@redhat.com> wrote:
> > -/* 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 think what the part you removed is trying to say, is that if the
> shared object you asked for was a symlink (such as libfoo.8.so ->
> libfoo.8.1.so) then the original asked-for name (libfoo.8.so) is
> what's passed to la_objsearch(), not the linked-to-file
> (libfoo.8.1.so) that will be actually opened.
>
> I suspect this info should go in the upcoming documentation instead,
> unless it counts as an implementation detail. It should of course be
> verified first ;-)
>
> This new API should go in the upcoming documentation too, but it's not
> fair to ask for that before either the API or the documentation are in
> place ;-)
>
>
I dug a bit into this, and that's kind of strange, maybe a leftover?
Up until the v6 of the patch that introduced the function, there was an
`origname` parameter [1]:
```
+bool _dl_audit_objsearch (const char **name, const char **origname,
+ struct link_map *l, unsigned int code);
```
Then on v7, the parameter got removed, while the comment was added
referencing the said parameter? [2]
```
+/* Call the la_objsearch from the audit modules from the link map L. If
+ ORIGNAME is non NULL, it is updated with the revious name prior calling
+ la_objsearch. */
+const char *_dl_audit_objsearch (const char *name, struct link_map *l,
+ unsigned int code)
+ attribute_hidden;
+
```
So it looks to me like Adhemerval planned to add the comment but then
removed the parameter and forgot to update the comment?
@Adhemerval Zanella Netto <adhemerval.zanella@linaro.org> Do you remember?
[1]
https://patchwork.sourceware.org/project/glibc/patch/20211115183734.531155-5-adhemerval.zanella@linaro.org/
[2]
https://patchwork.sourceware.org/project/glibc/patch/20211222132712.523295-4-adhemerval.zanella@linaro.org/
On 06/10/26 10:23, Frederic Berat wrote:
>
>
> On Thu, Sep 17, 2026 at 1:58 AM DJ Delorie <dj@redhat.com <mailto:dj@redhat.com>> wrote:
>
> > -/* 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 think what the part you removed is trying to say, is that if the
> shared object you asked for was a symlink (such as libfoo.8.so <http://libfoo.8.so> ->
> libfoo.8.1.so <http://libfoo.8.1.so>) then the original asked-for name (libfoo.8.so <http://libfoo.8.so>) is
> what's passed to la_objsearch(), not the linked-to-file
> (libfoo.8.1.so <http://libfoo.8.1.so>) that will be actually opened.
>
> I suspect this info should go in the upcoming documentation instead,
> unless it counts as an implementation detail. It should of course be
> verified first ;-)
>
> This new API should go in the upcoming documentation too, but it's not
> fair to ask for that before either the API or the documentation are in
> place ;-)
>
>
> I dug a bit into this, and that's kind of strange, maybe a leftover?
> Up until the v6 of the patch that introduced the function, there was an `origname` parameter [1]:
> ```
> +bool _dl_audit_objsearch (const char **name, const char **origname,
> + struct link_map *l, unsigned int code);
> ```
>
> Then on v7, the parameter got removed, while the comment was added referencing the said parameter? [2]
> ```
> +/* Call the la_objsearch from the audit modules from the link map L. If
> + ORIGNAME is non NULL, it is updated with the revious name prior calling
> + la_objsearch. */
> +const char *_dl_audit_objsearch (const char *name, struct link_map *l,
> + unsigned int code)
> + attribute_hidden;
> +
> ```
>
> So it looks to me like Adhemerval planned to add the comment but then removed the parameter and forgot to update the comment?
> @Adhemerval Zanella Netto <mailto:adhemerval.zanella@linaro.org> Do you remember?
>
> [1] https://patchwork.sourceware.org/project/glibc/patch/20211115183734.531155-5-adhemerval.zanella@linaro.org/ <https://patchwork.sourceware.org/project/glibc/patch/20211115183734.531155-5-adhemerval.zanella@linaro.org/>
> [2] https://patchwork.sourceware.org/project/glibc/patch/20211222132712.523295-4-adhemerval.zanella@linaro.org/ <https://patchwork.sourceware.org/project/glibc/patch/20211222132712.523295-4-adhemerval.zanella@linaro.org/>
Hi Frederic,
I must be sincere that I don't really recall why I have changed it on v7,
I will try to re-read the thread and if anything comes to mind I will let
you know.
@@ -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 \
@@ -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;
@@ -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);
}
@@ -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);
@@ -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
new file mode 100644
@@ -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>
new file mode 100644
@@ -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");
+}
@@ -151,6 +151,7 @@ libsupport-routines = \
xconnect \
xcopy_file_range \
xdlfcn \
+ xdlinfo \
xdlmopen \
xdup \
xdup2 \
@@ -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
new file mode 100644
@@ -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 ());
+}
@@ -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