[PATCH v2 0/3] elf: load the main program from AT_EXECFD
Christian Brauner
brauner@kernel.org
Thu Jul 16 14:55:28 GMT 2026
A Linux binfmt_misc handler can dispatch a program to a chosen dynamic
linker. When the handler is registered with the 'O' (open binary) or 'C'
(credentials) flag, the kernel keeps the executed file open across the
dispatch. It hands the descriptor to the interpreter in the AT_EXECFD
auxiliary vector entry. This is the SVR4 contract. AT_EXECFD is "the
file descriptor of the program to load", the alternative to AT_PHDR.
FreeBSD's rtld consumes it, and qemu-user has consumed it since 2013.
The GNU dynamic linker never has. When such a handler dispatches to
ld.so, the descriptor is ignored. It leaks into the new program. ld.so
opens the executable again, this time by the path spliced into the
argument vector.
Opening the file again by path is racy, and often it is not possible at
all. The file ld.so opens need not be the file the kernel checked and
executed. A binary with execute permission but no read permission can be
run by execve(). It cannot be opened for reading by path, so ld.so
rejects it with EACCES, even though the descriptor it was handed is
readable. Sometimes there is no path to open. The program may be a
sealed memfd.
As Carlos said in [1] "I like where this is going because you can run
ld.so to execute an isolated AT_EXECFD application." which is a good way
to think about this.
Also, nixos wants to use binfmt_misc to execute elf binaries and will be
invoking the loader this way. I also have a use-case for this in
systemd-homed in the future and I'm extending the abilities of
binfmt_misc a bit. So I would really appreciate if we could support
this in the glibc loader.
This series makes ld.so load the main program from AT_EXECFD when ld.so
is the program the kernel loaded. The object comes from the descriptor,
not from a path opened a second time. The loaded file is exactly the one
execve() checked, so there is no race. A binary with no read permission
runs. A program with no accessible path runs. Applications see no
difference. No kernel changes are needed. The binfmt_misc 'O' and 'C'
flags have been around for a long time. This works against current
kernels.
The series also adds the option "ld.so --program-fd N NAME". This is the
explicit counterpart to AT_EXECFD. It lets a program run from an
inherited descriptor, such as a sealed memfd, with no kernel dispatch at
all.
I've discussed this on mastodon in [2] with Carlos a little bit a couple
of days ago.
An LLM was used to get familiar with this particular part of the glibc
codebase and for review of the implementation. It's been a while so I
hope I still got the gist of contributions right.
Link: https://mastodon.social/deck/@codonell@fosstodon.org/116918469134997346 [1]
Link: https://mastodon.social/deck/@brauner/116913310306668229 [2]
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
Changes in v2:
- Load the ELF header with pread so a shared descriptor's file
position is neither relied upon nor disturbed (Florian).
- Fold the secure standard-descriptor recheck into patch 1 so no
intermediate commit is exposed (Andreas Schwab).
- Reject closed descriptors via F_GETFD; parse --program-fd as a strict
decimal, matching pldd's pid parsing.
- Add a generic dl-standard-fds.h wrapper so the loader links on Hurd.
- Harden tst-rtld-execfd (drop the capability bounding set) and use pread
in the MIPS PT_MIPS_ABIFLAGS check.
- Link to v1: https://patch.msgid.link/20260715-work-glibc-binfmt_misc-v1-0-b3b14336e664@kernel.org
---
Christian Brauner (3):
elf: load the main program from AT_EXECFD when run as a binfmt interpreter
elf: test AT_EXECFD consumption through a binfmt_misc 'O' handler
elf: add ld.so --program-fd
NEWS | 15 ++
elf/Makefile | 4 +
elf/dl-load.c | 65 +++++-
elf/dl-usage.c | 2 +
elf/rtld.c | 165 +++++++++++++-
elf/tst-rtld-program-fd-prog.c | 28 +++
elf/tst-rtld-program-fd.c | 127 +++++++++++
sysdeps/generic/dl-standard-fds.h | 34 +++
sysdeps/generic/ldsodefs.h | 7 +
sysdeps/mips/dl-machine-reject-phdr.h | 4 +-
sysdeps/unix/sysv/linux/Makefile | 5 +
sysdeps/unix/sysv/linux/dl-standard-fds.h | 33 +++
sysdeps/unix/sysv/linux/tst-rtld-execfd-prog.c | 43 ++++
sysdeps/unix/sysv/linux/tst-rtld-execfd.c | 291 +++++++++++++++++++++++++
14 files changed, 809 insertions(+), 14 deletions(-)
---
base-commit: 5396eb704531d9ede0388bffcc21b93bb661d404
change-id: 20260715-work-glibc-binfmt_misc-939266ac4824
More information about the Libc-alpha
mailing list