diff --git a/linux-user/syscall.c b/linux-user/syscall.c index d65e4930f6..0e9e875be7 100644 --- a/linux-user/syscall.c +++ b/linux-user/syscall.c @@ -10281,7 +10281,38 @@ static inline abi_long copy_to_user_flock64(abi_ulong target_flock_addr, return 0; } -static abi_long do_fcntl(int fd, int cmd, abi_ulong arg) +#ifdef TARGET_I386 +/* + * The executable identity fd belongs to the translator, not to the guest fd + * table. If a guest operation would mutate or destroy that descriptor, + * move the identity reference first and let the operation keep its normal + * result on the old descriptor number. + */ +static abi_long guest_exe_identity_relocate(CPUArchState *env, int fd) +{ + TaskState *ts = env_cpu(env)->opaque; + int new_fd = -1; + abi_long ret = 0; + + mmap_lock(); + if (ts->info->prctl_mm_exe_fd == fd) { + new_fd = fcntl(fd, F_DUPFD_CLOEXEC, 0); + if (new_fd < 0) { + ret = get_errno(-1); + } else { + ts->info->prctl_mm_exe_fd = new_fd; + } + } + mmap_unlock(); + + if (new_fd >= 0) { + fd_trans_unregister(new_fd); + } + return ret; +} +#endif + +static abi_long do_fcntl(CPUArchState *env, int fd, int cmd, abi_ulong arg) { struct flock64 fl64; #ifdef F_GETOWN_EX @@ -10294,6 +10325,17 @@ static abi_long do_fcntl(int fd, int cmd, abi_ulong arg) if (host_cmd == -TARGET_EINVAL) return host_cmd; +#ifdef TARGET_I386 + /* The identity copy is already close-on-exec. Setting that same bit is + * harmless; only clearing it would mutate the translator's reference. */ + if (cmd == TARGET_F_SETFD && !(arg & FD_CLOEXEC)) { + ret = guest_exe_identity_relocate(env, fd); + if (ret) { + return ret; + } + } +#endif + switch(cmd) { case TARGET_F_GETLK: ret = copy_from_user_flock(&fl64, arg); @@ -14408,6 +14450,12 @@ static abi_long do_syscall1(void *cpu_env, int num, abi_long arg1, return ret; #endif case TARGET_NR_close: +#ifdef TARGET_I386 + ret = guest_exe_identity_relocate(cpu_env, arg1); + if (ret) { + return ret; + } +#endif fd_trans_unregister(arg1); return get_errno(close(arg1)); @@ -15575,7 +15623,7 @@ static abi_long do_syscall1(void *cpu_env, int num, abi_long arg1, return ret; #ifdef TARGET_NR_fcntl case TARGET_NR_fcntl: - return do_fcntl(arg1, arg2, arg3); + return do_fcntl(cpu_env, arg1, arg2, arg3); #endif case TARGET_NR_setpgid: return get_errno(setpgid(arg1, arg2)); @@ -15589,6 +15637,14 @@ static abi_long do_syscall1(void *cpu_env, int num, abi_long arg1, return ret; #ifdef TARGET_NR_dup2 case TARGET_NR_dup2: +#ifdef TARGET_I386 + if (arg1 != arg2) { + ret = guest_exe_identity_relocate(cpu_env, arg2); + if (ret) { + return ret; + } + } +#endif ret = get_errno(dup2(arg1, arg2)); if (ret >= 0) { fd_trans_dup(arg1, arg2); @@ -15604,6 +15660,14 @@ static abi_long do_syscall1(void *cpu_env, int num, abi_long arg1, return -EINVAL; } host_flags = target_to_host_bitmask(arg3, fcntl_flags_tbl); +#ifdef TARGET_I386 + if (arg1 != arg2) { + ret = guest_exe_identity_relocate(cpu_env, arg2); + if (ret) { + return ret; + } + } +#endif ret = get_errno(dup3(arg1, arg2, host_flags)); if (ret >= 0) { fd_trans_dup(arg1, arg2); @@ -19273,7 +19337,7 @@ static abi_long do_syscall1(void *cpu_env, int num, abi_long arg1, ret = get_errno(safe_fcntl(arg1, cmd, &fl)); break; default: - ret = do_fcntl(arg1, arg2, arg3); + ret = do_fcntl(cpu_env, arg1, arg2, arg3); break; } return ret; diff --git a/tests/integration/prctl-x86-semantics.c b/tests/integration/prctl-x86-semantics.c index 93c7eed59d..c0703bcb82 100644 --- a/tests/integration/prctl-x86-semantics.c +++ b/tests/integration/prctl-x86-semantics.c @@ -10,7 +10,9 @@ typedef long guest_slong_t; #define __NR_read 0 #define __NR_write 1 #define __NR_close 3 +#define __NR_dup2 33 #define __NR_execve 59 +#define __NR_fcntl 72 #define __NR_mmap 9 #define __NR_mprotect 10 #define __NR_munmap 11 @@ -33,6 +35,7 @@ typedef long guest_slong_t; #define __NR_openat 257 #define __NR_memfd_create 319 #define __NR_readlinkat 267 +#define __NR_dup3 292 #define __NR_execveat 322 #define __NR_readlink 89 #define __NR_rename 82 @@ -66,6 +69,10 @@ typedef long guest_slong_t; #define AT_EMPTY_PATH 0x1000 #define O_DIRECTORY 00200000 +#define F_GETFD 1 +#define F_SETFD 2 +#define FD_CLOEXEC 1 + #define IPC_PRIVATE 0 #define IPC_CREAT 01000 #define IPC_RMID 0 @@ -282,6 +289,34 @@ static guest_ulong_t string_length(const char *value) return len; } +static int proc_fd_path(char *buffer, guest_ulong_t size, unsigned int fd) +{ + static const char prefix[] = "/proc/self/fd/"; + char digits[16]; + guest_ulong_t pos = 0; + unsigned int count = 0; + + if (size < sizeof(prefix) + 1) { + return 0; + } + while (pos < sizeof(prefix) - 1) { + buffer[pos] = prefix[pos]; + pos++; + } + do { + digits[count++] = '0' + fd % 10; + fd /= 10; + } while (fd && count < sizeof(digits)); + if (pos + count >= size) { + return 0; + } + while (count) { + buffer[pos++] = digits[--count]; + } + buffer[pos] = 0; + return 1; +} + static int contains(const char *buf, guest_ulong_t len, const char *needle, guest_ulong_t needle_len) { @@ -1548,6 +1583,81 @@ static int test_exec_race(const char *target_path, int use_execveat) return 135; } +static int find_exe_identity_fd(const char *self_path) +{ + char fd_path[32]; + char link_path[4096]; + guest_ulong_t self_len = string_length(self_path); + unsigned int fd; + + for (fd = 3; fd < 1024; fd++) { + guest_slong_t flags; + guest_slong_t len; + + if (!proc_fd_path(fd_path, sizeof(fd_path), fd)) { + return -1; + } + len = syscall3(__NR_readlink, (guest_slong_t)fd_path, + (guest_slong_t)link_path, sizeof(link_path)); + if (len != (guest_slong_t)self_len || + !bytes_equal(link_path, self_path, self_len)) { + continue; + } + flags = syscall3(__NR_fcntl, fd, F_GETFD, 0); + if (flags >= 0 && (flags & FD_CLOEXEC)) { + return fd; + } + } + return -1; +} + +static int test_exe_identity_mutation(const char *self_path, char mode, + int child) +{ + static char exe_path[] = "/proc/self/exe"; + static char null_path[] = "/dev/null"; + static char child_mode[] = "child"; + char mode_arg[] = { mode, 0 }; + char *argv[] = { exe_path, mode_arg, child_mode, 0 }; + guest_slong_t fd; + guest_slong_t null_fd; + guest_slong_t ret; + + if (child) { + return 0; + } + if (!self_path) { + return 136; + } + fd = find_exe_identity_fd(self_path); + if (fd < 0) { + return 136; + } + if (mode == 'q') { + ret = syscall1(__NR_close, fd); + } else if (mode == 'o') { + ret = syscall3(__NR_fcntl, fd, F_SETFD, 0); + } else { + null_fd = syscall4(__NR_openat, AT_FDCWD, + (guest_slong_t)null_path, 0, 0); + if (null_fd < 0) { + return 137; + } + if (mode == 'n') { + ret = syscall2(__NR_dup2, null_fd, fd); + } else { + ret = syscall3(__NR_dup3, null_fd, fd, 0); + } + syscall1(__NR_close, null_fd); + } + if (ret < 0) { + return 137; + } + syscall3(__NR_execve, (guest_slong_t)exe_path, + (guest_slong_t)argv, 0); + return 138; +} + int test_main(guest_ulong_t *stack) { guest_ulong_t argc = stack[0]; @@ -1609,6 +1719,10 @@ int test_main(guest_ulong_t *stack) if (mode == 'u') { return test_exec_race(argc == 3 ? argv[2] : 0, 1); } + if (mode == 'q' || mode == 'o' || mode == 'n' || mode == 'j') { + return test_exe_identity_mutation( + argv[0], mode, argc == 3 && argv[2][0] == 'c'); + } return 101; } diff --git a/tests/integration/test-prctl-x86-semantics.sh b/tests/integration/test-prctl-x86-semantics.sh index 80473d1142..bc7522c213 100755 --- a/tests/integration/test-prctl-x86-semantics.sh +++ b/tests/integration/test-prctl-x86-semantics.sh @@ -64,3 +64,7 @@ run_case i run_case e "$workdir/prctl-native-env-helper" run_case a "$workdir/prctl-native-env-helper" run_case d "$workdir/relative" +run_case q +run_case o +run_case n +run_case j