[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