Re: [PATCH v7 1/1] ldconfig: add --install option
Adhemerval Zanella Netto <[email protected]>
| Newsgroups | gmane.comp.lib.glibc.alpha |
|---|---|
| Organization | Linaro |
| Message-ID | <[email protected]> |
On 30/06/26 18:28, DJ Delorie wrote: > > Add --install option, which copies a pre-built ld.so.cache into place, > honoring the cache and root options and defaults. This gives the user > a canonical "correct" way to install a pre-built cache without risk > of a program trying to load a partially-written file. > > Co-authored-by: Adhemerval Zanella Netto <[email protected]> This version looks good to me, thanks. Reviewed-by: Adhemerval Zanella <[email protected]> > > --- >8 --- > > Changes since v6: > - split install_cache_file into separate function > - preserve errno across unlink/close > - don't use errno from > > Changes since v5: > - Fixed style issues > - Make counters ssize_t > - Fix write count accumulation logic > - additional error checks > > Changes since v4: > - Added testcase > - Fix open mode > > Changes since v3: > - initialize w because some compilers can't tell initialization > isn't needed. > > Changes since v2: > - removed "rename if possible" option to avoid removing source file. > We always copy now. > --- > NEWS | 2 + > elf/Makefile | 6 ++ > elf/ldconfig.c | 162 +++++++++++++++++++++++++++++++++++- > elf/tst-ldconfig-install.sh | 125 ++++++++++++++++++++++++++++ > 4 files changed, 293 insertions(+), 2 deletions(-) > create mode 100644 elf/tst-ldconfig-install.sh > > diff --git a/NEWS b/NEWS > index f9d90c5194..e30b7a5f76 100644 > --- a/NEWS > +++ b/NEWS > @@ -37,6 +37,8 @@ Major new features: > status via prctl syscall. This prevents disabling or corrupting GCS > shadow stack during runtime. > > +* Pre-built ld.so.cache files can be installed with ldconfig. > + > Deprecated and removed features, and other changes affecting compatibility: > > * Although malloc and related functions currently return pointers > diff --git a/elf/Makefile b/elf/Makefile > index 789c504da9..3479450191 100644 > --- a/elf/Makefile > +++ b/elf/Makefile > @@ -718,6 +718,7 @@ tests-special += $(tests-execstack-special-$(have-z-execstack)) > ifeq ($(run-built-tests),yes) > tests-special += \ > $(objpfx)tst-ldconfig-X.out \ > + $(objpfx)tst-ldconfig-install.out \ > $(objpfx)tst-ldconfig-p.out \ > $(objpfx)tst-ldconfig-soname.out \ > $(objpfx)tst-rtld-help.out \ > @@ -2833,6 +2834,11 @@ $(objpfx)tst-ldconfig-X.out : tst-ldconfig-X.sh $(objpfx)ldconfig > '$(run-program-env)' > $@; \ > $(evaluate-test) > > +$(objpfx)tst-ldconfig-install.out : tst-ldconfig-install.sh $(objpfx)ldconfig > + $(SHELL) $< '$(common-objpfx)' '$(test-wrapper-env)' \ > + '$(run-program-env)' > $@; \ > + $(evaluate-test) > + > $(objpfx)tst-ldconfig-p.out : tst-ldconfig-p.sh $(objpfx)ldconfig > $(SHELL) $< '$(common-objpfx)' '$(sysconfdir)' '$(test-wrapper-env)' \ > '$(run-program-env)' > $@; \ > diff --git a/elf/ldconfig.c b/elf/ldconfig.c > index 1ea55400f3..a39f3ecfcd 100644 > --- a/elf/ldconfig.c > +++ b/elf/ldconfig.c > @@ -109,6 +109,9 @@ static int opt_manual_link; > /* Should we ignore an old auxiliary cache file? */ > static int opt_ignore_aux_cache; > > +/* Install a pre-existing cache file instead of generating a new one. */ > +static int opt_install; > + > /* Cache file to use. */ > static char *cache_file; > > @@ -141,6 +144,7 @@ static const struct argp_option options[] = > { NULL, 'l', NULL, 0, N_("Manually link individual libraries."), 0}, > { "format", 'c', N_("FORMAT"), 0, N_("Format to use: new (default), old, or compat"), 0}, > { "ignore-aux-cache", 'i', NULL, 0, N_("Ignore auxiliary cache file"), 0}, > + { "install", 'I', NULL, 0, N_("Install pre-existing cache file"), 0}, > { NULL, 0, NULL, 0, NULL, 0 } > }; > > @@ -209,6 +213,9 @@ parse_opt (int key, char *arg, struct argp_state *state) > else if (strcmp (arg, "new") == 0) > opt_format = opt_format_new; > break; > + case 'I': > + opt_install = 1; > + break; > default: > return ARGP_ERR_UNKNOWN; > } > @@ -1041,6 +1048,155 @@ search_dirs (void) > } > } > > +static void > +install_cache_file (const char *source_arg) > +{ > + int e; > + > + if (source_arg == NULL) > + error (EXIT_FAILURE, 0, _("Missing source file name")); > + > + const char *source = (opt_chroot > + ? chroot_canon (opt_chroot, source_arg) > + : source_arg); > + if (source == NULL) > + error (EXIT_FAILURE, errno, _("Can't find %s"), source_arg); > + > + int src_fd = open (source, O_RDONLY); > + if (src_fd < 0) > + error (EXIT_FAILURE, errno, _("Can't open %s"), source); > + > + char *dest = xmalloc (strlen (cache_file) + 1 + 1); > + > + /* This matches the temp file created by cache.c, and should be > + on the same filesystem as the cache file. */ > + sprintf (dest, "%s~", cache_file); > + int dest_fd; > + > + struct stat st; > + if (fstat (src_fd, &st) < 0) > + error (EXIT_FAILURE, errno, _("Can't stat %s"), source); > + > + char buf[512]; > + ssize_t r, w = 0, sz = 0; > + char *bp = buf; > + > + /* Read the first part of the file and verify it looks > + reasonable. */ > + while (sz < sizeof (buf) > + && (r = read (src_fd, bp, sizeof (buf) - sz)) > 0) > + { > + sz += r; > + bp += r; > + } > + if (r < 0) > + error (EXIT_FAILURE, errno, _("Error reading file %s"), source); > + > + if (! ((sz >= sizeof (CACHEMAGIC) > + && memcmp (buf, CACHEMAGIC, > + sizeof (CACHEMAGIC) - 1) == 0) > + || (sz >= sizeof (CACHEMAGIC_NEW) > + && memcmp (buf, CACHEMAGIC_NEW, > + sizeof (CACHEMAGIC_NEW) - 1) == 0))) > + { > + error (EXIT_FAILURE, 0, > + _("File %s does not look like an ld.so.cache file"), > + source); > + } > + > + /* Now write that first part out. */ > + dest_fd = open (dest, O_CREAT|O_WRONLY|O_TRUNC|O_NOFOLLOW, > + S_IRUSR|S_IWUSR); > + if (dest_fd < 0) > + error (EXIT_FAILURE, errno, _("Can't create %s"), dest); > + > + r = sz; > + bp = buf; > + while (r > 0 && (w = write (dest_fd, bp, r)) > 0) > + { > + r -= w; > + bp += w; > + } > + if (w < 0) > + { > + e = errno; > + unlink (dest); > + close (dest_fd); > + error (EXIT_FAILURE, e, _("Error writing file %s"), dest); > + } > + > + /* At this point, sz contains the number of bytes copied so far. > + Copy the rest of the file. */ > + while ((r = read (src_fd, buf, sizeof(buf))) > 0) > + { > + bp = buf; > + while (r > 0 && (w = write (dest_fd, bp, r)) > 0) > + { > + bp += w; > + r -= w; > + sz += w; > + } > + if (w <= 0) > + break; > + } > + if (r < 0) > + { > + e = errno; > + unlink (dest); > + close (dest_fd); > + error (EXIT_FAILURE, e, _("Error reading file %s"), source); > + } > + if (w < 0) > + { > + e = errno; > + unlink (dest); > + close (dest_fd); > + error (EXIT_FAILURE, e, _("Error writing file %s"), dest); > + } > + > + close (src_fd); > + > + /* Make sure we copied it all. */ > + if (sz < st.st_size) > + { > + unlink (dest); > + close (dest_fd); > + error (EXIT_FAILURE, 0, _("Unable to copy file %s to %s"), > + source, dest); > + } > + > + /* Make sure user can always read the cache file */ > + if (fchmod (dest_fd, S_IROTH|S_IRGRP|S_IRUSR|S_IWUSR)) > + { > + e = errno; > + unlink (dest); > + close (dest_fd); > + error (EXIT_FAILURE, e, > + _("Changing access rights of %s to %#o failed"), dest, > + S_IROTH|S_IRGRP|S_IRUSR|S_IWUSR); > + } > + > + if (fsync (dest_fd) != 0) > + { > + e = errno; > + unlink (dest); > + close (dest_fd); > + error (EXIT_FAILURE, e, _("Writing to %s failed"), dest); > + } > + > + if (rename (dest, cache_file) < 0) > + { > + e = errno; > + unlink (dest); > + close (dest_fd); > + error (EXIT_FAILURE, e, _("Can't rename %s to %s"), > + dest, cache_file); > + } > + > + if (close (dest_fd) != 0) > + error (EXIT_FAILURE, errno, _("Writing to %s failed"), dest); > + exit (0); > +} > > int > main (int argc, char **argv) > @@ -1060,8 +1216,8 @@ main (int argc, char **argv) > argp_parse (&argp, argc, argv, 0, &remaining, NULL); > > /* Remaining arguments are additional directories if opt_manual_link > - is not set. */ > - if (remaining != argc && !opt_manual_link) > + and opt_install are not set. */ > + if (remaining != argc && !opt_manual_link && !opt_install) > { > int i; > for (i = remaining; i < argc; ++i) > @@ -1157,6 +1313,8 @@ main (int argc, char **argv) > exit (0); > } > > + if (opt_install) > + install_cache_file (argv[remaining]); > > if (opt_build_cache) > init_cache (); > diff --git a/elf/tst-ldconfig-install.sh b/elf/tst-ldconfig-install.sh > new file mode 100644 > index 0000000000..550281c7ab > --- /dev/null > +++ b/elf/tst-ldconfig-install.sh > @@ -0,0 +1,125 @@ > +#!/bin/sh > +# Test that ldconfig --install installs a pre-built cache file. > +# 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/>. > + > +set -e > + > +common_objpfx=$1 > +test_wrapper_env=$2 > +run_program_env=$3 > + > +testroot="${common_objpfx}elf/tst-ldconfig-install-directory" > +cleanup () { > + rm -rf "$testroot" > +} > +trap cleanup 0 > + > +rm -rf "$testroot" > +mkdir -p "$testroot/etc" > + > +ldconfig="${common_objpfx}elf/ldconfig" > +run_ldconfig () { > + ${test_wrapper_env} ${run_program_env} "$ldconfig" "$@" > +} > + > +errors=0 > +fail () { > + echo "error: $1" > + errors=1 > +} > + > +# Build a pre-built cache to install. > +source="$testroot/prebuilt-ld.so.cache" > +mkdir -p "$testroot/lib" > +run_ldconfig -X -f /dev/null -C "$source" "$testroot/lib" > +test -r "$source" || fail "ldconfig did not create the pre-built cache" > + > +# Pad the source past the 512-byte internal copy buffer so that the multi-block > +# copy path is exercised, while leaving the cache magic at the start intact. > +dd if=/dev/zero bs=1024 count=4 >> "$source" 2>/dev/null > + > +dest="$testroot/etc/ld.so.cache" > +temp="$dest~" > + > +run_ldconfig --install -f /dev/null -C "$dest" "$source" > + > +# The destination must exist and be byte-identical to the source. > +if test -r "$dest"; then > + if cmp -s "$source" "$dest"; then > + echo "info: installed cache matches the source" > + else > + fail "installed cache differs from the source" > + fi > +else > + fail "destination cache file was not created" > +fi > + > +# The temporary file used during the atomic rename must not be left behind. > +if test -e "$temp"; then > + fail "temporary file $temp was left behind" > +fi > + > +# The installed cache must be world-readable (0644). > +if test -r "$dest"; then > + mode=$(ls -l "$dest" | cut -c1-10) > + case "$mode" in > + (-rw-r--r--) echo "info: installed cache has expected permissions" ;; > + (*) fail "installed cache has unexpected permissions: $mode" ;; > + esac > +fi > + > +# A second install over an existing cache must also succeed. > +run_ldconfig --install -f /dev/null -C "$dest" "$source" > +if cmp -s "$source" "$dest"; then > + echo "info: re-install over an existing cache works" > +else > + fail "re-install produced a different cache" > +fi > + > +# Error case: a source file that is not a cache must be rejected, and no > +# destination must be produced. > +rm -f "$dest" > +notcache="$testroot/not-a-cache" > +echo "this is not an ld.so.cache file" > "$notcache" > +if run_ldconfig --install -f /dev/null -C "$dest" "$notcache" 2>"$testroot/err"; then > + fail "ldconfig accepted a file that is not a cache" > +else > + if grep -q "does not look like an ld.so.cache file" "$testroot/err"; then > + echo "info: non-cache source correctly rejected" > + else > + fail "unexpected error message for non-cache source" > + cat "$testroot/err" > + fi > +fi > +test -e "$dest" && fail "destination created from an invalid source" > + > +# Error case: a missing source argument must be diagnosed. > +if run_ldconfig --install -f /dev/null -C "$dest" 2>"$testroot/err"; then > + fail "ldconfig accepted --install without a source file" > +else > + grep -q "Missing source file name" "$testroot/err" \ > + || fail "unexpected error message for missing source" > +fi > + > +# Error case: a nonexistent source must be diagnosed. > +if run_ldconfig --install -f /dev/null -C "$dest" \ > + "$testroot/does-not-exist" 2>"$testroot/err"; then > + fail "ldconfig accepted a nonexistent source file" > +fi > + > +exit $errors