[PATCH 0/3] elf: load the main program from AT_EXECFD

Christian Brauner brauner@kernel.org
Wed Jul 15 21:10:03 GMT 2026


On 2026-07-15 13:08 +0200, Florian Weimer wrote:
> * Christian Brauner:
> 
> > A Linux binfmt_misc handler can dispatch a program to a chosen dynamic
> > linker.  This is how you run a program under a different or newer ld.so.
> 
> Ahem, you'd use an explicit ld.so invocation for that?

Sorry, that sentence accidentally took the spotlight and as evidenced
from the more detailed description it's obviously not the main use-case.
I'll drop it.

> > 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.
> 
> Adding this functionality to ld.so seems reasonable in principle.

Thanks!

> What does /proc/self/exe look like for such processes?  Does GDB work?
> There are two cases to consider: launching with GDB, and attaching GDB
> to a running process with the -p option.

Right, I checked that.

/proc/self/exe is ld.so. The kernel exec'd ld.so, so it names ld.so
whether the program arrived via AT_EXECFD, via --program-fd, or via a
plain "ld.so PROG" command line.

Whether the binary was descriptor loaded or not doesn't make a
difference. It is identical to an explicit "ld.so PROG" invocation
today. So it's not a new situation for the tooling.

I also checked both gdb cases and they are identical as well.

gdb -p: /proc/PID/exe is ld.so. gdb finds libc and the rest
through the link map and the backtrace is clean. A --program-fd
process and an "ld.so PROG" process produce identical output.

gdb --args ld.so ... PROG or --program-fd: a breakpoint in a library
function resolves and hits. /proc/PID/exe is ld.so and the backtrace are
identical.

Both cases of course mean that gdb does not auto-load the main program's
own symbols because it takes ld.so as the main object file. But that is
the existing behaviour of "ld.so PROG" as well and is independent of
this series.

So this just supports AT_EXECFD without changing behavior. My ideal
scenario however is to allow binfmt_misc to communicate a "transparent"
mode to the loader. And I have patches for that as well but they are
independent of this series.

In transparent mode binfmt_misc sets AT_EXECFD and a new
AT_FLAGS_PRESERVE_ARGV flag gets added that binfmt_misc may raise.
(It's modeled after AT_FLAGS_PRESERVE_ARGV0 which was introduced for qemu
quite some time ago.)

When AT_FLAGS_PRESERVE_ARGV is set together with AT_EXECFD binfmt_misc
leaves the argument vector entirely to the program.

It splices neither the interpreter nor the binary path into argv. The
loader takes the binary from AT_EXECFD, names it from AT_EXECFN and
consumes no arguments. So argv[0] and /proc/pid/cmdline look exactly
like a direct execution. The loader becomes invisible.

The flag is what lets the loader tell the layouts apart. A classic
'O'/'C' entry today also passes AT_EXECFD but splices the path into
argv.

All of that is a separate series though. This one is just the AT_EXECFD
consumption it builds on and is useful independent of this.

Fyi, I expressed the same idea to Carlos on Mastodon.



More information about the Libc-alpha mailing list