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

Christian Brauner brauner@kernel.org
Fri Jul 17 11:12:00 GMT 2026


Expose the AT_EXECFD loading path for explicit loader invocations:

    ld.so --program-fd NUMBER NAME [ARGS...]

loads the main program from the inherited descriptor NUMBER; NAME is
still consumed as the program name argument and only names the program
(argument processing, --argv0 and everything else compose as usual).
FreeBSD's ld-elf.so.1 has the equivalent -f option.

This makes running a program from a descriptor possible without any
kernel dispatch (e.g. executing a sealed memfd under a chosen loader)
and gives the descriptor-loading code deterministic test coverage on
kernels and CI setups where the binfmt_misc test is UNSUPPORTED.  For
a descriptor without a usable path such as a sealed memfd, $ORIGIN
degrades to "/", just as it does for fexecve; a comment notes this.

The descriptor number is parsed with _dl_strtoul like the loader's
other numbers, but rejected unless it is a bare non-negative decimal in
range, so a signed or zero-padded argument cannot select an unintended
descriptor.

The --verify and --help code paths go through map_doit, which learns to
route around the path-based open when a descriptor is set. The secure
standard-descriptor recheck added with AT_EXECFD already covers a
descriptor from either source.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 NEWS           |  6 ++++++
 elf/dl-load.c  |  4 +++-
 elf/dl-usage.c |  2 ++
 elf/rtld.c     | 35 +++++++++++++++++++++++++++++++++--
 4 files changed, 44 insertions(+), 3 deletions(-)

diff --git a/NEWS b/NEWS
index cacd3f8be6..2988ffc14b 100644
--- a/NEWS
+++ b/NEWS
@@ -18,6 +18,12 @@ Major new features:
   handlers, and the descriptor refers to the file the kernel actually
   access-checked, eliminating the re-open race.
 
+* The dynamic linker accepts a new option --program-fd NUMBER when
+  invoked as a command, loading the executable from the inherited
+  descriptor NUMBER; the program name argument then only names the
+  program.  This is the explicit-invocation counterpart of AT_EXECFD
+  (FreeBSD's ld-elf.so.1 has the equivalent -f option).
+
 * A new tunable, glibc.elf.thp, is added to map read-only segments with
   Transparent Huge Pages (THP) if THP isn't disable in kernel.  When
   glibc.elf.thp is set to 1, malloc uses the actual kernel THP mode
diff --git a/elf/dl-load.c b/elf/dl-load.c
index f2f14dd6f3..6cc9a22010 100644
--- a/elf/dl-load.c
+++ b/elf/dl-load.c
@@ -2249,7 +2249,9 @@ _dl_map_object (struct link_map *loader, const char *name,
    that kept the executed binary open) or from an explicit loader
    invocation.  NAME is the name the program is known by and is only
    used for diagnostics; the canonical name used for $ORIGIN is derived
-   from the descriptor itself (__RTLD_OPENEXEC).  There may be no path
+   from the descriptor itself (__RTLD_OPENEXEC).  For a descriptor
+   without a usable path - a sealed memfd, say - the origin degrades
+   to "/", just as it does for fexecve.  There may be no path
    the program could be opened by: the descriptor is readable even for
    an execute-only binary, and it refers to the very file the kernel
    access-checked, so no path re-open takes its place.  */
diff --git a/elf/dl-usage.c b/elf/dl-usage.c
index a5bc1cb4ad..51db2355d3 100644
--- a/elf/dl-usage.c
+++ b/elf/dl-usage.c
@@ -196,6 +196,8 @@ setting environment variables (which would be inherited by subprocesses).\n\
   --audit LIST          use objects named in LIST as auditors\n\
   --preload LIST        preload objects named in LIST\n\
   --argv0 STRING        set argv[0] to STRING before running\n\
+  --program-fd FD       load the executable from the inherited file\n\
+                        descriptor FD; EXECUTABLE-FILE only names it\n\
   --list-tunables       list all tunables with minimum and maximum values\n\
   --list-diagnostics    list diagnostics information\n\
   --help                display this help and exit\n\
diff --git a/elf/rtld.c b/elf/rtld.c
index 3d383ae3b9..778bfd3856 100644
--- a/elf/rtld.c
+++ b/elf/rtld.c
@@ -19,6 +19,7 @@
 #include <errno.h>
 #include <dlfcn.h>
 #include <fcntl.h>
+#include <limits.h>
 #include <stdbool.h>
 #include <stdlib.h>
 #include <string.h>
@@ -603,6 +604,9 @@ struct map_args
   const char *str;
   struct link_map *loader;
   int mode;
+  /* If not -1, map the main executable from this descriptor instead
+     of opening STR (requires __RTLD_OPENEXEC in MODE).  */
+  int execfd;
   /* Return value of map_doit.  */
   struct link_map *map;
 };
