@@ -559,6 +559,7 @@ endif
tests-container += \
tst-dlopen-self-container \
tst-dlopen-tlsmodid-container \
+ tst-ldconfig-cache \
tst-pldd \
tst-preload-pthread-libc \
tst-rootdir \
@@ -714,6 +715,8 @@ one-hundred = $(foreach x,0 1 2 3 4 5 6 7 8 9, \
0$x 1$x 2$x 3$x 4$x 5$x 6$x 7$x 8$x 9$x)
tst-tls-many-dynamic-modules := \
$(foreach n,$(one-hundred),tst-tls-manydynamic$(n)mod)
+tst-ldconfig-cache-modules := \
+ $(foreach n,1 2 3 4 5,tst-tls-manydynamic$(n)mod)
tst-tls-many-dynamic-modules-dep-suffixes = 0 1 2 3 4 5 6 7 8 9 10 11 12 13 \
14 15 16 17 18 19
tst-tls-many-dynamic-modules-dep = \
@@ -25,11 +25,21 @@
#include <_itoa.h>
#include <dl-hwcaps.h>
#include <dl-isa-level.h>
+#include <fcntl.h>
+#include <sys/stat.h>
/* This is the starting address and the size of the mmap()ed file. */
static struct cache_file *cache;
static struct cache_file_new *cache_new;
static size_t cachesize;
+static struct cache_extension_all_loaded ext;
+
+static struct {
+ typeof ((*(struct __stat64_t64 *)0).st_mtime) mtime;
+ typeof ((*(struct __stat64_t64 *)0).st_ino) ino;
+ typeof ((*(struct __stat64_t64 *)0).st_size) size;
+ typeof ((*(struct __stat64_t64 *)0).st_dev) dev;
+} cache_file_time, new_cache_file_time;
#ifdef SHARED
/* This is used to cache the priorities of glibc-hwcaps
@@ -52,6 +62,7 @@ glibc_hwcaps_priorities_free (void)
free (glibc_hwcaps_priorities);
glibc_hwcaps_priorities = NULL;
glibc_hwcaps_priorities_allocated = 0;
+ glibc_hwcaps_priorities_length = 0;
}
/* Ordered comparison of a hwcaps string from the cache on the left
@@ -83,10 +94,6 @@ glibc_hwcaps_compare (uint32_t left_index, struct dl_hwcaps_priority *right)
static void
glibc_hwcaps_priorities_init (void)
{
- struct cache_extension_all_loaded ext;
- if (!cache_extension_load (cache_new, cache, cachesize, &ext))
- return;
-
uint32_t length = (ext.sections[cache_extension_tag_glibc_hwcaps].size
/ sizeof (uint32_t));
if (length > glibc_hwcaps_priorities_allocated)
@@ -373,6 +380,162 @@ _dl_cache_libcmp (const char *p1, const char *p2)
return *p1 - *p2;
}
+/* Set the cache back to the "no cache" state, which may include
+ cleaning up a loaded cache. */
+static void
+_dl_maybe_unload_ldsocache (void)
+{
+ if (cache != NULL)
+ __munmap (cache, cachesize);
+
+ cache = NULL;
+ cache_new = NULL;
+ cachesize = 0;
+
+#ifdef SHARED
+ glibc_hwcaps_priorities_free ();
+#endif
+}
+
+/* Returns TRUE if for any reason the cache needs to be reloaded
+ (including, the first time, loaded). */
+static bool
+_dl_check_ldsocache_needs_loading (void)
+{
+ int rv;
+ static bool copy_old_time = 0;
+ struct __stat64_t64 new_cache_file_stat;
+
+ /* Save the previous stat every time. We only care when this
+ changes, and we only stat it here, so we can get away with doing
+ the copy now instead of at every single return statement in this
+ function. However, we only need to copy it if the previous stat
+ succeeded. The only way this could be subverted is if the admin
+ moves the file aside, then moves it back, but CACHE would be set
+ to NULL in the interim so that would be detected. */
+ if (copy_old_time)
+ cache_file_time = new_cache_file_time;
+ rv = __fstatat64_time64 (AT_FDCWD, LD_SO_CACHE, &new_cache_file_stat, 0);
+ copy_old_time = (rv >= 0);
+
+ /* No file to load, but there used to be. Assume user intentionally
+ deleted the cache and act accordingly. */
+ if (rv < 0 && cache != NULL)
+ {
+ _dl_maybe_unload_ldsocache ();
+ return false;
+ }
+
+ /* No file to load and no loaded cache, so nothing to do. */
+ if (rv < 0)
+ return false;
+
+ /* Any file is better than no file (likely the first time
+ through). */
+ if (cache == NULL)
+ return true;
+
+ /* Store the fields we check, in order they're likely to differ. */
+ new_cache_file_time.mtime = new_cache_file_stat.st_mtime;
+ new_cache_file_time.ino = new_cache_file_stat.st_ino;
+ new_cache_file_time.size = new_cache_file_stat.st_size;
+ new_cache_file_time.dev = new_cache_file_stat.st_dev;
+
+ /* At this point, NEW_CACHE_FILE_TIME is valid as well as
+ CACHE_FILE_TIME, so we compare them. */
+ return (memcmp (&new_cache_file_time, &cache_file_time,
+ sizeof(new_cache_file_time)));
+}
+
+/* Attemps to load and validate the cache. On return, CACHE is either
+ unchanged (still loaded or still not loaded) or valid. */
+static void
+_dl_maybe_load_ldsocache (void)
+{
+ struct cache_file *tmp_cache = NULL;
+ struct cache_file_new *tmp_cache_new = NULL;
+ size_t tmp_cachesize = 0;
+
+ /* Read the contents of the file. */
+ void *file = _dl_sysdep_read_whole_file (LD_SO_CACHE, &tmp_cachesize,
+ PROT_READ);
+
+ /* We can handle three different cache file formats here:
+ - only the new format
+ - the old libc5/glibc2.0/2.1 format
+ - the old format with the new format in it
+ The following checks if the cache contains any of these formats. */
+ if (file != MAP_FAILED && tmp_cachesize > sizeof *cache_new
+ && memcmp (file, CACHEMAGIC_VERSION_NEW,
+ sizeof CACHEMAGIC_VERSION_NEW - 1) == 0
+ /* Check for corruption, avoiding overflow. */
+ && ((tmp_cachesize - sizeof *cache_new) / sizeof (struct file_entry_new)
+ >= ((struct cache_file_new *) file)->nlibs))
+ {
+ if (! cache_file_new_matches_endian (file))
+ {
+ __munmap (file, tmp_cachesize);
+ return;
+ }
+
+ tmp_cache_new = file;
+ tmp_cache = file;
+ }
+ else if (file != MAP_FAILED && cachesize > sizeof *cache
+ && memcmp (file, CACHEMAGIC, sizeof CACHEMAGIC - 1) == 0
+ /* Check for corruption, avoiding overflow. */
+ && ((tmp_cachesize - sizeof *cache) / sizeof (struct file_entry)
+ >= ((struct cache_file *) file)->nlibs))
+ {
+ size_t offset;
+ /* Looks ok. */
+ tmp_cache = file;
+
+ /* Check for new version. */
+ offset = ALIGN_CACHE (sizeof (struct cache_file)
+ + cache->nlibs * sizeof (struct file_entry));
+
+ tmp_cache_new = (struct cache_file_new *) ((void *) tmp_cache + offset);
+ if (tmp_cachesize < (offset + sizeof (struct cache_file_new))
+ || memcmp (tmp_cache_new->magic, CACHEMAGIC_VERSION_NEW,
+ sizeof CACHEMAGIC_VERSION_NEW - 1) != 0)
+ tmp_cache_new = NULL;
+ else
+ {
+ if (! cache_file_new_matches_endian (tmp_cache_new))
+ /* The old-format part of the cache is bogus as well
+ if the endianness does not match. (But it is
+ unclear how the new header can be located if the
+ endianness does not match.) */
+ {
+ __munmap (file, tmp_cachesize);
+ return;
+ }
+ }
+ }
+ else
+ {
+ if (file != MAP_FAILED)
+ __munmap (file, tmp_cachesize);
+ return;
+ }
+
+ struct cache_extension_all_loaded tmp_ext;
+ if (!cache_extension_load (tmp_cache_new, tmp_cache, tmp_cachesize, &tmp_ext))
+ /* The extension is corrupt, so the cache is corrupt. */
+ return;
+
+ /* If we've gotten here, the loaded cache is good and we need to
+ save it. */
+ _dl_maybe_unload_ldsocache ();
+ cache = tmp_cache;
+ cache_new = tmp_cache_new;
+ cachesize = tmp_cachesize;
+ ext = tmp_ext;
+
+ assert (cache != NULL);
+}
+
/* Look up NAME in ld.so.cache and return the file name stored there, or null
if none is found. The cache is loaded if it was not already. If loading
@@ -388,81 +551,14 @@ _dl_load_cache_lookup (const char *name)
if (__glibc_unlikely (GLRO(dl_debug_mask) & DL_DEBUG_LIBS))
_dl_debug_printf (" search cache=%s\n", LD_SO_CACHE);
- if (cache == NULL)
- {
- /* Read the contents of the file. */
- void *file = _dl_sysdep_read_whole_file (LD_SO_CACHE, &cachesize,
- PROT_READ);
-
- /* We can handle three different cache file formats here:
- - only the new format
- - the old libc5/glibc2.0/2.1 format
- - the old format with the new format in it
- The following checks if the cache contains any of these formats. */
- if (file != MAP_FAILED && cachesize > sizeof *cache_new
- && memcmp (file, CACHEMAGIC_VERSION_NEW,
- sizeof CACHEMAGIC_VERSION_NEW - 1) == 0
- /* Check for corruption, avoiding overflow. */
- && ((cachesize - sizeof *cache_new) / sizeof (struct file_entry_new)
- >= ((struct cache_file_new *) file)->nlibs))
- {
- if (! cache_file_new_matches_endian (file))
- {
- __munmap (file, cachesize);
- file = (void *) -1;
- }
- cache_new = file;
- cache = file;
- }
- else if (file != MAP_FAILED && cachesize > sizeof *cache
- && memcmp (file, CACHEMAGIC, sizeof CACHEMAGIC - 1) == 0
- /* Check for corruption, avoiding overflow. */
- && ((cachesize - sizeof *cache) / sizeof (struct file_entry)
- >= ((struct cache_file *) file)->nlibs))
- {
- size_t offset;
- /* Looks ok. */
- cache = file;
-
- /* Check for new version. */
- offset = ALIGN_CACHE (sizeof (struct cache_file)
- + cache->nlibs * sizeof (struct file_entry));
-
- cache_new = (struct cache_file_new *) ((void *) cache + offset);
- if (cachesize < (offset + sizeof (struct cache_file_new))
- || memcmp (cache_new->magic, CACHEMAGIC_VERSION_NEW,
- sizeof CACHEMAGIC_VERSION_NEW - 1) != 0)
- cache_new = (void *) -1;
- else
- {
- if (! cache_file_new_matches_endian (cache_new))
- {
- /* The old-format part of the cache is bogus as well
- if the endianness does not match. (But it is
- unclear how the new header can be located if the
- endianness does not match.) */
- cache = (void *) -1;
- cache_new = (void *) -1;
- __munmap (file, cachesize);
- }
- }
- }
- else
- {
- if (file != MAP_FAILED)
- __munmap (file, cachesize);
- cache = (void *) -1;
- }
+ if (_dl_check_ldsocache_needs_loading ())
+ _dl_maybe_load_ldsocache ();
- assert (cache != NULL);
- }
-
- if (cache == (void *) -1)
- /* Previously looked for the cache file and didn't find it. */
+ if (cache == NULL)
return NULL;
const char *best;
- if (cache_new != (void *) -1)
+ if (cache_new != NULL)
{
const char *string_table = (const char *) cache_new;
best = search_cache (string_table, cachesize,
@@ -488,7 +584,7 @@ _dl_load_cache_lookup (const char *name)
return NULL;
/* The double copy is *required* since malloc may be interposed
- and call dlopen itself whose completion would unmap the data
+ and call dlopen itself whose completion may unmap the data
we are accessing. Therefore we must make the copy of the
mapping data without using malloc. */
char *temp;
@@ -506,14 +602,7 @@ _dl_load_cache_lookup (const char *name)
void
_dl_unload_cache (void)
{
- if (cache != NULL && cache != (struct cache_file *) -1)
- {
- __munmap (cache, cachesize);
- cache = NULL;
- }
-#ifdef SHARED
- /* This marks the glibc_hwcaps_priorities array as out-of-date. */
- glibc_hwcaps_priorities_length = 0;
-#endif
+ /* Functionality is no longer needed, but kept for internal ABI for
+ now. */
}
#endif
new file mode 100644
@@ -0,0 +1,134 @@
+/* Test ldconfig cache is correctly used when changed.
+ 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; see the file COPYING.LIB. If
+ not, see <https://www.gnu.org/licenses/>. */
+
+/* What we're testing for: We initially load ld.so.cache at startup
+ and remember it. If we detect that ld.so.cache has changed, and we
+ can load it successfully, we replace our remember it. If it
+ doesn't change, or if the new version is corrupted, we continue
+ using the old remembered copy. */
+
+#include <fcntl.h>
+
+#include <support/support.h>
+#include <support/check.h>
+
+#include <support/xstdio.h>
+#include <support/xstdlib.h>
+#include <support/xdlfcn.h>
+#include <support/xunistd.h>
+
+
+/* Verify that we can (or can't) load one of our test objects. */
+static void
+try (int i, int invert)
+{
+ char dlname[100];
+ char symname[100];
+ int (*proc)(int);
+ void *dl;
+
+ /* These match the objects copied by tst-ldconfig-cache.script,
+ copied from tst-tls-manydynamic*.so. */
+ sprintf (dlname, "libcache%d.so", i);
+ sprintf (symname, "set_value_%02d", i);
+
+ dl = dlopen (dlname, RTLD_NOW);
+
+ if (invert)
+ {
+ /* This is a negative test; if the object doesn't load the test
+ passes. */
+ TEST_VERIFY (dl == NULL);
+ return;
+ }
+ else
+ {
+ /* This is a positive test; if the object doesn't load the test
+ fails. */
+ if (dl == NULL)
+ FAIL_EXIT1 ("error: dlopen: %s\n", dlerror ());
+ }
+
+ proc = xdlsym (dl, symname);
+ /* We don't need to call the symbol, just make sure it exists. */
+ TEST_VERIFY (proc != NULL);
+
+ xdlclose (dl);
+}
+
+/* Cause corruption in the cache that should prevent loading it. */
+static void
+corrupt (void)
+{
+ int fd = xopen ("/etc/ld.so.cache", O_RDWR, 0);
+ char bytes[] = { 15, 32, 184, 4 };
+ xwrite (fd, bytes, sizeof(bytes));
+ xclose (fd);
+}
+
+/* Regenerate the cache from ld.so.conf. */
+static void
+ldconfig (void)
+{
+ xsystem ("/sbin/ldconfig -X");
+}
+
+/* Change ld.so.conf to refer to the new directory, and generate a new
+ cache. */
+static void
+newpath (const char *p)
+{
+ FILE *f = xfopen ("/etc/ld.so.conf", "w");
+ fprintf (f, "%s\n", p);
+ xfclose (f);
+
+ ldconfig ();
+}
+
+static int
+do_test (void)
+{
+ /* Test that the cache we started with can still load objects in
+ /a. */
+ try (1, 0);
+
+ /* Create a new cache that doesn't include /a but corrupt it. Test
+ that we still use the cache with /a in it. */
+ newpath ("/c");
+ corrupt ();
+ try (2, 0);
+
+ /* Regenerate a clean cache with /a in it and verify we can load
+ objects in /a. */
+ newpath ("/a");
+ try (3, 0);
+
+ /* Generate a new cache with /b but not /a and make sure objects
+ in /a can't be loaded. */
+ newpath ("/b");
+ try (3, 1);
+
+ /* But objects in /b can be loaded. */
+ try (4, 0);
+ /* Even multiple times. */
+ try (5, 0);
+
+ return 0;
+}
+
+#include <support/test-driver.c>
new file mode 100644
@@ -0,0 +1,3 @@
+/lib
+/lib64
+/a
new file mode 100644
new file mode 100644
@@ -0,0 +1,7 @@
+mkdirp 0755 /a
+cp $B/elf/tst-tls-manydynamic01mod.so /a/libcache1.so
+cp $B/elf/tst-tls-manydynamic02mod.so /a/libcache2.so
+cp $B/elf/tst-tls-manydynamic03mod.so /a/libcache3.so
+mkdirp 0755 /b
+cp $B/elf/tst-tls-manydynamic04mod.so /b/libcache4.so
+cp $B/elf/tst-tls-manydynamic05mod.so /b/libcache5.so