[PATCH] elf: Add la_objsearch2 with lmid parameter for rtld-audit [BZ #34093]
Adhemerval Zanella Netto
adhemerval.zanella@linaro.org
Tue Sep 22 12:52:06 GMT 2026
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.
More information about the Libc-alpha
mailing list