diff options
| author | Oleg Nesterov <oleg@tv-sign.ru> | 2008-05-23 16:04:41 -0400 |
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2008-05-24 12:56:10 -0400 |
| commit | da7978b0348d497688541e2d2f5739aa2a2c334f (patch) | |
| tree | f4f55bf4293ff203f598e7a57959da1c5a7ad295 /kernel | |
| parent | dfc7064500061677720fa26352963c772d3ebe6b (diff) | |
signals: fix sigqueue_free() vs __exit_signal() race
__exit_signal() does flush_sigqueue(tsk->pending) outside of ->siglock.
This can race with another thread doing sigqueue_free(), we can free the
same SIGQUEUE_PREALLOC sigqueue twice or corrupt the pending->list.
Note that even sys_exit_group() can trigger this race, not only
sys_timer_delete().
Move the callsite of flush_sigqueue(tsk->pending) under ->siglock.
This patch doesn't touch flush_sigqueue(->shared_pending) below, it is
called when there are no other threads which can play with signals, and
sigqueue_free() can't be used outside of our thread group.
Signed-off-by: Oleg Nesterov <oleg@tv-sign.ru>
Acked-by: Roland McGrath <roland@redhat.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Diffstat (limited to 'kernel')
| -rw-r--r-- | kernel/exit.c | 7 | ||||
| -rw-r--r-- | kernel/signal.c | 3 |
2 files changed, 8 insertions, 2 deletions
diff --git a/kernel/exit.c b/kernel/exit.c index 1510f78a0ffa..8f6185e69b69 100644 --- a/kernel/exit.c +++ b/kernel/exit.c | |||
| @@ -126,6 +126,12 @@ static void __exit_signal(struct task_struct *tsk) | |||
| 126 | 126 | ||
| 127 | __unhash_process(tsk); | 127 | __unhash_process(tsk); |
| 128 | 128 | ||
| 129 | /* | ||
| 130 | * Do this under ->siglock, we can race with another thread | ||
| 131 | * doing sigqueue_free() if we have SIGQUEUE_PREALLOC signals. | ||
| 132 | */ | ||
| 133 | flush_sigqueue(&tsk->pending); | ||
| 134 | |||
| 129 | tsk->signal = NULL; | 135 | tsk->signal = NULL; |
| 130 | tsk->sighand = NULL; | 136 | tsk->sighand = NULL; |
| 131 | spin_unlock(&sighand->siglock); | 137 | spin_unlock(&sighand->siglock); |
| @@ -133,7 +139,6 @@ static void __exit_signal(struct task_struct *tsk) | |||
| 133 | 139 | ||
| 134 | __cleanup_sighand(sighand); | 140 | __cleanup_sighand(sighand); |
| 135 | clear_tsk_thread_flag(tsk,TIF_SIGPENDING); | 141 | clear_tsk_thread_flag(tsk,TIF_SIGPENDING); |
| 136 | flush_sigqueue(&tsk->pending); | ||
| 137 | if (sig) { | 142 | if (sig) { |
| 138 | flush_sigqueue(&sig->shared_pending); | 143 | flush_sigqueue(&sig->shared_pending); |
| 139 | taskstats_tgid_free(sig); | 144 | taskstats_tgid_free(sig); |
diff --git a/kernel/signal.c b/kernel/signal.c index 72bb4f51f963..12ffea7c201d 100644 --- a/kernel/signal.c +++ b/kernel/signal.c | |||
| @@ -1242,7 +1242,8 @@ void sigqueue_free(struct sigqueue *q) | |||
| 1242 | /* | 1242 | /* |
| 1243 | * If the signal is still pending remove it from the | 1243 | * If the signal is still pending remove it from the |
| 1244 | * pending queue. We must hold ->siglock while testing | 1244 | * pending queue. We must hold ->siglock while testing |
| 1245 | * q->list to serialize with collect_signal(). | 1245 | * q->list to serialize with collect_signal() or with |
| 1246 | * __exit_signal()->flush_sigqueue(). | ||
| 1246 | */ | 1247 | */ |
| 1247 | spin_lock_irqsave(lock, flags); | 1248 | spin_lock_irqsave(lock, flags); |
| 1248 | if (!list_empty(&q->list)) | 1249 | if (!list_empty(&q->list)) |
