Skip to content

Take AT_EXECFN from the execve filename not argv[0] - #282

Merged
jserv merged 1 commit into
sysprog21:mainfrom
open-sources-port:execve-rosetta
Aug 12, 2026
Merged

Take AT_EXECFN from the execve filename not argv[0]#282
jserv merged 1 commit into
sysprog21:mainfrom
open-sources-port:execve-rosetta

Conversation

@doanbaotrung

@doanbaotrung doanbaotrung commented Aug 11, 2026

Copy link
Copy Markdown
Collaborator

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.

  • Bug Fixes
    • Pass the execve path into build_linux_stack and copy it onto the stack as its own string for AT_EXECFN; fall back to argv[0] only if no filename is provided.
    • Apply to initial bootstrap, rosetta execve re-bootstrap, and native execve (including execve(path, ["altname"], ...)); keeps AT_EXECFN consistent with /proc/self/exe.

Written for commit e22f298. Summary will update on new commits.

Review in cubic

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 1 file

Re-trigger cubic

@jserv jserv left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the AT_EXECFN change on three independent passes. The change is correct and I found no blocker.

Verified as non-issues:

  • argc > 1 can never be false on a rosetta stack build. rosetta_finalize computes rosetta_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, and total_entries is unaffected since AT_EXECFN still contributes exactly one entry.
  • g->is_rosetta is set before both build_linux_stack call sites: bootstrap.c:421 for the initial boot, and exec.c:1179 before guest_reset for the re-bootstrap. guest_clear_rosetta_state only 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] is binary_guest_path, not elf_host_path or 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:

  1. src/syscall/syscall.c:2218 resolves execveat targets through fcntl(F_GETPATH), which resolves symlinks. That resolved path becomes sys_execve's path, then rosetta_argv[1], then AT_EXECFN. A multi-call binary reached through a symlinked applet name (/usr/bin/ln pointing at coreutils) would report coreutils on that route, which is the same failure this PR fixes for plain execve. Plain execve is unaffected because path keeps the guest's literal string. Worth knowing whether any of the apt/dpkg workloads reach exec through execveat/fexecve, or whether this stays theoretical.

  2. src/syscall/exec.c:779 overwrites path with 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 leaves bprm->exec pointing at the script. This is consistent with what /proc/self/exe reports today, so changing it would mean deciding those two surfaces are allowed to disagree, which they do on real Linux.

Comment thread src/core/stack.c Outdated
* otherwise read back "rosetta" as their own program name and abort.
*/
uint64_t execfn_ptr = 0;
if (g->is_rosetta && argc > 1)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@doanbaotrung

Copy link
Copy Markdown
Collaborator Author

What changed

build_linux_stack() now takes an explicit const char *execfn and writes it onto the stack as its own string, the way fs/binfmt_elf.c does, instead of deriving AT_EXECFN from an argv index. The argv-shape coupling is gone: if rosetta_finalize ever switches to the 4-slot preserving form, nothing in stack.c needs to know.

Three call sites, not two — your list missed src/syscall/exec.c:1548, the native execve re-bootstrap. The compiler caught it:

  • bootstrap.c:627 (initial boot, native + rosetta) → elf_guest_path
  • bootstrap.c:884 (rosetta execve re-bootstrap) → elf_guest_path
  • exec.c:1548 (native execve re-bootstrap) → path, the same guest-visible spelling published to proc_set_elf_path() twenty lines later

That third one closes the native asymmetry you flagged: execve("/bin/ls", ["altname"], …) now reports /bin/ls, and AT_EXECFN agrees with /proc/self/exe on all paths rather than only the rosetta one. execfn == NULL still falls back to argv[0] so no caller can produce an AT_EXECFN of 0.

Your question about execveat

Not theoretical-only by construction, but theoretical for these workloads. Grepping syscall 281@ across the verbose traces I captured of the failing apt-get install (apt-get → dpkg → dpkg-deb → tar → rm → dpkg-split, 32 execve calls) and of the standalone dpkg runs: zero execveat calls, all 32 execs go through plain execve(221). So the F_GETPATH symlink-resolution route at syscall.c:2218 is unreached by apt/dpkg; the multi-call-via-symlinked-applet case would need a caller that actually uses execveat/fexecve.

Gap 2 (shebang) left alone

I passed path at exec.c:1548 deliberately, which preserves today's behaviour: AT_EXECFN reports the interpreter for a shebang script, matching what /proc/self/exe says. Changing it means letting those two surfaces disagree — which, as you note, is what real Linux does. That's a decision about which surface to make accurate, not a bug this PR should settle.

Verification

   
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 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.

@doanbaotrung doanbaotrung changed the title Report the target binary in AT_EXECFN under rosetta Take AT_EXECFN from the execve filename not argv[0] Aug 11, 2026
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.
@jserv
jserv merged commit 0023115 into sysprog21:main Aug 12, 2026
21 checks passed
@doanbaotrung
doanbaotrung deleted the execve-rosetta branch August 12, 2026 03:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants