linux-user/syscall.c | 45 +++++++++++++++++++++++--------------------- 1 file changed, 24 insertions(+), 21 deletions(-)
The Linux kernel writes back the remaining timeout for select-family
syscalls in poll_select_finish(). If that writeback fails, it keeps
the original return value.
However, QEMU only writes back the timeout on success. If the writeback
fails, QEMU returns -TARGET_EFAULT. This can lose the remaining
timeout and change the return value.
Update do_select(), do_pselect6(), and do_ppoll() to always write back
the timeout to match the Linux kernel's behavior. If the timeout
writeback fails, keep the original return value.
Tested with the issue reproducer.
Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/3343
Signed-off-by: Sun Haoyu <shyliuli@aosc.io>
---
Changes since v2:
- move the Resolves: tag above Signed-off-by
- ignore timeout writeback failures directly(to match kernel) and add comments
- fix indentation in do_pselect6()
linux-user/syscall.c | 45 +++++++++++++++++++++++---------------------
1 file changed, 24 insertions(+), 21 deletions(-)
diff --git a/linux-user/syscall.c b/linux-user/syscall.c
index 064bc604c9..0a0c2fa1eb 100644
--- a/linux-user/syscall.c
+++ b/linux-user/syscall.c
@@ -1394,14 +1394,15 @@ static abi_long do_select(int n,
return -TARGET_EFAULT;
if (efd_addr && copy_to_user_fdset(efd_addr, &efds, n))
return -TARGET_EFAULT;
-
- if (target_tv_addr) {
- tv.tv_sec = ts.tv_sec;
- tv.tv_usec = ts.tv_nsec / 1000;
- if (copy_to_user_timeval(target_tv_addr, &tv)) {
- return -TARGET_EFAULT;
- }
- }
+ }
+ if (target_tv_addr) {
+ tv.tv_sec = ts.tv_sec;
+ tv.tv_usec = ts.tv_nsec / 1000;
+ /*
+ * Like the kernel, we deliberately ignore possible
+ * failures writing back to the timeout struct.
+ */
+ copy_to_user_timeval(target_tv_addr, &tv);
}
return ret;
@@ -1529,14 +1530,16 @@ static abi_long do_pselect6(abi_long arg1, abi_long arg2, abi_long arg3,
if (efd_addr && copy_to_user_fdset(efd_addr, &efds, n)) {
return -TARGET_EFAULT;
}
+ }
+ if (ts_addr) {
+ /*
+ * Like the kernel, we deliberately ignore possible
+ * failures writing back to the timeout struct.
+ */
if (time64) {
- if (ts_addr && host_to_target_timespec64(ts_addr, &ts)) {
- return -TARGET_EFAULT;
- }
+ host_to_target_timespec64(ts_addr, &ts);
} else {
- if (ts_addr && host_to_target_timespec(ts_addr, &ts)) {
- return -TARGET_EFAULT;
- }
+ host_to_target_timespec(ts_addr, &ts);
}
}
return ret;
@@ -1606,15 +1609,15 @@ static abi_long do_ppoll(abi_long arg1, abi_long arg2, abi_long arg3,
if (set) {
finish_sigsuspend_mask(ret);
}
- if (!is_error(ret) && arg3) {
+ if (arg3) {
+ /*
+ * Like the kernel, we deliberately ignore possible
+ * failures writing back to the timeout struct.
+ */
if (time64) {
- if (host_to_target_timespec64(arg3, timeout_ts)) {
- return -TARGET_EFAULT;
- }
+ host_to_target_timespec64(arg3, timeout_ts);
} else {
- if (host_to_target_timespec(arg3, timeout_ts)) {
- return -TARGET_EFAULT;
- }
+ host_to_target_timespec(arg3, timeout_ts);
}
}
} else {
--
2.53.0
On Fri, 20 Mar 2026 at 11:17, Sun Haoyu <shyliuli@aosc.io> wrote: > > The Linux kernel writes back the remaining timeout for select-family > syscalls in poll_select_finish(). If that writeback fails, it keeps > the original return value. > > However, QEMU only writes back the timeout on success. If the writeback > fails, QEMU returns -TARGET_EFAULT. This can lose the remaining > timeout and change the return value. > > Update do_select(), do_pselect6(), and do_ppoll() to always write back > the timeout to match the Linux kernel's behavior. If the timeout > writeback fails, keep the original return value. > > Tested with the issue reproducer. > > Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/3343 > > Signed-off-by: Sun Haoyu <shyliuli@aosc.io> > --- Reviewed-by: Peter Maydell <peter.maydell@linaro.org> thanks -- PMM
© 2016 - 2026 Red Hat, Inc.