Take AT_EXECFN from the execve filename not argv[0] - #282
Conversation
jserv
left a comment
There was a problem hiding this comment.
Reviewed the AT_EXECFN change on three independent passes. The change is correct and I found no blocker.
Verified as non-issues:
argc > 1can never be false on a rosetta stack build.rosetta_finalizecomputesrosetta_argc = (guest_argc > 0) ? guest_argc + 1 : 2(src/core/rosetta.c:442), so slot 1 always exists even when the caller supplied no argv. The fallback branch is unreachable from the rosetta side rather than a silent re-introduction of the bug.arg_ptrs[1]is in bounds under that guard, andtotal_entriesis unaffected since AT_EXECFN still contributes exactly one entry.g->is_rosettais set before bothbuild_linux_stackcall sites:bootstrap.c:421for the initial boot, andexec.c:1179beforeguest_resetfor the re-bootstrap.guest_clear_rosetta_stateonly runs when leaving rosetta, so there is no window where the flag is stale at stack-build time.- No host path leaks into guest auxv.
rosetta_argv[1]isbinary_guest_path, notelf_host_pathor the materialized temp, so a FUSE-backed binary still reports its guest spelling.
Two residual gaps that this PR does not introduce but that sit on the same path, filed here rather than inline because they are outside the diff:
-
src/syscall/syscall.c:2218resolves execveat targets throughfcntl(F_GETPATH), which resolves symlinks. That resolved path becomessys_execve'spath, thenrosetta_argv[1], then AT_EXECFN. A multi-call binary reached through a symlinked applet name (/usr/bin/lnpointing atcoreutils) would reportcoreutilson that route, which is the same failure this PR fixes for plain execve. Plainexecveis unaffected becausepathkeeps the guest's literal string. Worth knowing whether any of the apt/dpkg workloads reach exec throughexecveat/fexecve, or whether this stays theoretical. -
src/syscall/exec.c:779overwritespathwith the interpreter before the rosetta re-bootstrap, so a shebang script under rosetta reports the x86_64 interpreter in AT_EXECFN. Linux keeps the original script filename there: binfmt_script prepends the interpreter to argv but leavesbprm->execpointing at the script. This is consistent with what/proc/self/exereports today, so changing it would mean deciding those two surfaces are allowed to disagree, which they do on real Linux.
| * otherwise read back "rosetta" as their own program name and abort. | ||
| */ | ||
| uint64_t execfn_ptr = 0; | ||
| if (g->is_rosetta && argc > 1) |
There was a problem hiding this comment.
The fix is right, but the exec filename is inferred from argv shape rather than passed in. It holds today only because rosetta_finalize owns both ends of the contract, and that coupling is implicit: the comment at src/core/rosetta.c:424 already contemplates switching to the 4-slot preserving form ([rosetta, binary, original_argv[0], original_argv[1..]]) if a login-shell or execve(path, "altname", ...) workload ever surfaces. If that switch happens, the target moves to slot 1 or 2 depending on the form and this line silently points at the wrong string again, with no compile-time or runtime signal. Threading an explicit const char *execfn into build_linux_stack from the two callers that already know the path would make the contract checkable at the call site instead of inferred from indices.
That parameter would also close the matching gap on the native side, which still reports argv[0]. Linux takes AT_EXECFN from the filename handed to execve, so execve("/bin/ls", ["altname"], ...) on a native aarch64 guest still reports altname where the kernel reports /bin/ls. Pre-existing and out of scope for this PR, but after this change the rosetta branch is the accurate one and the native branch is not, so the asymmetry is worth recording somewhere.
62ef460 to
7542021
Compare
What changed
Three call sites, not two — your list missed
That third one closes the native asymmetry you flagged: Your question about
|
| x64 rosetta, initial bootstrap | coreutils.real echo hi → hi |
| x64 rosetta, execve re-bootstrap | ln --version | head -1 → ln (uutils coreutils) 0.8.0 |
| arm64 native | same, no regression |
| make test-sysroot-procfs-exec | 6 passed, 0 failed |
| make test-proctitle-low-stack | PASS (stack=1024 KiB) — relevant since the execfn string adds to the string area |
| tests/test-rosetta-execfd.sh | 2 passed, 1 skipped (no aarch64 cross-compiler) |
Updated commit message
Take AT_EXECFN from the execve filename, not argv[0]build_linux_stack derived AT_EXECFN from argv[0]. Linux takes it from
bprm->filename, and the two diverge in two ways elfuse reproduces:
execve(path, ["altname"], ...) reports path, and under binfmt_misc the
prepended interpreter is not the program the guest asked to run.Under rosetta argv[0] is the translator, so every translated process
reported "rosetta" as its own program name. Guests that identify
themselves through auxv rather than argv[0] broke on it: rust-coreutils
dispatches its multi-call applet from AT_EXECFN, so every coreutils
invocation in an x86_64 Ubuntu 26.04 rootfs died withcoreutils: unknown program 'rosetta'
taking down any apt/dpkg run that shelled out to ln, cat or rm.
Pass the filename explicitly and copy it onto the stack as its own
string, as fs/binfmt_elf.c does, rather than pointing at an argv slot.
The rosetta argv layout is free to change (see the preserving-form note
in rosetta.c) without silently re-breaking this. All three stack-build
paths supply the guest-visible spelling they already publish through
/proc/self/exe: initial bootstrap, rosetta execve re-bootstrap, and
native execve re-bootstrap. The last of these also fixes the native case
of execve(path, ["altname"], ...), which reported the alternate name.
build_linux_stack derived AT_EXECFN from argv[0]. Linux takes it from bprm->filename, and the two diverge in two ways elfuse reproduces: execve(path, ["altname"], ...) reports path, and under binfmt_misc the prepended interpreter is not the program the guest asked to run. Under rosetta argv[0] is the translator, so every translated process reported "rosetta" as its own program name. Guests that identify themselves through auxv rather than argv[0] broke on it: rust-coreutils dispatches its multi-call applet from AT_EXECFN, so every coreutils invocation in an x86_64 Ubuntu 26.04 rootfs died with coreutils: unknown program 'rosetta' taking down any apt/dpkg run that shelled out to ln, cat or rm. Pass the filename explicitly and copy it onto the stack as its own string, as fs/binfmt_elf.c does, rather than pointing at an argv slot. The rosetta argv layout is free to change (see the preserving-form note in rosetta.c) without silently re-breaking this. All three stack-build paths supply the guest-visible spelling they already publish through /proc/self/exe: initial bootstrap, rosetta execve re-bootstrap, and native execve re-bootstrap. The last of these also fixes the native case of execve(path, ["altname"], ...), which reported the alternate name.
7542021 to
e22f298
Compare
Take AT_EXECFN from the execve filename, not argv[0]
build_linux_stack derived AT_EXECFN from argv[0]. Linux takes it from
bprm->filename, and the two diverge in two ways elfuse reproduces:
execve(path, ["altname"], ...) reports path, and under binfmt_misc the
prepended interpreter is not the program the guest asked to run.
Under rosetta argv[0] is the translator, so every translated process
reported "rosetta" as its own program name. Guests that identify
themselves through auxv rather than argv[0] broke on it: rust-coreutils
dispatches its multi-call applet from AT_EXECFN, so every coreutils
invocation in an x86_64 Ubuntu 26.04 rootfs died with
coreutils: unknown program 'rosetta'
taking down any apt/dpkg run that shelled out to ln, cat or rm.
Pass the filename explicitly and copy it onto the stack as its own
string, as fs/binfmt_elf.c does, rather than pointing at an argv slot.
The rosetta argv layout is free to change (see the preserving-form note
in rosetta.c) without silently re-breaking this. All three stack-build
paths supply the guest-visible spelling they already publish through
/proc/self/exe: initial bootstrap, rosetta execve re-bootstrap, and
native execve re-bootstrap. The last of these also fixes the native case
of execve(path, ["altname"], ...), which reported the alternate name.
Summary by cubic
Set AT_EXECFN to the execve filename across native and rosetta so processes report the target binary, not the translator, matching Linux semantics. This restores rust-coreutils multi-call dispatch and unblocks apt/dpkg on translated guests.
build_linux_stackand copy it onto the stack as its own string for AT_EXECFN; fall back to argv[0] only if no filename is provided.Written for commit e22f298. Summary will update on new commits.