summaryrefslogtreecommitdiff
path: root/linux-user/qemu.h
diff options
context:
space:
mode:
authorPeter Maydell <peter.maydell@linaro.org>2016-05-27 15:51:49 +0100
committerRiku Voipio <riku.voipio@linaro.org>2016-06-07 16:39:07 +0300
commit3d3efba020da1de57a715e2087cf761ed0ad0904 (patch)
tree915076847494f60b5ab922f781069a4afedaf696 /linux-user/qemu.h
parent2fe4fba115b5b9f7e6722720c57810e0fc64b9b5 (diff)
downloadqemu-3d3efba020da1de57a715e2087cf761ed0ad0904.tar.gz
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/qemu.h')
-rw-r--r--linux-user/qemu.h50
1 files changed, 48 insertions, 2 deletions
diff --git a/linux-user/qemu.h b/linux-user/qemu.h
index f09b750bbf..5138289bfb 100644
--- a/linux-user/qemu.h
+++ b/linux-user/qemu.h
@@ -123,14 +123,33 @@ typedef struct TaskState {
#endif
uint32_t stack_base;
int used; /* non zero if used */
- bool sigsegv_blocked; /* SIGSEGV blocked by guest */
struct image_info *info;
struct linux_binprm *bprm;
struct emulated_sigtable sigtab[TARGET_NSIG];
struct sigqueue sigqueue_table[MAX_SIGQUEUE_SIZE]; /* siginfo queue */
struct sigqueue *first_free; /* first free siginfo queue entry */
- int signal_pending; /* non zero if a signal may be pending */
+ /* This thread's signal mask, as requested by the guest program.
+ * The actual signal mask of this thread may differ:
+ * + we don't let SIGSEGV and SIGBUS be blocked while running guest code
+ * + sometimes we block all signals to avoid races
+ */
+ sigset_t signal_mask;
+ /* The signal mask imposed by a guest sigsuspend syscall, if we are
+ * currently in the middle of such a syscall
+ */
+ sigset_t sigsuspend_mask;
+ /* Nonzero if we're leaving a sigsuspend and sigsuspend_mask is valid. */
+ int in_sigsuspend;
+
+ /* Nonzero if process_pending_signals() needs to do something (either
+ * handle a pending signal or unblock signals).
+ * This flag is written from a signal handler so should be accessed via
+ * the atomic_read() and atomic_write() functions. (It is not accessed
+ * from multiple threads.)
+ */
+ int signal_pending;
+
} __attribute__((aligned(16))) TaskState;
extern char *exec_path;
@@ -235,6 +254,12 @@ unsigned long init_guest_space(unsigned long host_start,
* It's also OK to implement these with safe_syscall, though it will be
* a little less efficient if a signal is delivered at the 'wrong' moment.
*
+ * Some non-interruptible syscalls need to be handled using block_signals()
+ * to block signals for the duration of the syscall. This mainly applies
+ * to code which needs to modify the data structures used by the
+ * host_signal_handler() function and the functions it calls, including
+ * all syscalls which change the thread's signal mask.
+ *
* (2) Interruptible syscalls
*
* These are guest syscalls that can be interrupted by signals and
@@ -266,6 +291,8 @@ unsigned long init_guest_space(unsigned long host_start,
* you make in the implementation returns either -TARGET_ERESTARTSYS or
* EINTR though.)
*
+ * block_signals() cannot be used for interruptible syscalls.
+ *
*
* How and why the safe_syscall implementation works:
*
@@ -352,6 +379,25 @@ long do_sigreturn(CPUArchState *env);
long do_rt_sigreturn(CPUArchState *env);
abi_long do_sigaltstack(abi_ulong uss_addr, abi_ulong uoss_addr, abi_ulong sp);
int do_sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
+/**
+ * block_signals: block all signals while handling this guest syscall
+ *
+ * Block all signals, and arrange that the signal mask is returned to
+ * its correct value for the guest before we resume execution of guest code.
+ * If this function returns non-zero, then the caller should immediately
+ * return -TARGET_ERESTARTSYS to the main loop, which will take the pending
+ * signal and restart execution of the syscall.
+ * If block_signals() returns zero, then the caller can continue with
+ * emulation of the system call knowing that no signals can be taken
+ * (and therefore that no race conditions will result).
+ * This should only be called once, because if it is called a second time
+ * it will always return non-zero. (Think of it like a mutex that can't
+ * be recursively locked.)
+ * Signals will be unblocked again by process_pending_signals().
+ *
+ * Return value: non-zero if there was a pending signal, zero if not.
+ */
+int block_signals(void); /* Returns non zero if signal pending */
#ifdef TARGET_I386
/* vm86.c */