[PATCH v3] elf: Allow vDSO as a direct dependency (BZ 33335)

Adhemerval Zanella Netto adhemerval.zanella@linaro.org
Thu Sep 10 18:15:28 GMT 2026


Ping.

On 06/05/26 14:35, Adhemerval Zanella wrote:
> When a program explicitly links against the vDSO by name (e.g.
> -l:linux-vdso.so.1), the dynamic linker exhibits two problems:
> 
> 1. Symbol resolution always binds to the kernel's implicit vDSO rather
>    than the explicitly provided stub, because _dl_lookup_map returns the
>    already-loaded kernel vDSO when the soname matches.
> 
> 2. Depending on the link order, startup crashes with an assertion:
> 
>      Inconsistency detected by ld.so: rtld.c: dl_main:
>      Assertion `_dl_rtld_map.l_prev->l_next == _dl_rtld_map.l_next'
> 
>    This happens because the kernel vDSO is inserted early in the link
>    map (right after the interpreter), so reusing it as a DT_NEEDED
>    dependency places it at a search-list position inconsistent with its
>    chain position.  When rtld.c later re-inserts the interpreter into
>    the chain at symbol-search order, the adjacency invariant it relies
>    on no longer holds.
> 
> Both problems stem from the same root cause: _dl_lookup_map matching
> the kernel vDSO when a DSO asks for it by name.
> 
> The fix adds lt_vdso as a new value in the l_type enum of struct
> link_map, replacing the former __RTLD_VDSO mode flag.  The kernel-mapped
> vDSO link map is created with type lt_vdso (via setup-vdso.h), and
> _dl_lookup_map skips such objects during name resolution.  With the
> implicit vDSO invisible to name resolution, an explicit dependency
> triggers a fresh filesystem load of the stub at its proper position in
> the chain, and the rtld reinsertion logic sees a consistent adjacency.
> 
> The audit allocation in _dl_new_object previously relied on __RTLD_VDSO
> to allocate DL_NNS audit slots for the vDSO (needed before the audit
> count is known).  This is now handled by checking type == lt_vdso
> directly.
> 
> The _dlfo_process_initial function in dl-find_object.c is updated to
> treat lt_vdso link maps the same as lt_library (implicitly NODELETE),
> preserving dladdr() behavior on vDSO addresses.
> 
> The two new tests check:
> 
> - tst-vdso-1: links against the stub with a normal dependency order
>   (-l:linux-vdso.so.1 before libc).  Verifies that the stub's symbol
>   is resolved instead of the kernel vDSO.
> 
> - tst-vdso-2: uses a special link order (-lc -l:linux-vdso.so.1
>   -l:ld.so) that reproduces the exact BZ 33335 crash without the fix.
> 
> The tests are enabled for all Linux architectures that define
> vdso-name (set by each arch's sysdep Makeconfig).
> 
> Checked on x86_64-linux-gnu, i686-linux-gnu, aarch64-linux-gnu,
> powerpc-linux-gnu, and powerpc64-linux-gnu.
> --
> Changes from v2:
> * Replace __RTLD_VDSO usage by lt_vdso.
> * Fix _dlfo_process_initial to also considere lt_vdso.
> * Move vdso-name to arch-specific Makeconfig.
> Changes from v1:
> * Use a bit from l_type instead of adding a new member at link_map.
> * Move tests to sysdeps/unix/sysv/linux/.
> ---
>  elf/Makefile                                  | 39 +++++++++++++++++++
>  elf/dl-find_object.c                          |  5 ++-
>  elf/dl-load.c                                 |  8 +++-
>  elf/dl-object.c                               |  2 +-
>  elf/setup-vdso.h                              |  4 +-
>  elf/tst-vdso-1.c                              |  1 +
>  elf/tst-vdso-2.c                              |  1 +
>  elf/tst-vdso.c                                | 34 ++++++++++++++++
>  include/dlfcn.h                               |  2 -
>  include/link.h                                |  3 +-
>  sysdeps/unix/sysv/linux/aarch64/Makeconfig    |  1 +
>  sysdeps/unix/sysv/linux/arm/Makeconfig        |  1 +
>  sysdeps/unix/sysv/linux/hppa/Makeconfig       |  1 +
>  sysdeps/unix/sysv/linux/i386/Makeconfig       |  1 +
>  sysdeps/unix/sysv/linux/loongarch/Makeconfig  |  1 +
>  sysdeps/unix/sysv/linux/mips/Makeconfig       |  1 +
>  .../sysv/linux/powerpc/powerpc32/Makeconfig   |  1 +
>  .../sysv/linux/powerpc/powerpc64/Makeconfig   |  1 +
>  sysdeps/unix/sysv/linux/riscv/Makeconfig      |  1 +
>  sysdeps/unix/sysv/linux/s390/Makeconfig       |  1 +
>  .../unix/sysv/linux/sparc/sparc32/Makeconfig  |  1 +
>  .../unix/sysv/linux/sparc/sparc64/Makeconfig  |  1 +
>  sysdeps/unix/sysv/linux/tst-vdso-lib.c        | 24 ++++++++++++
>  sysdeps/unix/sysv/linux/x86_64/Makeconfig     |  1 +
>  24 files changed, 126 insertions(+), 10 deletions(-)
>  create mode 100644 elf/tst-vdso-1.c
>  create mode 100644 elf/tst-vdso-2.c
>  create mode 100644 elf/tst-vdso.c
>  create mode 100644 sysdeps/unix/sysv/linux/aarch64/Makeconfig
>  create mode 100644 sysdeps/unix/sysv/linux/arm/Makeconfig
>  create mode 100644 sysdeps/unix/sysv/linux/hppa/Makeconfig
>  create mode 100644 sysdeps/unix/sysv/linux/i386/Makeconfig
>  create mode 100644 sysdeps/unix/sysv/linux/loongarch/Makeconfig
>  create mode 100644 sysdeps/unix/sysv/linux/mips/Makeconfig
>  create mode 100644 sysdeps/unix/sysv/linux/powerpc/powerpc32/Makeconfig
>  create mode 100644 sysdeps/unix/sysv/linux/powerpc/powerpc64/Makeconfig
>  create mode 100644 sysdeps/unix/sysv/linux/riscv/Makeconfig
>  create mode 100644 sysdeps/unix/sysv/linux/s390/Makeconfig
>  create mode 100644 sysdeps/unix/sysv/linux/sparc/sparc32/Makeconfig
>  create mode 100644 sysdeps/unix/sysv/linux/sparc/sparc64/Makeconfig
>  create mode 100644 sysdeps/unix/sysv/linux/tst-vdso-lib.c
>  create mode 100644 sysdeps/unix/sysv/linux/x86_64/Makeconfig
> 
> diff --git a/elf/Makefile b/elf/Makefile
> index c835eb8156d..2f8d91dd08a 100644
> --- a/elf/Makefile
> +++ b/elf/Makefile
> @@ -1452,6 +1452,14 @@ ifeq ($(run-built-tests),yes)
>  tests-special += $(objpfx)tst-origin.out
>  endif
>  
> +ifneq (,$(vdso-name))
> +tests += tst-vdso-1
> +
> +ifeq ($(run-built-tests),yes)
> +tests-special += $(objpfx)tst-vdso-2.out
> +endif
> +endif
> +
>  include ../Rules
>  
>  ifeq (yes,$(build-shared))
> @@ -3554,3 +3562,34 @@ $(objpfx)tst-dl-debug-exclude.out: tst-dl-debug-exclude.sh \
>  		 $(objpfx)tst-recursive-tls > $@; \
>  	$(evaluate-test)
>  endif
> +
> +ifneq (,$(vdso-name))
> +CFLAGS-tst-vdso-lib.c += $(no-stack-protector)
> +$(objpfx)tst-vdso-lib.os: ../sysdeps/unix/sysv/linux/tst-vdso-lib.c $(before-compile)
> +	$(compile-command.c) -UMODULE_NAME -DMODULE_NAME=testsuite
> +$(objpfx)$(vdso-name).so: $(objpfx)tst-vdso-lib.os
> +	$(LINK.o) -nodefaultlibs  -Wl,--soname,$(vdso-name).so.1 -shared \
> +		  -o $@ -B$(csu-objpfx) $(LDFLAGS.so) $<
> +$(objpfx)$(vdso-name).so.1: $(objpfx)$(vdso-name).so
> +	$(make-link)
> +
> +# Link tst-vdso-1 with $(vdso-name).so, but without a full path.
> +LDFLAGS-tst-vdso-1 += -Wl,-rpath,\$$ORIGIN -L$(subst :, -L,$(rpath-link))
> +LDLIBS-tst-vdso-1 += -l:$(vdso-name).so
> +$(objpfx)tst-vdso-1: +nolink-deps += $(objpfx)$(vdso-name).so
> +$(objpfx)tst-vdso-1: $(objpfx)$(vdso-name).so.1
> +
> +# The tst-vdso-2 requires a special link rule to put the vDSO on the last
> +# tag in the dynamic section (to trigger the circular dependency as
> +# described by BZ 33335)
> +$(objpfx)tst-vdso-2: +nolink-deps += $(objpfx)$(vdso-name).so
> +$(objpfx)tst-vdso-2: $(objpfx)tst-vdso-2.o $(objpfx)$(vdso-name).so.1 $(libsupport)
> +	$(LINK.o) -nodefaultlibs -o $@ $< \
> +	  $(libsupport) $(static-gnulib) $(common-objpfx)libc_nonshared.a \
> +	  $(link-libc-rpath) \
> +	  -Wl,-rpath,\$$ORIGIN -L$(subst :, -L,$(rpath-link)) \
> +	  -lc -l:$(vdso-name).so \
> +	  -Wl,--dynamic-linker=$(objpfx)ld.so,--no-as-needed $(objpfx)ld.so
> +
> +$(objpfx)tst-vdso-2.out: $(objpfx)tst-vdso-2
> +endif
> diff --git a/elf/dl-find_object.c b/elf/dl-find_object.c
> index 28c04b374ea..3493294e0c1 100644
> --- a/elf/dl-find_object.c
> +++ b/elf/dl-find_object.c
> @@ -517,8 +517,9 @@ _dlfo_process_initial (void)
>        /* Skip the main map processed above, and proxy maps.  */
>        if (l != main_map && l == l->l_real)
>          {
> -          /* lt_library link maps are implicitly NODELETE.  */
> -          if (l->l_type == lt_library || l->l_nodelete_active)
> +          /* lt_library and lt_vdso link maps are implicitly NODELETE.  */
> +          if (l->l_type == lt_library || l->l_type == lt_vdso
> +              || l->l_nodelete_active)
>              {
>                /* The kernel may have loaded ld.so with gaps.   */
>                if (!l->l_contiguous && is_rtld_link_map (l))
> diff --git a/elf/dl-load.c b/elf/dl-load.c
> index 48575fff06a..7b5782c8744 100644
> --- a/elf/dl-load.c
> +++ b/elf/dl-load.c
> @@ -1904,8 +1904,12 @@ _dl_lookup_map (Lmid_t nsid, const char *name)
>      {
>        /* If the requested name matches the soname of a loaded object,
>  	 use that object.  Elide this check for names that have not
> -	 yet been opened.  */
> -      if (__glibc_unlikely ((l->l_faked | l->l_removed) != 0))
> +	 yet been opened.
> +
> +	 Also, avoid matching the vDSO; if the DSO requires it, assume it is
> +	 provided by a real object (rather than the implicitly loaded one).  */
> +      if (__glibc_unlikely ((l->l_faked | l->l_removed) != 0
> +			    || l->l_type == lt_vdso))
>  	continue;
>        if (!_dl_name_match_p (name, l))
>  	{
> diff --git a/elf/dl-object.c b/elf/dl-object.c
> index 8153e5ca28e..e10de4ab0dc 100644
> --- a/elf/dl-object.c
> +++ b/elf/dl-object.c
> @@ -59,7 +59,7 @@ _dl_new_object (char *realname, const char *libname, int type,
>  {
>  #ifdef SHARED
>    unsigned int naudit;
> -  if (__glibc_unlikely ((mode & (__RTLD_OPENEXEC | __RTLD_VDSO)) != 0))
> +  if (__glibc_unlikely ((mode & __RTLD_OPENEXEC) != 0 || type == lt_vdso))
>      {
>        if (mode & __RTLD_OPENEXEC)
>  	{
> diff --git a/elf/setup-vdso.h b/elf/setup-vdso.h
> index 0dba7072d53..1ecdc635ab7 100644
> --- a/elf/setup-vdso.h
> +++ b/elf/setup-vdso.h
> @@ -29,8 +29,8 @@ setup_vdso (struct link_map *main_map __attribute__ ((unused)),
>       better be, since it's read-only and so we couldn't relocate it).
>       We just want our data structures to describe it as if we had just
>       mapped and relocated it normally.  */
> -  struct link_map *l = _dl_new_object ((char *) "", "", lt_library, NULL,
> -				       __RTLD_VDSO, LM_ID_BASE);
> +  struct link_map *l = _dl_new_object ((char *) "", "", lt_vdso, NULL,
> +				       0, LM_ID_BASE);
>    bool l_addr_set = false;
>    if (__glibc_likely (l != NULL))
>      {
> diff --git a/elf/tst-vdso-1.c b/elf/tst-vdso-1.c
> new file mode 100644
> index 00000000000..2dd0855ea2b
> --- /dev/null
> +++ b/elf/tst-vdso-1.c
> @@ -0,0 +1 @@
> +#include "tst-vdso.c"
> diff --git a/elf/tst-vdso-2.c b/elf/tst-vdso-2.c
> new file mode 100644
> index 00000000000..2dd0855ea2b
> --- /dev/null
> +++ b/elf/tst-vdso-2.c
> @@ -0,0 +1 @@
> +#include "tst-vdso.c"
> diff --git a/elf/tst-vdso.c b/elf/tst-vdso.c
> new file mode 100644
> index 00000000000..d79c776a9c8
> --- /dev/null
> +++ b/elf/tst-vdso.c
> @@ -0,0 +1,34 @@
> +/* Check for explicit vDSO dependency (BZ 33335)
> +   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 <time.h>
> +#include <support/check.h>
> +
> +extern int __kernel_clock_gettime(clockid_t clk_id, struct timespec *tp);
> +
> +static int
> +do_test (void)
> +{
> +  TEST_COMPARE (__kernel_clock_gettime (CLOCK_REALTIME,
> +					&(struct timespec) { 0 }),
> +		42);
> +
> +  return 0;
> +}
> +
> +#include <support/test-driver.c>
> diff --git a/include/dlfcn.h b/include/dlfcn.h
> index a44420fa374..73131f8d3b4 100644
> --- a/include/dlfcn.h
> +++ b/include/dlfcn.h
> @@ -14,8 +14,6 @@ rtld_hidden_proto (_dl_find_object)
>  #define __RTLD_AUDIT	0x08000000
>  #define __RTLD_SECURE	0x04000000 /* Apply additional security checks.  */
>  #define __RTLD_NOIFUNC	0x02000000 /* Suppress calling ifunc functions.  */
> -#define __RTLD_VDSO	0x01000000 /* Tell _dl_new_object the object is
> -				      system-loaded.  */
>  
>  #define __LM_ID_CALLER	-2
>  
> diff --git a/include/link.h b/include/link.h
> index 8f851d2212d..fe28f3b3ce1 100644
> --- a/include/link.h
> +++ b/include/link.h
> @@ -175,7 +175,8 @@ struct link_map
>        {
>  	lt_executable,		/* The main executable program.  */
>  	lt_library,		/* Library needed by main executable.  */
> -	lt_loaded		/* Extra run-time loaded shared object.  */
> +	lt_loaded,		/* Extra run-time loaded shared object.  */
> +	lt_vdso,		/* The vDSO object provided by the kernel.  */
>        } l_type:2;
>      unsigned int l_dt_relr_ref:1; /* Nonzero if GLIBC_ABI_DT_RELR is
>  				     referenced.  */
> diff --git a/sysdeps/unix/sysv/linux/aarch64/Makeconfig b/sysdeps/unix/sysv/linux/aarch64/Makeconfig
> new file mode 100644
> index 00000000000..3deec2f369f
> --- /dev/null
> +++ b/sysdeps/unix/sysv/linux/aarch64/Makeconfig
> @@ -0,0 +1 @@
> +vdso-name := linux-vdso
> diff --git a/sysdeps/unix/sysv/linux/arm/Makeconfig b/sysdeps/unix/sysv/linux/arm/Makeconfig
> new file mode 100644
> index 00000000000..3deec2f369f
> --- /dev/null
> +++ b/sysdeps/unix/sysv/linux/arm/Makeconfig
> @@ -0,0 +1 @@
> +vdso-name := linux-vdso
> diff --git a/sysdeps/unix/sysv/linux/hppa/Makeconfig b/sysdeps/unix/sysv/linux/hppa/Makeconfig
> new file mode 100644
> index 00000000000..3deec2f369f
> --- /dev/null
> +++ b/sysdeps/unix/sysv/linux/hppa/Makeconfig
> @@ -0,0 +1 @@
> +vdso-name := linux-vdso
> diff --git a/sysdeps/unix/sysv/linux/i386/Makeconfig b/sysdeps/unix/sysv/linux/i386/Makeconfig
> new file mode 100644
> index 00000000000..55ea59b0d85
> --- /dev/null
> +++ b/sysdeps/unix/sysv/linux/i386/Makeconfig
> @@ -0,0 +1 @@
> +vdso-name := linux-gate
> diff --git a/sysdeps/unix/sysv/linux/loongarch/Makeconfig b/sysdeps/unix/sysv/linux/loongarch/Makeconfig
> new file mode 100644
> index 00000000000..3deec2f369f
> --- /dev/null
> +++ b/sysdeps/unix/sysv/linux/loongarch/Makeconfig
> @@ -0,0 +1 @@
> +vdso-name := linux-vdso
> diff --git a/sysdeps/unix/sysv/linux/mips/Makeconfig b/sysdeps/unix/sysv/linux/mips/Makeconfig
> new file mode 100644
> index 00000000000..3deec2f369f
> --- /dev/null
> +++ b/sysdeps/unix/sysv/linux/mips/Makeconfig
> @@ -0,0 +1 @@
> +vdso-name := linux-vdso
> diff --git a/sysdeps/unix/sysv/linux/powerpc/powerpc32/Makeconfig b/sysdeps/unix/sysv/linux/powerpc/powerpc32/Makeconfig
> new file mode 100644
> index 00000000000..e6101fdfbc8
> --- /dev/null
> +++ b/sysdeps/unix/sysv/linux/powerpc/powerpc32/Makeconfig
> @@ -0,0 +1 @@
> +vdso-name := linux-vdso32
> diff --git a/sysdeps/unix/sysv/linux/powerpc/powerpc64/Makeconfig b/sysdeps/unix/sysv/linux/powerpc/powerpc64/Makeconfig
> new file mode 100644
> index 00000000000..0c9649e945f
> --- /dev/null
> +++ b/sysdeps/unix/sysv/linux/powerpc/powerpc64/Makeconfig
> @@ -0,0 +1 @@
> +vdso-name := linux-vdso64
> diff --git a/sysdeps/unix/sysv/linux/riscv/Makeconfig b/sysdeps/unix/sysv/linux/riscv/Makeconfig
> new file mode 100644
> index 00000000000..3deec2f369f
> --- /dev/null
> +++ b/sysdeps/unix/sysv/linux/riscv/Makeconfig
> @@ -0,0 +1 @@
> +vdso-name := linux-vdso
> diff --git a/sysdeps/unix/sysv/linux/s390/Makeconfig b/sysdeps/unix/sysv/linux/s390/Makeconfig
> new file mode 100644
> index 00000000000..0c9649e945f
> --- /dev/null
> +++ b/sysdeps/unix/sysv/linux/s390/Makeconfig
> @@ -0,0 +1 @@
> +vdso-name := linux-vdso64
> diff --git a/sysdeps/unix/sysv/linux/sparc/sparc32/Makeconfig b/sysdeps/unix/sysv/linux/sparc/sparc32/Makeconfig
> new file mode 100644
> index 00000000000..55ea59b0d85
> --- /dev/null
> +++ b/sysdeps/unix/sysv/linux/sparc/sparc32/Makeconfig
> @@ -0,0 +1 @@
> +vdso-name := linux-gate
> diff --git a/sysdeps/unix/sysv/linux/sparc/sparc64/Makeconfig b/sysdeps/unix/sysv/linux/sparc/sparc64/Makeconfig
> new file mode 100644
> index 00000000000..3deec2f369f
> --- /dev/null
> +++ b/sysdeps/unix/sysv/linux/sparc/sparc64/Makeconfig
> @@ -0,0 +1 @@
> +vdso-name := linux-vdso
> diff --git a/sysdeps/unix/sysv/linux/tst-vdso-lib.c b/sysdeps/unix/sysv/linux/tst-vdso-lib.c
> new file mode 100644
> index 00000000000..3426d90723b
> --- /dev/null
> +++ b/sysdeps/unix/sysv/linux/tst-vdso-lib.c
> @@ -0,0 +1,24 @@
> +/* Check for explicit vDSO dependency (BZ 33335)
> +   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 <time.h>
> +
> +int __kernel_clock_gettime (clockid_t clk_id, struct timespec *tp)
> +{
> +  return 42;
> +}
> diff --git a/sysdeps/unix/sysv/linux/x86_64/Makeconfig b/sysdeps/unix/sysv/linux/x86_64/Makeconfig
> new file mode 100644
> index 00000000000..3deec2f369f
> --- /dev/null
> +++ b/sysdeps/unix/sysv/linux/x86_64/Makeconfig
> @@ -0,0 +1 @@
> +vdso-name := linux-vdso



More information about the Libc-alpha mailing list