diff --git a/docs/internals.md b/docs/internals.md index b88f9b76..a023e230 100644 --- a/docs/internals.md +++ b/docs/internals.md @@ -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()` diff --git a/src/core/bootstrap.c b/src/core/bootstrap.c index 6e79c161..985d1a99 100644 --- a/src/core/bootstrap.c +++ b/src/core/bootstrap.c @@ -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); @@ -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"); diff --git a/src/core/stack.c b/src/core/stack.c index 4a93c5b6..61642d7f 100644 --- a/src/core/stack.c +++ b/src/core/stack.c @@ -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): @@ -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 @@ -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. diff --git a/src/core/stack.h b/src/core/stack.h index e7a60767..f2e1cf9d 100644 --- a/src/core/stack.h +++ b/src/core/stack.h @@ -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. @@ -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); diff --git a/src/syscall/exec.c b/src/syscall/exec.c index 1d8b343b..8bced5da 100644 --- a/src/syscall/exec.c +++ b/src/syscall/exec.c @@ -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