[PATCH v3 4/4] elf: test ld.so --program-fd

Christian Brauner <[email protected]>
Newsgroups gmane.comp.lib.glibc.alpha
Message-ID <[email protected]>
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) <[email protected]>
---
 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
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.