From a2c72fae694277071dcbab33dfb7f88af5e54954 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Thu, 11 Dec 2003 22:20:08 -0800 Subject: [PATCH] Fix subtle bug in "finish_wait()", which can cause kernel stack corruption on SMP because of another CPU still accessing a waitqueue even after it was de-allocated. Use a careful version of the list emptiness check to make sure we don't de-allocate the stack frame before the waitqueue is all done. --- include/linux/list.h | 12 ++++++++++++ kernel/fork.c | 15 ++++++++++++++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/include/linux/list.h b/include/linux/list.h index b7d383e6a3121..0835011b0ffbd 100644 --- a/include/linux/list.h +++ b/include/linux/list.h @@ -208,6 +208,18 @@ static inline int list_empty(const struct list_head *head) return head->next == head; } +/** + * list_empty_careful - tests whether a list is + * empty _and_ checks that no other CPU might be + * in the process of still modifying either member + * @head: the list to test. + */ +static inline int list_empty_careful(const struct list_head *head) +{ + struct list_head *next = head->next; + return (next == head) && (next == head->prev); +} + static inline void __list_splice(struct list_head *list, struct list_head *head) { diff --git a/kernel/fork.c b/kernel/fork.c index 80c037b5ac3db..6fa0ce76acc58 100644 --- a/kernel/fork.c +++ b/kernel/fork.c @@ -159,7 +159,20 @@ void finish_wait(wait_queue_head_t *q, wait_queue_t *wait) unsigned long flags; __set_current_state(TASK_RUNNING); - if (!list_empty(&wait->task_list)) { + /* + * We can check for list emptiness outside the lock + * IFF: + * - we use the "careful" check that verifies both + * the next and prev pointers, so that there cannot + * be any half-pending updates in progress on other + * CPU's that we haven't seen yet (and that might + * still change the stack area. + * and + * - all other users take the lock (ie we can only + * have _one_ other CPU that looks at or modifies + * the list). + */ + if (!list_empty_careful(&wait->task_list)) { spin_lock_irqsave(&q->lock, flags); list_del_init(&wait->task_list); spin_unlock_irqrestore(&q->lock, flags); -- 2.53.0