linux-user: Fix AT_EXECFN in AUXV for symlinked programs
The AT_EXECFN entry in AUXV needs to keep the value which was used when the program was started. Especially for symlinked programs qemu should not try to resolve the realpath. Here is a reproducer: (arm64-chroot)root@p100:/# cd /usr/bin (arm64-chroot)root@p100:/usr/bin# ln -s echo testprog (arm64-chroot)root@p100:/usr/bin# LD_SHOW_AUXV=1 ./testprog | grep AT_EXECFN AT_EXECFN: ./testprog In this example, "./testprog" is the correct output, and not "/usr/bin/echo". This patch fixes parts of commit 258bec39 ("linux-user: Fix access to /proc/self/exe"). Fixes: 258bec39 ("linux-user: Fix access to /proc/self/exe") Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/3379 Signed-off-by: Helge Deller <deller@gmx.de>
Helge Deller committed
May 1, 2026 at 12:56 UTC
6b5aef7cac9dab7c16451588cff6615eb2048293
3 files changed
+12
-9
linux-user/main.c
+4
-2
@@ -772,8 +772,10 @@ int main(int argc, char **argv, char **envp)
772
}
773
774
/* Resolve executable file name to full path name */
775
- if (realpath(exec_path, real_exec_path)) {
776
- exec_path = real_exec_path;
775
+ /* Keep how we started the program in exec_path, e.g. "./my_program" */
776
+ /* Store real path in real_exec_path, e.g. "/usr/local/bin/my_program" */
777
+ if (!realpath(exec_path, real_exec_path)) {
778
+ printf("Could not resolve %s\n", exec_path);
779
}
780
781
/*
linux-user/syscall.c
+7
-7
@@ -8790,9 +8790,9 @@ static int maybe_do_fake_open(CPUArchState *cpu_env, int dirfd,
8790
return -1;
8791
}
8792
if (safe) {
8793
- return safe_openat(dirfd, exec_path, flags, mode);
8793
+ return safe_openat(dirfd, real_exec_path, flags, mode);
8794
} else {
8795
- return openat(dirfd, exec_path, flags, mode);
8795
+ return openat(dirfd, real_exec_path, flags, mode);
8796
}
8797
}
8798
@@ -8929,9 +8929,9 @@ ssize_t do_guest_readlink(const char *pathname, char *buf, size_t bufsiz)
8929
* Don't worry about sign mismatch as earlier mapping
8930
* logic would have thrown a bad address error.
8931
*/
8932
- ret = MIN(strlen(exec_path), bufsiz);
8932
+ ret = MIN(strlen(real_exec_path), bufsiz);
8933
/* We cannot NUL terminate the string. */
8934
- memcpy(buf, exec_path, ret);
8934
+ memcpy(buf, real_exec_path, ret);
8935
} else {
8936
ret = readlink(path(pathname), buf, bufsiz);
8937
}
@@ -9022,7 +9022,7 @@ static int do_execv(CPUArchState *cpu_env, int dirfd,
9022
9023
const char *exe = p;
9024
if (is_proc_myself(p, "exe")) {
9025
- exe = exec_path;
9025
+ exe = real_exec_path;
9026
}
9027
ret = is_execveat
9028
? safe_execveat(dirfd, exe, argp, envp, flags)
@@ -11033,9 +11033,9 @@ static abi_long do_syscall1(CPUArchState *cpu_env, int num, abi_long arg1,
11033
* Don't worry about sign mismatch as earlier mapping
11034
* logic would have thrown a bad address error.
11035
*/
11036
- ret = MIN(strlen(exec_path), arg4);
11036
+ ret = MIN(strlen(real_exec_path), arg4);
11037
/* We cannot NUL terminate the string. */
11038
- memcpy(p2, exec_path, ret);
11038
+ memcpy(p2, real_exec_path, ret);
11039
} else {
11040
ret = get_errno(readlinkat(arg1, path(p), p2, arg4));
11041
}
linux-user/user-internals.h
+1
@@ -24,6 +24,7 @@
24
#include "exec/translation-block.h"
25
26
extern char *exec_path;
27
+extern char real_exec_path[PATH_MAX];
28
void init_task_state(TaskState *ts);
29
void task_settid(TaskState *);
30
void stop_all_tasks(void);