summaryrefslogtreecommitdiffstats
path: root/linux-user/syscall.c
diff options
context:
space:
mode:
authorPeter Maydell2016-05-27 16:51:49 +0200
committerRiku Voipio2016-06-07 15:39:07 +0200
commit3d3efba020da1de57a715e2087cf761ed0ad0904 (patch)
tree915076847494f60b5ab922f781069a4afedaf696 /linux-user/syscall.c
parentlinux-user: Use safe_syscall for sigsuspend syscalls (diff)
downloadqemu-3d3efba020da1de57a715e2087cf761ed0ad0904.tar.gz
qemu-3d3efba020da1de57a715e2087cf761ed0ad0904.tar.xz
qemu-3d3efba020da1de57a715e2087cf761ed0ad0904.zip
linux-user: Fix race between multiple signals
If multiple host signals are received in quick succession they would be queued in TaskState then delivered to the guest in spite of signals being supposed to be blocked by the guest signal handler's sa_mask. Fix this by decoupling the guest signal mask from the host signal mask, so we can have protected sections where all host signals are blocked. In particular we block signals from when host_signal_handler() queues a signal from the guest until process_pending_signals() has unqueued it. We also block signals while we are manipulating the guest signal mask in emulation of sigprocmask and similar syscalls. Blocking host signals also ensures the correct behaviour with respect to multiple threads and the overrun count of timer related signals. Alas blocking and queuing in qemu is still needed because of virtual processor exceptions, SIGSEGV and SIGBUS. Blocking signals inside process_pending_signals() protects against concurrency problems that would otherwise happen if host_signal_handler() ran and accessed the signal data structures while process_pending_signals() was manipulating them. Since we now track the guest signal mask separately from that of the host, the sigsuspend system calls must track the signal mask passed to them, because when we process signals as we leave the sigsuspend the guest signal mask in force is that passed to sigsuspend. Signed-off-by: Timothy Edward Baldwin <T.E.Baldwin99@members.leeds.ac.uk> Message-id: 1441497448-32489-19-git-send-email-T.E.Baldwin99@members.leeds.ac.uk [PMM: make signal_pending a simple flag rather than a word with two flag bits; ensure we don't call block_signals() twice in sigreturn codepaths; document and assert() the guarantee that using do_sigprocmask() to get the current mask never fails; use the qemu atomics.h functions rather than raw volatile variable access; add extra commentary and documentation; block SIGSEGV/SIGBUS in block_signals() and in process_pending_signals() because they can't occur synchronously here; check the right do_sigprocmask() call for errors in ssetmask syscall; expand commit message; fixed sigsuspend() hanging] Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Signed-off-by: Riku Voipio <riku.voipio@linaro.org>
Diffstat (limited to 'linux-user/syscall.c')
-rw-r--r--linux-user/syscall.c73
1 files changed, 51 insertions, 22 deletions
diff --git a/linux-user/syscall.c b/linux-user/syscall.c
index 5bdfe2a936..639b328c74 100644
--- a/linux-user/syscall.c
+++ b/linux-user/syscall.c
@@ -5387,6 +5387,7 @@ static int do_fork(CPUArchState *env, unsigned int flags, abi_ulong newsp,
new_cpu->opaque = ts;
ts->bprm = parent_ts->bprm;
ts->info = parent_ts->info;
+ ts->signal_mask = parent_ts->signal_mask;
nptl_flags = flags;
flags &= ~CLONE_NPTL_FLAGS2;
@@ -7482,9 +7483,11 @@ abi_long do_syscall(void *cpu_env, int num, abi_long arg1,
{
sigset_t cur_set;
abi_ulong target_set;
- do_sigprocmask(0, NULL, &cur_set);
- host_to_target_old_sigset(&target_set, &cur_set);
- ret = target_set;
+ ret = do_sigprocmask(0, NULL, &cur_set);
+ if (!ret) {
+ host_to_target_old_sigset(&target_set, &cur_set);
+ ret = target_set;
+ }
}
break;
#endif
@@ -7493,12 +7496,20 @@ abi_long do_syscall(void *cpu_env, int num, abi_long arg1,
{
sigset_t set, oset, cur_set;
abi_ulong target_set = arg1;
- do_sigprocmask(0, NULL, &cur_set);
+ /* We only have one word of the new mask so we must read
+ * the rest of it with do_sigprocmask() and OR in this word.
+ * We are guaranteed that a do_sigprocmask() that only queries
+ * the signal mask will not fail.
+ */
+ ret = do_sigprocmask(0, NULL, &cur_set);
+ assert(!ret);
target_to_host_old_sigset(&set, &target_set);
sigorset(&set, &set, &cur_set);
- do_sigprocmask(SIG_SETMASK, &set, &oset);
- host_to_target_old_sigset(&target_set, &oset);
- ret = target_set;
+ ret = do_sigprocmask(SIG_SETMASK, &set, &oset);
+ if (!ret) {
+ host_to_target_old_sigset(&target_set, &oset);
+ ret = target_set;
+ }
}
break;
#endif
@@ -7527,7 +7538,7 @@ abi_long do_syscall(void *cpu_env, int num, abi_long arg1,
mask = arg2;
target_to_host_old_sigset(&set, &mask);
- ret = get_errno(do_sigprocmask(how, &set, &oldset));
+ ret = do_sigprocmask(how, &set, &oldset);
if (!is_error(ret)) {
host_to_target_old_sigset(&mask, &oldset);
ret = mask;
@@ -7561,7 +7572,7 @@ abi_long do_syscall(void *cpu_env, int num, abi_long arg1,
how = 0;
set_ptr = NULL;
}
- ret = get_errno(do_sigprocmask(how, set_ptr, &oldset));
+ ret = do_sigprocmask(how, set_ptr, &oldset);
if (!is_error(ret) && arg3) {
if (!(p = lock_user(VERIFY_WRITE, arg3, sizeof(target_sigset_t), 0)))
goto efault;
@@ -7601,7 +7612,7 @@ abi_long do_syscall(void *cpu_env, int num, abi_long arg1,
how = 0;
set_ptr = NULL;
}
- ret = get_errno(do_sigprocmask(how, set_ptr, &oldset));
+ ret = do_sigprocmask(how, set_ptr, &oldset);
if (!is_error(ret) && arg3) {
if (!(p = lock_user(VERIFY_WRITE, arg3, sizeof(target_sigset_t), 0)))
goto efault;
@@ -7639,28 +7650,36 @@ abi_long do_syscall(void *cpu_env, int num, abi_long arg1,
#ifdef TARGET_NR_sigsuspend
case TARGET_NR_sigsuspend:
{
- sigset_t set;
+ TaskState *ts = cpu->opaque;
#if defined(TARGET_ALPHA)
abi_ulong mask = arg1;
- target_to_host_old_sigset(&set, &mask);
+ target_to_host_old_sigset(&ts->sigsuspend_mask, &mask);
#else
if (!(p = lock_user(VERIFY_READ, arg1, sizeof(target_sigset_t), 1)))
goto efault;
- target_to_host_old_sigset(&set, p);
+ target_to_host_old_sigset(&ts->sigsuspend_mask, p);
unlock_user(p, arg1, 0);
#endif
- ret = get_errno(safe_rt_sigsuspend(&set, SIGSET_T_SIZE));
+ ret = get_errno(safe_rt_sigsuspend(&ts->sigsuspend_mask,
+ SIGSET_T_SIZE));
+ if (ret != -TARGET_ERESTARTSYS) {
+ ts->in_sigsuspend = 1;
+ }
}
break;
#endif
case TARGET_NR_rt_sigsuspend:
{
- sigset_t set;
+ TaskState *ts = cpu->opaque;
if (!(p = lock_user(VERIFY_READ, arg1, sizeof(target_sigset_t), 1)))
goto efault;
- target_to_host_sigset(&set, p);
+ target_to_host_sigset(&ts->sigsuspend_mask, p);
unlock_user(p, arg1, 0);
- ret = get_errno(safe_rt_sigsuspend(&set, SIGSET_T_SIZE));
+ ret = get_errno(safe_rt_sigsuspend(&ts->sigsuspend_mask,
+ SIGSET_T_SIZE));
+ if (ret != -TARGET_ERESTARTSYS) {
+ ts->in_sigsuspend = 1;
+ }
}
break;
case TARGET_NR_rt_sigtimedwait:
@@ -7706,11 +7725,19 @@ abi_long do_syscall(void *cpu_env, int num, abi_long arg1,
break;
#ifdef TARGET_NR_sigreturn
case TARGET_NR_sigreturn:
- ret = do_sigreturn(cpu_env);
+ if (block_signals()) {
+ ret = -TARGET_ERESTARTSYS;
+ } else {
+ ret = do_sigreturn(cpu_env);
+ }
break;
#endif
case TARGET_NR_rt_sigreturn:
- ret = do_rt_sigreturn(cpu_env);
+ if (block_signals()) {
+ ret = -TARGET_ERESTARTSYS;
+ } else {
+ ret = do_rt_sigreturn(cpu_env);
+ }
break;
case TARGET_NR_sethostname:
if (!(p = lock_user_string(arg1)))
@@ -9764,9 +9791,11 @@ abi_long do_syscall(void *cpu_env, int num, abi_long arg1,
}
mask = arg2;
target_to_host_old_sigset(&set, &mask);
- do_sigprocmask(how, &set, &oldset);
- host_to_target_old_sigset(&mask, &oldset);
- ret = mask;
+ ret = do_sigprocmask(how, &set, &oldset);
+ if (!ret) {
+ host_to_target_old_sigset(&mask, &oldset);
+ ret = mask;
+ }
}
break;
#endif