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

Christian Brauner <[email protected]>
Newsgroups gmane.comp.lib.glibc.alpha
Message-ID <[email protected]>
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) <[email protected]>
---
 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
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.