[PATCH v2] elf: Fix handling of symbol versions which hash to zero (bug 29190)

Adhemerval Zanella Netto adhemerval.zanella@linaro.org
Fri Mar 7 16:35:21 GMT 2025



On 07/03/25 06:36, Florian Weimer wrote:
> This was found through code inspection.  No application impact is
> known.

LGTM, thanks.

Reviewed-by: Adhemerval Zanella  <adhemerval.zanella@linaro.org>

> 
> ---
>  elf/Makefile                          | 21 +++++++++++++
>  elf/dl-lookup.c                       | 22 ++++++++++----
>  elf/dl-version.c                      |  7 +++++
>  elf/tst-version-hash-zero-linkmod.c   | 22 ++++++++++++++
>  elf/tst-version-hash-zero-linkmod.map |  7 +++++
>  elf/tst-version-hash-zero-mod.c       | 20 +++++++++++++
>  elf/tst-version-hash-zero-mod.map     | 13 ++++++++
>  elf/tst-version-hash-zero-refmod.c    | 23 ++++++++++++++
>  elf/tst-version-hash-zero.c           | 56 +++++++++++++++++++++++++++++++++++
>  9 files changed, 185 insertions(+), 6 deletions(-)
> 
> diff --git a/elf/Makefile b/elf/Makefile
> index 18b59a8632..67052b5694 100644
> --- a/elf/Makefile
> +++ b/elf/Makefile
> @@ -498,6 +498,7 @@ tests += \
>    tst-unique2 \
>    tst-unwind-ctor \
>    tst-unwind-main \
> +  tst-version-hash-zero \
>    unload3 \
>    unload4 \
>    unload5 \
> @@ -1035,6 +1036,9 @@ modules-names += \
>    tst-unique2mod1 \
>    tst-unique2mod2 \
>    tst-unwind-ctor-lib \
> +  tst-version-hash-zero-linkmod \
> +  tst-version-hash-zero-mod \
> +  tst-version-hash-zero-refmod \
>    unload2dep \
>    unload2mod \
>    unload3mod1 \
> @@ -3412,3 +3416,20 @@ $(objpfx)tst-nolink-libc-2: $(objpfx)tst-nolink-libc.o
>  	  -Wl,--dynamic-linker=$(objpfx)ld.so
>  $(objpfx)tst-nolink-libc-2.out: $(objpfx)tst-nolink-libc-2 $(objpfx)ld.so
>  	$< > $@ 2>&1; $(evaluate-test)
> +
> +$(objpfx)tst-version-hash-zero.out: \
> +  $(objpfx)tst-version-hash-zero-mod.so \
> +  $(objpfx)tst-version-hash-zero-refmod.so
> +LDFLAGS-tst-version-hash-zero-mod.so = \
> +  -Wl,--version-script=tst-version-hash-zero-mod.map
> +# The run-time test module tst-version-hash-zero-refmod.so is linked
> +# to a stub module, tst-version-hash-zero-linkmod.so, to produce an
> +# expected relocation error.
> +$(objpfx)tst-version-hash-zero-refmod.so: \
> +  $(objpfx)tst-version-hash-zero-linkmod.so
> +LDFLAGS-tst-version-hash-zero-linkmod.so = \
> +  -Wl,--version-script=tst-version-hash-zero-linkmod.map \
> +  -Wl,--soname=tst-version-hash-zero-mod.so
> +$(objpfx)tst-version-hash-zero-refmod.so: \
> +  $(objpfx)tst-version-hash-zero-linkmod.so
> +tst-version-hash-zero-refmod.so-no-z-defs = yes
> diff --git a/elf/dl-lookup.c b/elf/dl-lookup.c
> index ece647f009..2f5cd674f5 100644
> --- a/elf/dl-lookup.c
> +++ b/elf/dl-lookup.c
> @@ -100,12 +100,22 @@ check_match (const char *const undef_name,
>  	  /* We can match the version information or use the
>  	     default one if it is not hidden.  */
>  	  ElfW(Half) ndx = verstab[symidx] & 0x7fff;
> -	  if ((map->l_versions[ndx].hash != version->hash
> -	       || strcmp (map->l_versions[ndx].name, version->name))
> -	      && (version->hidden || map->l_versions[ndx].hash
> -		  || (verstab[symidx] & 0x8000)))
> -	    /* It's not the version we want.  */
> -	    return NULL;
> +	  if (map->l_versions[ndx].hash == version->hash
> +	      && strcmp (map->l_versions[ndx].name, version->name) == 0)
> +	    /* This is an exact version match.  Return the symbol below.  */
> +	    ;
> +	  else
> +	    {
> +	      if (!version->hidden
> +		  && map->l_versions[ndx].name[0] == '\0'
> +		  && (verstab[symidx] & 0x8000) == 0
> +		  && (*num_versions)++ == 0)
> +		/* This is the global default version.  Store it as a
> +		   fallback match.  */
> +		*versioned_sym = sym;
> +
> +	      return NULL;
> +	    }
>  	}
>      }
>    else
> diff --git a/elf/dl-version.c b/elf/dl-version.c
> index d414bd1e18..2fbf4942b9 100644
> --- a/elf/dl-version.c
> +++ b/elf/dl-version.c
> @@ -357,6 +357,13 @@ _dl_check_map_versions (struct link_map *map, int verbose, int trace_mode)
>  	      ent = (ElfW(Verdef) *) ((char *) ent + ent->vd_next);
>  	    }
>  	}
> +
> +      /* The empty string has ELF hash zero.  This avoids a NULL check
> +	 before the version string comparison in check_match in
> +	 dl-lookup.c.  */
> +      for (unsigned int i = 0; i < map->l_nversions; ++i)
> +	if (map->l_versions[i].name == NULL)
> +	  map->l_versions[i].name = "";
>      }
>  
>    /* When there is a DT_VERNEED entry with libc.so on DT_NEEDED, issue
> diff --git a/elf/tst-version-hash-zero-linkmod.c b/elf/tst-version-hash-zero-linkmod.c
> new file mode 100644
> index 0000000000..15e2506d01
> --- /dev/null
> +++ b/elf/tst-version-hash-zero-linkmod.c
> @@ -0,0 +1,22 @@
> +/* Stub module for linking tst-version-hash-zero-refmod.so.
> +   Copyright (C) 2025  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; see the file COPYING.LIB.  If
> +   not, see <https://www.gnu.org/licenses/>.  */
> +
> +/* The version script assigns a different symbol version for the stub
> +   module.  Loading the module with the incorrect version is expected
> +   to fail.  */
> +#include "tst-version-hash-zero-mod.c"
> diff --git a/elf/tst-version-hash-zero-linkmod.map b/elf/tst-version-hash-zero-linkmod.map
> new file mode 100644
> index 0000000000..2dba7c22d7
> --- /dev/null
> +++ b/elf/tst-version-hash-zero-linkmod.map
> @@ -0,0 +1,7 @@
> +Base {
> +  local: *;
> +};
> +
> +OTHER_VERSION {
> +  global: global_variable;
> +} Base;
> diff --git a/elf/tst-version-hash-zero-mod.c b/elf/tst-version-hash-zero-mod.c
> new file mode 100644
> index 0000000000..ac6b0dc4a5
> --- /dev/null
> +++ b/elf/tst-version-hash-zero-mod.c
> @@ -0,0 +1,20 @@
> +/* Test module with a zero version symbol hash.
> +   Copyright (C) 2025  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; see the file COPYING.LIB.  If
> +   not, see <https://www.gnu.org/licenses/>.  */
> +
> +/* The symbol version is assigned by version script.  */
> +int global_variable;
> diff --git a/elf/tst-version-hash-zero-mod.map b/elf/tst-version-hash-zero-mod.map
> new file mode 100644
> index 0000000000..41eaff7914
> --- /dev/null
> +++ b/elf/tst-version-hash-zero-mod.map
> @@ -0,0 +1,13 @@
> +Base {
> +  local: *;
> +};
> +
> +/* Define the version so that tst-version-hash-zero-refmod.so passes
> +   the initial symbol version check.  */
> +OTHER_VERSION {
> +} Base;
> +
> +/* This version string hashes to zero.  */
> +PPPPPPPPPPPP {
> +  global: global_variable;
> +} Base;
> diff --git a/elf/tst-version-hash-zero-refmod.c b/elf/tst-version-hash-zero-refmod.c
> new file mode 100644
> index 0000000000..cd8b3dcef5
> --- /dev/null
> +++ b/elf/tst-version-hash-zero-refmod.c
> @@ -0,0 +1,23 @@
> +/* Test module that triggers a relocation failure in tst-version-hash-zero.
> +   Copyright (C) 2025  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; see the file COPYING.LIB.  If
> +   not, see <https://www.gnu.org/licenses/>.  */
> +
> +/* This is bound to global_variable@@OTHER_VERSION via
> +   tst-version-hash-zero-linkmod.so, but at run time, only
> +   global_variable@PPPPPPPPPPPP exists.  */
> +extern int global_variable;
> +int *pointer_variable = &global_variable;
> diff --git a/elf/tst-version-hash-zero.c b/elf/tst-version-hash-zero.c
> new file mode 100644
> index 0000000000..66a0db4f51
> --- /dev/null
> +++ b/elf/tst-version-hash-zero.c
> @@ -0,0 +1,56 @@
> +/* Symbols with version hash zero should not match any version (bug 29190).
> +   Copyright (C) 2025  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; see the file COPYING.LIB.  If
> +   not, see <https://www.gnu.org/licenses/>.  */
> +
> +#include <support/check.h>
> +#include <support/xdlfcn.h>
> +#include <stddef.h>
> +#include <string.h>
> +
> +static int
> +do_test (void)
> +{
> +  void *handle = xdlopen ("tst-version-hash-zero-mod.so", RTLD_NOW);
> +
> +  /* This used to crash because some struct r_found_version entries
> +     with hash zero did not have valid version strings.  */
> +  TEST_VERIFY (xdlvsym (handle, "global_variable", "PPPPPPPPPPPP") != NULL);
> +
> +  /* Consistency check.  */
> +  TEST_VERIFY (xdlsym (handle, "global_variable")
> +               == xdlvsym (handle, "global_variable", "PPPPPPPPPPPP"));
> +
> +  /* This symbol version is supposed to be missing.  */
> +  TEST_VERIFY (dlvsym (handle, "global_variable", "OTHER_VERSION") == NULL);
> +
> +  /* tst-version-hash-zero-refmod.so references
> +     global_variable@@OTHER_VERSION and is expected to fail to load.
> +     dlvsym sets the hidden flag during lookup.  Relocation does not,
> +     so this exercises a different failure case.  */
> +  TEST_VERIFY_EXIT (dlopen ("tst-version-hash-zero-refmod.so", RTLD_NOW)
> +                    == NULL);
> +  const char *message = dlerror ();
> +  if (strstr (message,
> +              ": undefined symbol: global_variable, version OTHER_VERSION")
> +      == NULL)
> +    FAIL_EXIT1 ("unexpected dlopen failure: %s", message);
> +
> +  xdlclose (handle);
> +  return 0;
> +}
> +
> +#include <support/test-driver.c>
> 
> base-commit: 59dc232df277c21239c357e3519682c26e182cd7
> 



More information about the Libc-alpha mailing list