[PATCH] elf: Do not load cache extensions from an old-format ld.so.cache [BZ #34600]
Shamil Abdulaev
ashamil435@gmail.com
Mon Sep 7 10:52:45 GMT 2026
Since b9957a70b8 the cache extensions are loaded in
_dl_maybe_load_ldsocache. A cache written by "ldconfig -c old" has no
new-format header, so tmp_cache_new is NULL there and
cache_extension_load dereferences it. Because __tunables_init loads the
cache unconditionally, ld.so crashes at startup of every dynamically
linked program. An old-format cache cannot carry extensions, so return
early instead.
Checked on x86_64-linux-gnu.
Signed-off-by: Shamil Abdulaev <ashamil435@gmail.com>
---
elf/Makefile | 4 +-
elf/tst-dl-cache-old-format.c | 144 ++++++++++++++++++
.../postclean.req | 0
sysdeps/generic/dl-cache.h | 3 +
4 files changed, 150 insertions(+), 1 deletion(-)
create mode 100644 elf/tst-dl-cache-old-format.c
create mode 100644 elf/tst-dl-cache-old-format.root/postclean.req
diff --git a/elf/Makefile b/elf/Makefile
index 4d1032bb2a..b0f594f5b3 100644
--- a/elf/Makefile
+++ b/elf/Makefile
@@ -308,6 +308,7 @@ tst-ifunc-resolver-protector-static-non-pie-no-pie = yes
tests-container := \
tst-dl-cache-long-path \
+ tst-dl-cache-old-format \
tst-ldconfig-bad-aux-cache \
tst-ldconfig-ld_so_conf-update \
# tests-container
@@ -3065,9 +3066,10 @@ LDFLAGS-tst-dlopen-nodelete-reloc-mod17.so = -Wl,--no-as-needed
$(objpfx)tst-ldconfig-ld_so_conf-update.out: $(objpfx)tst-ldconfig-ld-mod.so
-# Reuses the trivial module already built for tst-dl-path-buf.
$(objpfx)tst-dl-cache-long-path: $(shared-thread-library)
+# Both cache tests reuse the trivial module already built for tst-dl-path-buf.
$(objpfx)tst-dl-cache-long-path.out: $(objpfx)tst-dl-path-buf-mod.so
+$(objpfx)tst-dl-cache-old-format.out: $(objpfx)tst-dl-path-buf-mod.so
LDFLAGS-tst-dst-needed-leaf-mod.so = \
-Wl,-soname,\$$ORIGIN/\$$ORIGIN/\$$ORIGIN/\$$ORIGIN/\$$ORIGIN/leaf.so
diff --git a/elf/tst-dl-cache-old-format.c b/elf/tst-dl-cache-old-format.c
new file mode 100644
index 0000000000..985aed99ec
--- /dev/null
+++ b/elf/tst-dl-cache-old-format.c
@@ -0,0 +1,144 @@
+/* Test that ld.so accepts an ld.so.cache written in the old format.
+ 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/>. */
+
+/* A cache written by "ldconfig -c old" carries no new-format header, so
+ the loader must not look for cache extensions in it. The test writes
+ such a cache, then starts a dynamically linked child, which is where
+ an unpatched loader dies before main, and then dlopens a module that
+ is only reachable through the cache, which covers the reload path. */
+
+#include <dlfcn.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+
+#include <support/capture_subprocess.h>
+#include <support/check.h>
+#include <support/support.h>
+#include <support/temp_file.h>
+#include <support/xdlfcn.h>
+#include <support/xunistd.h>
+
+/* ldconfig only indexes file names starting with "lib", so the module is
+ deployed under a lib-prefixed name and dlopened by that name. */
+#define MOD_BUILT "tst-dl-path-buf-mod.so"
+#define MOD_DEPLOYED "libtst-dl-path-buf-mod.so"
+#define MOD_SYMBOL "tst_dl_path_buf_mod_value"
+#define MOD_EXPECTED 0xaabbccddu
+
+#define MOD_DIR "/tst-dl-cache-old-format"
+
+/* Copied from sysdeps/generic/dl-cache.h, which cannot be included here
+ because it relies on libc-internal attributes. */
+#define CACHE_MAGIC_OLD "ld.so-1.7.0"
+#define CACHE_MAGIC_NEW "glibc-ld.so.cache1.1"
+
+/* Set in the re-executed child, whose only job is to reach main. */
+static int restart;
+#define CMDLINE_OPTIONS \
+ { "restart", no_argument, &restart, 1 },
+
+static void
+run_ldconfig (void *closure __attribute__ ((unused)))
+{
+ char *prog = xasprintf ("%s/ldconfig", support_install_rootsbindir);
+ char *args[] = { prog, (char *) "-c", (char *) "old", NULL };
+ execv (args[0], args);
+ FAIL_EXIT1 ("execv (%s): %m", prog);
+}
+
+/* ldconfig accepts an unknown -c argument without complaining and falls
+ back to the new format, so verify that the cache on disk really is the
+ old format and carries no new-format header anywhere. */
+static void
+check_cache_is_old_format (void)
+{
+ char *path = xasprintf ("%s/ld.so.cache", support_sysconfdir_prefix);
+ int fd = xopen (path, O_RDONLY, 0);
+ struct stat64 st;
+ xfstat64 (fd, &st);
+ size_t length = st.st_size;
+ char *contents = xmalloc (length);
+ xread (fd, contents, length);
+ xclose (fd);
+
+ TEST_COMPARE_BLOB (contents, sizeof CACHE_MAGIC_OLD - 1,
+ CACHE_MAGIC_OLD, sizeof CACHE_MAGIC_OLD - 1);
+ TEST_VERIFY (memmem (contents, length, CACHE_MAGIC_NEW,
+ sizeof CACHE_MAGIC_NEW - 1) == NULL);
+
+ free (contents);
+ free (path);
+}
+
+static void
+do_prepare (int argc, char **argv)
+{
+ if (restart)
+ return;
+
+ xmkdirp (MOD_DIR, 0777);
+ add_temp_file (MOD_DIR);
+ char *src = xasprintf ("%s/elf/" MOD_BUILT, support_objdir_root);
+ support_copy_file (src, MOD_DIR "/" MOD_DEPLOYED);
+ add_temp_file (MOD_DIR "/" MOD_DEPLOYED);
+ free (src);
+
+ char *conf = xasprintf ("%s/ld.so.conf", support_sysconfdir_prefix);
+ support_write_file_string (conf, MOD_DIR "\n");
+ free (conf);
+
+ xmkdirp ("/var/cache/ldconfig", 0777);
+ struct support_capture_subprocess result
+ = support_capture_subprocess (run_ldconfig, NULL);
+ support_capture_subprocess_check (&result, "ldconfig", 0, sc_allow_none);
+ support_capture_subprocess_free (&result);
+
+ check_cache_is_old_format ();
+}
+#define PREPARE do_prepare
+
+static int
+do_test (int argc, char **argv)
+{
+ if (restart)
+ /* Reaching main is the entire check: the loader read the old-format
+ cache while starting this process. */
+ return 0;
+
+ char *spargv[] = { argv[0], (char *) "--direct", (char *) "--restart",
+ NULL };
+ struct support_capture_subprocess result
+ = support_capture_subprogram (spargv[0], spargv, NULL);
+ support_capture_subprocess_check (&result, "restart", 0, sc_allow_none);
+ support_capture_subprocess_free (&result);
+
+ /* This process still has the cache from its own startup mapped, so the
+ dlopen exercises the reload of the replaced file as well. */
+ void *handle = xdlopen (MOD_DEPLOYED, RTLD_NOW | RTLD_LOCAL);
+ unsigned int (*value) (void) = xdlsym (handle, MOD_SYMBOL);
+ TEST_COMPARE (value (), MOD_EXPECTED);
+ xdlclose (handle);
+
+ return 0;
+}
+
+#define TEST_FUNCTION_ARGV do_test
+#include <support/test-driver.c>
diff --git a/elf/tst-dl-cache-old-format.root/postclean.req b/elf/tst-dl-cache-old-format.root/postclean.req
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/sysdeps/generic/dl-cache.h b/sysdeps/generic/dl-cache.h
index 972ab32b86..b1de245438 100644
--- a/sysdeps/generic/dl-cache.h
+++ b/sysdeps/generic/dl-cache.h
@@ -321,6 +321,9 @@ cache_extension_load (const struct cache_file_new *cache,
struct cache_extension_all_loaded *loaded)
{
memset (loaded, 0, sizeof (*loaded));
+ if (cache == NULL)
+ /* Old-format cache without a new-format header, so no extensions. */
+ return true;
if (cache->extension_offset == 0)
/* No extensions present. This is not a format error. */
return true;
--
2.55.0
More information about the Libc-alpha
mailing list