Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion docs/internals.md
Original file line number Diff line number Diff line change
Expand Up @@ -981,7 +981,10 @@ How it works:
2. The interpreter is loaded as `ET_DYN` at `g->interp_base` (computed
dynamically: 60 GiB for 36-bit IPA, 1020 GiB for 40-bit IPA).
3. `build_linux_stack()` passes `AT_BASE` (interpreter load address) and
`AT_EXECFN` (`argv[0]`) in the auxiliary vector.
`AT_EXECFN` (the execve filename, supplied by the caller) in the auxiliary
vector. Linux takes `AT_EXECFN` from `bprm->filename`, so it stays the
program the guest asked for even when `argv[0]` is an alternate name or the
rosetta binfmt_misc argv prepends the translator.
4. The entry point becomes `interp_entry + load_base`; the dynamic linker
takes over from there.
5. Guest absolute paths reach the host through `path_translate_at()`
Expand Down
9 changes: 5 additions & 4 deletions src/core/bootstrap.c
Original file line number Diff line number Diff line change
Expand Up @@ -627,7 +627,7 @@ int guest_bootstrap_prepare(guest_t *g,
boot->stack_pointer = build_linux_stack(
g, g->stack_top, stack_argc, stack_argv, (const char **) environ,
stack_elf, stack_elf_load_base, stack_interp_base, native_vdso,
rosetta_execfd, &auxv);
rosetta_execfd, elf_guest_path, &auxv);
if (boot->stack_pointer == 0) {
log_error("failed to build initial stack");
free(rosetta_argv);
Expand Down Expand Up @@ -881,9 +881,10 @@ int guest_bootstrap_rosetta_post_reset(guest_t *g,

uint64_t native_vdso = vdso_build(g);
linux_stack_auxv_t auxv;
uint64_t sp = build_linux_stack(g, g->stack_top, rosetta_argc, rosetta_argv,
(const char **) environ, &rr.rosetta_info,
0, 0, native_vdso, rosetta_execfd, &auxv);
uint64_t sp =
build_linux_stack(g, g->stack_top, rosetta_argc, rosetta_argv,
(const char **) environ, &rr.rosetta_info, 0, 0,
native_vdso, rosetta_execfd, elf_guest_path, &auxv);
free(rosetta_argv);
if (sp == 0) {
log_error("build_linux_stack failed during exec re-bootstrap");
Expand Down
33 changes: 31 additions & 2 deletions src/core/stack.c
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ uint64_t build_linux_stack(guest_t *g,
uint64_t interp_base,
uint64_t vdso_base,
int execfd,
const char *execfn,
linux_stack_auxv_t *auxv_out)
{
/* Linux initial stack layout (growing from high to low):
Expand Down Expand Up @@ -218,6 +219,31 @@ uint64_t build_linux_stack(guest_t *g,
uint64_t platform_ptr = str_ptr;
str_err |= write_str(g, platform_ptr, "aarch64");

/* AT_EXECFN: the filename handed to execve, copied onto the stack as its
* own string exactly as fs/binfmt_elf.c does.
*
* The kernel takes this from bprm->filename, not from argv[0], and the two
* diverge in two ways elfuse reproduces: execve(path, "altname", ...)
* reports path, and under binfmt_misc the interpreter rosetta.c prepends to
* argv is not the program the guest asked to run. Taking the string from
* the caller rather than from an argv index keeps that contract out of the
* argv layout, which differs between the native and rosetta forms and is
* free to change again (see the preserving-form note in rosetta.c).
*
* Guests that identify themselves through auxv rather than argv[0] depend
* on getting this right: rust-coreutils dispatches its multi-call applet
* from AT_EXECFN, so a leaked interpreter path makes every applet abort
* with "unknown program 'rosetta'".
*/
uint64_t execfn_ptr = 0;
if (execfn) {
size_t execfn_len = strlen(execfn) + 1;
if (!stack_take(&str_ptr, stack_floor, execfn_len))
return 0;
execfn_ptr = str_ptr;
str_err |= write_str(g, execfn_ptr, execfn);
}

/* Dynamically allocate pointer arrays to avoid stack buffer overflow with
* large argument or environment lists. calloc(0, ...) is
* implementation-defined, so always allocate at least one slot. The extra
Expand Down Expand Up @@ -251,8 +277,11 @@ uint64_t build_linux_stack(guest_t *g,
str_err |= write_str(g, str_ptr, argv[i]);
}

/* AT_EXECFN: pointer to argv[0] string (write it near the top) */
uint64_t execfn_ptr = (argc > 0) ? arg_ptrs[0] : 0;
/* Callers with no filename to report keep the historical argv[0] spelling
* rather than an AT_EXECFN of 0, which no Linux process ever sees.
*/
if (!execfn_ptr && argc > 0)
execfn_ptr = arg_ptrs[0];

/* Phase 2: Build the structured part of the stack. Align str_ptr down to 16
* bytes first.
Expand Down
6 changes: 6 additions & 0 deletions src/core/stack.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@ typedef struct {
* ET_EXEC). interp_base is the load base of the dynamic linker (0 if statically
* linked). vdso_base is the guest address of the vDSO ELF image (0 if no vDSO).
* execfd is the pre-opened binary fd for binfmt_misc (AT_EXECFD); -1 if none.
* execfn is the guest path handed to execve, reported as AT_EXECFN. Linux takes
* that string from the execve filename rather than from argv[0], and the two
* differ whenever the caller passed an alternate argv[0] or the kernel
* prepended a binfmt_misc interpreter, so callers pass it explicitly instead of
* leaving it to be inferred from argv's shape. NULL falls back to argv[0].
* If auxv_out is non-NULL, it receives the exact auxv words written to guest
* memory, in the same order exposed by /proc/self/auxv.
* Returns the initial SP (stack pointer) to pass to the guest.
Expand All @@ -68,4 +73,5 @@ uint64_t build_linux_stack(guest_t *g,
uint64_t interp_base,
uint64_t vdso_base,
int execfd,
const char *execfn,
linux_stack_auxv_t *auxv_out);
7 changes: 6 additions & 1 deletion src/syscall/exec.c
Original file line number Diff line number Diff line change
Expand Up @@ -1545,9 +1545,14 @@ int64_t sys_execve(hv_vcpu_t vcpu,
uint64_t exec_vdso = vdso_build(g);
exec_republish_shim_globals_or_die(vcpu, g, verbose);

/* AT_EXECFN gets the same guest-visible spelling published to
* /proc/self/exe below, so the two surfaces agree on what this process
* is. Passing path rather than argv_const[0] also matches Linux for
* execve(path, ["altname"], ...), where the kernel reports path.
*/
sp = build_linux_stack(g, g->stack_top, argc, argv_const, envp_const,
&elf_info, elf_load_base, interp_base, exec_vdso,
-1 /* no AT_EXECFD */, &auxv);
-1 /* no AT_EXECFD */, path, &auxv);

/* 0 is build_linux_stack's failure return. Past the point of no return
* there is no image to go back to, and programming SP_EL0 from it would
Expand Down
Loading