@@ -640,8 +644,11 @@ map_doit (void *a)
 {
   struct map_args *args = (struct map_args *) a;
   int type = (args->mode == __RTLD_OPENEXEC) ? lt_executable : lt_library;
-  args->map = _dl_map_object (args->loader, args->str, type, 0,
-			      args->mode, LM_ID_BASE);
+  if (args->mode == __RTLD_OPENEXEC && args->execfd != -1)
+    args->map = _dl_map_object_execfd (args->execfd, args->str);
+  else
+    args->map = _dl_map_object (args->loader, args->str, type, 0,
+				args->mode, LM_ID_BASE);
 }
 
 static void
@@ -792,6 +799,7 @@ do_preload (const char *fname, struct link_map *main_map, const char *where)
   args.str = fname;
   args.loader = main_map;
   args.mode = __RTLD_SECURE;
+  args.execfd = -1;
 
   unsigned int old_nloaded = GL(dl_ns)[LM_ID_BASE]._ns_nloaded;
 
@@ -1514,6 +1522,28 @@ dl_main (const ElfW(Phdr) *phdr,
 	  {
 	    argv0 = _dl_argv[2];
 
+	    _dl_argc -= 2;
+	    _dl_argv += 2;
+	  }
+	else if (! strcmp (_dl_argv[1], "--program-fd") && _dl_argc > 2)
+	  {
+	    /* Load the program from an inherited descriptor, like AT_EXECFD
+	       does; the program name argument only names it (same as
+	       FreeBSD's ld-elf.so.1 -f).  Parse the descriptor with
+	       _dl_strtoul as the loader parses its other numbers, then
+	       reject what it would accept for a plain descriptor - a sign,
+	       leading whitespace, a base prefix - and require a bare decimal
+	       ("0" or [1-9][0-9]*), fully consumed and in range, like pldd's
+	       pid check.  */
+	    const char *arg = _dl_argv[2];
+	    char *endp;
+	    uint64_t fd = _dl_strtoul (arg, &endp);
+	    if (arg[0] < '0' || arg[0] > '9' || *endp != '\0'
+		|| (arg[0] == '0' && arg[1] != '\0') || fd > INT_MAX)
+	      _dl_fatal_printf ("%s: invalid descriptor '%s' given to"
+				" --program-fd\n", ld_so_name, _dl_argv[2]);
+	    execfd = fd;
+
 	    _dl_argc -= 2;
 	    _dl_argv += 2;
 	  }
@@ -1633,6 +1663,7 @@ dl_main (const ElfW(Phdr) *phdr,
 	  args.str = rtld_progname;
 	  args.loader = NULL;
 	  args.mode = __RTLD_OPENEXEC;
+	  args.execfd = execfd;
 	  (void) _dl_catch_error (&objname, &err_str, &malloced, map_doit,
 				  &args);
 	  if (__glibc_unlikely (err_str != NULL))

-- 
2.53.0



More information about the Libc-alpha mailing list