This is a note to let you know that I've just added the patch titled futex: Fix more put_pi_state() vs. exit_pi_state_list() races to the 4.9-stable tree which can be found at: http://www.kernel.org/git/?p=linux/kernel/git/stable/stable-queue.git;a=summary The filename of the patch is: futex-fix-more-put_pi_state-vs.-exit_pi_state_list-races.patch and it can be found in the queue-4.9 subdirectory. If you, or anyone else, feels it should not be added to the stable tree, please let <stable@xxxxxxxxxxxxxxx> know about it. >From foo@baz Thu Mar 4 02:09:29 PM CET 2021 From: Ben Hutchings <ben@xxxxxxxxxxxxxxx> Date: Mon, 1 Mar 2021 18:31:55 +0100 Subject: futex: Fix more put_pi_state() vs. exit_pi_state_list() races To: stable@xxxxxxxxxxxxxxx Cc: Lee Jones <lee.jones@xxxxxxxxxx>, "Luis Claudio R. Goncalves" <lgoncalv@xxxxxxxxxx> Message-ID: <YD0lC+fuTMO3BKSw@xxxxxxxxxxxxxxx> Content-Disposition: inline From: Ben Hutchings <ben@xxxxxxxxxxxxxxx> From: Peter Zijlstra <peterz@xxxxxxxxxxxxx> commit 153fbd1226fb30b8630802aa5047b8af5ef53c9f upstream. Dmitry (through syzbot) reported being able to trigger the WARN in get_pi_state() and a use-after-free on: raw_spin_lock_irq(&pi_state->pi_mutex.wait_lock); Both are due to this race: exit_pi_state_list() put_pi_state() lock(&curr->pi_lock) while() { pi_state = list_first_entry(head); hb = hash_futex(&pi_state->key); unlock(&curr->pi_lock); dec_and_test(&pi_state->refcount); lock(&hb->lock) lock(&pi_state->pi_mutex.wait_lock) // uaf if pi_state free'd lock(&curr->pi_lock); .... unlock(&curr->pi_lock); get_pi_state(); // WARN; refcount==0 The problem is we take the reference count too late, and don't allow it being 0. Fix it by using inc_not_zero() and simply retrying the loop when we fail to get a refcount. In that case put_pi_state() should remove the entry from the list. Reported-by: Dmitry Vyukov <dvyukov@xxxxxxxxxx> Signed-off-by: Peter Zijlstra (Intel) <peterz@xxxxxxxxxxxxx> Reviewed-by: Thomas Gleixner <tglx@xxxxxxxxxxxxx> Cc: Gratian Crisan <gratian.crisan@xxxxxx> Cc: Linus Torvalds <torvalds@xxxxxxxxxxxxxxxxxxxx> Cc: Peter Zijlstra <peterz@xxxxxxxxxxxxx> Cc: dvhart@xxxxxxxxxxxxx Cc: syzbot <bot+2af19c9e1ffe4d4ee1d16c56ae7580feaee75765@xxxxxxxxxxxxxxxxxxxxxxxxx> Cc: syzkaller-bugs@xxxxxxxxxxxxxxxx Cc: <stable@xxxxxxxxxxxxxxx> Fixes: c74aef2d06a9 ("futex: Fix pi_state->owner serialization") Link: http://lkml.kernel.org/r/20171031101853.xpfh72y643kdfhjs@xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx Signed-off-by: Ingo Molnar <mingo@xxxxxxxxxx> Signed-off-by: Ben Hutchings <ben@xxxxxxxxxxxxxxx> Signed-off-by: Greg Kroah-Hartman <gregkh@xxxxxxxxxxxxxxxxxxx> --- kernel/futex.c | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) --- a/kernel/futex.c +++ b/kernel/futex.c @@ -941,11 +941,27 @@ static void exit_pi_state_list(struct ta */ raw_spin_lock_irq(&curr->pi_lock); while (!list_empty(head)) { - next = head->next; pi_state = list_entry(next, struct futex_pi_state, list); key = pi_state->key; hb = hash_futex(&key); + + /* + * We can race against put_pi_state() removing itself from the + * list (a waiter going away). put_pi_state() will first + * decrement the reference count and then modify the list, so + * its possible to see the list entry but fail this reference + * acquire. + * + * In that case; drop the locks to let put_pi_state() make + * progress and retry the loop. + */ + if (!atomic_inc_not_zero(&pi_state->refcount)) { + raw_spin_unlock_irq(&curr->pi_lock); + cpu_relax(); + raw_spin_lock_irq(&curr->pi_lock); + continue; + } raw_spin_unlock_irq(&curr->pi_lock); spin_lock(&hb->lock); @@ -956,8 +972,10 @@ static void exit_pi_state_list(struct ta * task still owns the PI-state: */ if (head->next != next) { + /* retain curr->pi_lock for the loop invariant */ raw_spin_unlock(&pi_state->pi_mutex.wait_lock); spin_unlock(&hb->lock); + put_pi_state(pi_state); continue; } @@ -965,9 +983,8 @@ static void exit_pi_state_list(struct ta WARN_ON(list_empty(&pi_state->list)); list_del_init(&pi_state->list); pi_state->owner = NULL; - raw_spin_unlock(&curr->pi_lock); - get_pi_state(pi_state); + raw_spin_unlock(&curr->pi_lock); raw_spin_unlock_irq(&pi_state->pi_mutex.wait_lock); spin_unlock(&hb->lock); Patches currently in stable-queue which might be from ben@xxxxxxxxxxxxxxx are queue-4.9/futex-cleanup-refcounting.patch queue-4.9/futex-fix-more-put_pi_state-vs.-exit_pi_state_list-races.patch queue-4.9/futex-futex_unlock_pi-determinism.patch queue-4.9/futex-cleanup-variable-names-for-futex_top_waiter.patch queue-4.9/futex-don-t-enable-irqs-unconditionally-in-put_pi_state.patch queue-4.9/futex-fix-pi_state-owner-serialization.patch queue-4.9/futex-pull-rt_mutex_futex_unlock-out-from-under-hb-lock.patch