[PATCH v3 4/4] elf: test ld.so --program-fd
Christian Brauner
brauner@kernel.org
Fri Jul 17 11:12:01 GMT 2026
Run the dynamic linker with --program-fd on an inherited descriptor of
a helper program: it must be loaded from the descriptor, with the
program name argument only naming it. This exercises the same loading
path as AT_EXECFD without any kernel support, giving the
descriptor-loading code deterministic coverage on kernels and CI
setups where the binfmt_misc test reports UNSUPPORTED.
Unlike a descriptor installed by the kernel, one given on the command
line need not be positioned at the start of the file, so one subtest
hands the loader a descriptor deliberately seeked mid-file and
verifies that the program runs and that the shared file position is
left undisturbed (the ELF header is read with pread). The remaining
subtests verify that --program-fd composes with --argv0 and that a
closed descriptor or a non-numeric argument produces a clean error,
not a crash.
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
elf/Makefile | 4 ++
elf/tst-rtld-program-fd-prog.c | 28 ++++++++
elf/tst-rtld-program-fd.c | 150 +++++++++++++++++++++++++++++++++++++++++
3 files changed, 182 insertions(+)
diff --git a/elf/Makefile b/elf/Makefile
index 01e77f2ca0..2acf8d9c71 100644
--- a/elf/Makefile
+++ b/elf/Makefile
@@ -493,6 +493,7 @@ tests += \
tst-rtld-no-malloc \
tst-rtld-no-malloc-audit \
tst-rtld-no-malloc-preload \
+ tst-rtld-program-fd \
tst-rtld-run-static \
tst-single_threaded \
tst-single_threaded-pthread \
@@ -595,6 +596,7 @@ tests-container += \
test-srcs = \
tst-pathopt \
+ tst-rtld-program-fd-prog \
tst-sprof-basic \
# tests-srcs
@@ -3248,6 +3250,8 @@ $(objpfx)tst-rtld-list-diagnostics.out: tst-rtld-list-diagnostics.py \
> $@; \
$(evaluate-test)
+$(objpfx)tst-rtld-program-fd.out: $(objpfx)tst-rtld-program-fd-prog
+
$(objpfx)tst-rtld-run-static.out: $(objpfx)ldconfig
$(objpfx)tst-dl_find_object.out: \
diff --git a/elf/tst-rtld-program-fd-prog.c b/elf/tst-rtld-program-fd-prog.c
new file mode 100644
index 0000000000..183358f4aa
--- /dev/null
+++ b/elf/tst-rtld-program-fd-prog.c
@@ -0,0 +1,28 @@
+/* Helper program for tst-rtld-program-fd: report the argument vector.
+ 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/>. */
+
+#include <stdio.h>
+
+int
+main (int argc, char **argv)
+{
+ printf ("argc=%d\n", argc);
+ for (int i = 0; i < argc; ++i)
+ printf ("argv[%d]=%s\n", i, argv[i]);
+ return 0;
+}
diff --git a/elf/tst-rtld-program-fd.c b/elf/tst-rtld-program-fd.c
new file mode 100644
index 0000000000..9c3966943f
--- /dev/null
+++ b/elf/tst-rtld-program-fd.c
@@ -0,0 +1,150 @@
+/* Test the ld.so --program-fd option.
+ 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/>. */
+
+/* Run the dynamic linker with --program-fd on an inherited descriptor
+ of the helper program: it must be loaded from the descriptor, with
+ the program name argument only naming it. Exercises the same
+ loading path as AT_EXECFD, without requiring kernel support. */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <support/capture_subprocess.h>
+#include <support/check.h>
+#include <support/support.h>
+#include <support/xunistd.h>
+
+static int
+do_test (void)
+{
+ char *prog = xasprintf ("%s/elf/tst-rtld-program-fd-prog",
+ support_objdir_root);
+ char *libpath = xasprintf ("%s:%s/elf", support_objdir_root,
+ support_objdir_root);
+
+ /* No O_CLOEXEC: the descriptor must survive into ld.so. */
+ int fd = xopen (prog, O_RDONLY, 0);
+ char *fdstr = xasprintf ("%d", fd);
+
+ /* Plain use: the program comes from the descriptor, the name
+ argument becomes argv[0]. */
+ {
+ char *argv[] =
+ {
+ (char *) "ld.so", (char *) "--library-path", libpath,
+ (char *) "--program-fd", fdstr,
+ (char *) "displayed-name", (char *) "tail-arg", NULL
+ };
+ struct support_capture_subprocess cap
+ = support_capture_subprogram (support_objdir_elf_ldso, argv, NULL);
+ support_capture_subprocess_check (&cap, "program-fd", 0,
+ sc_allow_stdout);
+ TEST_COMPARE_STRING (cap.out.buffer,
+ "argc=2\n"
+ "argv[0]=displayed-name\n"
+ "argv[1]=tail-arg\n");
+ support_capture_subprocess_free (&cap);
+ }
+
+ /* The descriptor's file position must be irrelevant and preserved:
+ the loader reads the ELF header with pread. Hand over the
+ descriptor deliberately positioned mid-file and verify that the
+ program still runs and that the position - shared with the
+ subprocess - is where it was left. */
+ {
+ xlseek (fd, 123, SEEK_SET);
+ char *argv[] =
+ {
+ (char *) "ld.so", (char *) "--library-path", libpath,
+ (char *) "--program-fd", fdstr,
+ (char *) "displayed-name", NULL
+ };
+ struct support_capture_subprocess cap
+ = support_capture_subprogram (support_objdir_elf_ldso, argv, NULL);
+ support_capture_subprocess_check (&cap, "program-fd position", 0,
+ sc_allow_stdout);
+ TEST_COMPARE_STRING (cap.out.buffer,
+ "argc=1\n"
+ "argv[0]=displayed-name\n");
+ TEST_COMPARE (xlseek (fd, 0, SEEK_CUR), 123);
+ support_capture_subprocess_free (&cap);
+ }
+
+ /* Composes with --argv0. No rewind: the previous subtest left the
+ position mid-file, which the loader ignores. */
+ {
+ char *argv[] =
+ {
+ (char *) "ld.so", (char *) "--library-path", libpath,
+ (char *) "--program-fd", fdstr, (char *) "--argv0",
+ (char *) "overridden", (char *) "displayed-name", NULL
+ };
+ struct support_capture_subprocess cap
+ = support_capture_subprogram (support_objdir_elf_ldso, argv, NULL);
+ support_capture_subprocess_check (&cap, "program-fd --argv0", 0,
+ sc_allow_stdout);
+ TEST_COMPARE_STRING (cap.out.buffer,
+ "argc=1\n"
+ "argv[0]=overridden\n");
+ support_capture_subprocess_free (&cap);
+ }
+
+ /* A closed descriptor must produce a clean error, not a crash. */
+ {
+ char *argv[] =
+ {
+ (char *) "ld.so", (char *) "--program-fd", (char *) "977",
+ (char *) "does-not-matter", NULL
+ };
+ struct support_capture_subprocess cap
+ = support_capture_subprogram (support_objdir_elf_ldso, argv, NULL);
+ support_capture_subprocess_check (&cap, "program-fd bad fd", 127,
+ sc_allow_stderr);
+ TEST_VERIFY (strstr (cap.err.buffer,
+ "cannot load main program from descriptor")
+ != NULL);
+ support_capture_subprocess_free (&cap);
+ }
+
+ /* A non-numeric argument must produce a clean error. */
+ {
+ char *argv[] =
+ {
+ (char *) "ld.so", (char *) "--program-fd", (char *) "pear",
+ (char *) "does-not-matter", NULL
+ };
+ struct support_capture_subprocess cap
+ = support_capture_subprogram (support_objdir_elf_ldso, argv, NULL);
+ support_capture_subprocess_check (&cap, "program-fd non-numeric", 127,
+ sc_allow_stderr);
+ TEST_VERIFY (strstr (cap.err.buffer, "invalid descriptor") != NULL);
+ support_capture_subprocess_free (&cap);
+ }
+
+ xclose (fd);
+ free (fdstr);
+ free (libpath);
+ free (prog);
+ return 0;
+}
+
+#include <support/test-driver.c>
--
2.53.0
More information about the Libc-alpha
mailing list