diff options
author | Oleg Nesterov <oleg@redhat.com> | 2011-04-27 14:59:41 -0400 |
---|---|---|
committer | Oleg Nesterov <oleg@redhat.com> | 2011-04-28 07:01:37 -0400 |
commit | e6fa16ab9c1e9b344428e6fea4d29e3cc4b28fb0 (patch) | |
tree | c1bdacc0537213cba8ba75a63ac286e21c0a7250 /kernel/signal.c | |
parent | 73ef4aeb61b53fce464a7e24ef03a26f98b2f617 (diff) |
signal: sigprocmask() should do retarget_shared_pending()
In short, almost every changing of current->blocked is wrong, or at least
can lead to the unexpected results.
For example. Two threads T1 and T2, T1 sleeps in sigtimedwait/pause/etc.
kill(tgid, SIG) can pick T2 for TIF_SIGPENDING. If T2 calls sigprocmask()
and blocks SIG before it notices the pending signal, nobody else can handle
this pending shared signal.
I am not sure this is bug, but at least this looks strange imho. T1 should
not sleep forever, there is a signal which should wake it up.
This patch moves the code which actually changes ->blocked into the new
helper, set_current_blocked() and changes this code to call
retarget_shared_pending() as exit_signals() does. We should only care about
the signals we just blocked, we use "newset & ~current->blocked" as a mask.
We do not check !sigisemptyset(newblocked), retarget_shared_pending() is
cheap unless mask & shared_pending.
Note: for this particular case we could simply change sigprocmask() to
return -EINTR if signal_pending(), but then we should change other callers
and, more importantly, if we need this fix then set_current_blocked() will
have more callers and some of them can't restart. See the next patch as a
random example.
Signed-off-by: Oleg Nesterov <oleg@redhat.com>
Reviewed-by: Matt Fleming <matt.fleming@linux.intel.com>
Acked-by: Tejun Heo <tj@kernel.org>
Diffstat (limited to 'kernel/signal.c')
-rw-r--r-- | kernel/signal.c | 29 |
1 files changed, 24 insertions, 5 deletions
diff --git a/kernel/signal.c b/kernel/signal.c index e8308e3238c1..8aa3a2e226af 100644 --- a/kernel/signal.c +++ b/kernel/signal.c | |||
@@ -2299,6 +2299,29 @@ long do_no_restart_syscall(struct restart_block *param) | |||
2299 | return -EINTR; | 2299 | return -EINTR; |
2300 | } | 2300 | } |
2301 | 2301 | ||
2302 | /** | ||
2303 | * set_current_blocked - change current->blocked mask | ||
2304 | * @newset: new mask | ||
2305 | * | ||
2306 | * It is wrong to change ->blocked directly, this helper should be used | ||
2307 | * to ensure the process can't miss a shared signal we are going to block. | ||
2308 | */ | ||
2309 | void set_current_blocked(const sigset_t *newset) | ||
2310 | { | ||
2311 | struct task_struct *tsk = current; | ||
2312 | |||
2313 | spin_lock_irq(&tsk->sighand->siglock); | ||
2314 | if (signal_pending(tsk) && !thread_group_empty(tsk)) { | ||
2315 | sigset_t newblocked; | ||
2316 | /* A set of now blocked but previously unblocked signals. */ | ||
2317 | signandsets(&newblocked, newset, ¤t->blocked); | ||
2318 | retarget_shared_pending(tsk, &newblocked); | ||
2319 | } | ||
2320 | tsk->blocked = *newset; | ||
2321 | recalc_sigpending(); | ||
2322 | spin_unlock_irq(&tsk->sighand->siglock); | ||
2323 | } | ||
2324 | |||
2302 | /* | 2325 | /* |
2303 | * This is also useful for kernel threads that want to temporarily | 2326 | * This is also useful for kernel threads that want to temporarily |
2304 | * (or permanently) block certain signals. | 2327 | * (or permanently) block certain signals. |
@@ -2330,11 +2353,7 @@ int sigprocmask(int how, sigset_t *set, sigset_t *oldset) | |||
2330 | return -EINVAL; | 2353 | return -EINVAL; |
2331 | } | 2354 | } |
2332 | 2355 | ||
2333 | spin_lock_irq(&tsk->sighand->siglock); | 2356 | set_current_blocked(&newset); |
2334 | tsk->blocked = newset; | ||
2335 | recalc_sigpending(); | ||
2336 | spin_unlock_irq(&tsk->sighand->siglock); | ||
2337 | |||
2338 | return 0; | 2357 | return 0; |
2339 | } | 2358 | } |
2340 | 2359 | ||