include/linux/sched.h | 19 ++++++++++--------- kernel/locking/ww_mutex.h | 6 +++++- 2 files changed, 15 insertions(+), 10 deletions(-)
The __clear_task_blocked_on() helper added a number of sanity
checks ensuring we hold the lock and that the task we are
clearing blocked_on pointer (if set) matches the lock.
However, there is an edge case in the _ww_mutex_wound() logic
where we need to clear the blocked_on pointer for the task that
owns the lock, not the task that is waiting on the lock.
For this case the sanity checks aren't valid, so handle this
by allowing a NULL lock to skip the additional checks.
This was easier to miss, I realized, as the test-ww_mutex
driver only exercises the wait-die class of ww_mutexes.
I've got a follow up patch to extend the test so that it
will exercise both.
Fixes: a4f0b6fef4b0 ("locking/mutex: Add p->blocked_on wrappers for correctness checks")
Reported-by: syzbot+602c4720aed62576cd79@syzkaller.appspotmail.com
Reported-by: Maarten Lankhorst <maarten.lankhorst@linux.intel.com>
Closes: https://lore.kernel.org/lkml/68894443.a00a0220.26d0e1.0015.GAE@google.com/
Signed-off-by: John Stultz <jstultz@google.com>
---
Cc: Ingo Molnar <mingo@redhat.com>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Juri Lelli <juri.lelli@redhat.com>
Cc: Vincent Guittot <vincent.guittot@linaro.org>
Cc: Dietmar Eggemann <dietmar.eggemann@arm.com>
Cc: Valentin Schneider <valentin.schneider@arm.com>
Cc: K Prateek Nayak <kprateek.nayak@amd.com>
Cc: Suleiman Souhlal <suleiman@google.com>
Cc: Maarten Lankhorst <maarten.lankhorst@linux.intel.com>
Cc: airlied@gmail.com
Cc: mripard@kernel.org
Cc: simona@ffwll.ch
Cc: tzimmermann@suse.de
Cc: dri-devel@lists.freedesktop.org
Cc: kernel-team@android.com
---
include/linux/sched.h | 19 ++++++++++---------
kernel/locking/ww_mutex.h | 6 +++++-
2 files changed, 15 insertions(+), 10 deletions(-)
diff --git a/include/linux/sched.h b/include/linux/sched.h
index 40d2fa90df425..a9a78f51f7f57 100644
--- a/include/linux/sched.h
+++ b/include/linux/sched.h
@@ -2166,15 +2166,16 @@ static inline void set_task_blocked_on(struct task_struct *p, struct mutex *m)
static inline void __clear_task_blocked_on(struct task_struct *p, struct mutex *m)
{
- WARN_ON_ONCE(!m);
- /* Currently we serialize blocked_on under the mutex::wait_lock */
- lockdep_assert_held_once(&m->wait_lock);
- /*
- * There may be cases where we re-clear already cleared
- * blocked_on relationships, but make sure we are not
- * clearing the relationship with a different lock.
- */
- WARN_ON_ONCE(m && p->blocked_on && p->blocked_on != m);
+ if (m) {
+ /* Currently we serialize blocked_on under the mutex::wait_lock */
+ lockdep_assert_held_once(&m->wait_lock);
+ /*
+ * There may be cases where we re-clear already cleared
+ * blocked_on relationships, but make sure we are not
+ * clearing the relationship with a different lock.
+ */
+ WARN_ON_ONCE(m && p->blocked_on && p->blocked_on != m);
+ }
p->blocked_on = NULL;
}
diff --git a/kernel/locking/ww_mutex.h b/kernel/locking/ww_mutex.h
index 086fd5487ca77..ef8ef3c28592c 100644
--- a/kernel/locking/ww_mutex.h
+++ b/kernel/locking/ww_mutex.h
@@ -342,8 +342,12 @@ static bool __ww_mutex_wound(struct MUTEX *lock,
* When waking up the task to wound, be sure to clear the
* blocked_on pointer. Otherwise we can see circular
* blocked_on relationships that can't resolve.
+ *
+ * NOTE: We pass NULL here instead of lock, because we
+ * are waking the lock owner, who may be currently blocked
+ * on a different lock.
*/
- __clear_task_blocked_on(owner, lock);
+ __clear_task_blocked_on(owner, NULL);
wake_q_add(wake_q, owner);
}
return true;
--
2.50.1.565.gc32cd1483b-goog
Hello John, On 8/1/2025 1:43 AM, John Stultz wrote: [..snip..] > diff --git a/include/linux/sched.h b/include/linux/sched.h > index 40d2fa90df425..a9a78f51f7f57 100644 > --- a/include/linux/sched.h > +++ b/include/linux/sched.h > @@ -2166,15 +2166,16 @@ static inline void set_task_blocked_on(struct task_struct *p, struct mutex *m) > > static inline void __clear_task_blocked_on(struct task_struct *p, struct mutex *m) > { > - WARN_ON_ONCE(!m); > - /* Currently we serialize blocked_on under the mutex::wait_lock */ > - lockdep_assert_held_once(&m->wait_lock); > - /* > - * There may be cases where we re-clear already cleared > - * blocked_on relationships, but make sure we are not > - * clearing the relationship with a different lock. > - */ > - WARN_ON_ONCE(m && p->blocked_on && p->blocked_on != m); > + if (m) { > + /* Currently we serialize blocked_on under the mutex::wait_lock */ > + lockdep_assert_held_once(&m->wait_lock); > + /* > + * There may be cases where we re-clear already cleared > + * blocked_on relationships, but make sure we are not > + * clearing the relationship with a different lock. > + */ > + WARN_ON_ONCE(m && p->blocked_on && p->blocked_on != m); Small concern since we don't hold the "owner->blocked_on->wait_lock" here when arriving from __ww_mutex_wound() as Hillf pointed out, can we run into a situation like: CPU0 CPU1 (Owner of Mutex A, (Trying to acquire Mutex A) trying to acquire Mutex B) ========================== =========================== __mutex_lock_common(B) ... /* B->wait_lock held */ set_task_blocked_on(ownerA, B) if (__mutex_trylock(B)) /* Returns true */ __mutex_lock_common(A) goto acquired; /* Goes to below point */ ... /* A->wait_lock held */ __clear_task_blocked_on(ownerA, B); __ww_mutex_wound(ownerA) WARN_ON_ONCE(m /* Mutex B*/ ... && ownerA->blocked_on /* Mutex B */ __clear_task_blocked_on(ownerA, NULL) ... ownerA->blocked_on = NULL; && ownerA->blocked_on /* NULL */ != m /* Mutex B */); !!! SPLAT !!! At the very least I think we should make a local copy of "p->blocked_on" to see a consistent view throughout __clear_task_blocked_on() - task either sees it is blocked on the mutex and clear "p->blocked_on", or it sees it is blocked on nothing and still clears "p->blocked_on". (Tested lightly with syzbot's C reproducer) diff --git a/include/linux/sched.h b/include/linux/sched.h index 02c340450469..f35d93cca64f 100644 --- a/include/linux/sched.h +++ b/include/linux/sched.h @@ -2165,6 +2165,8 @@ static inline void set_task_blocked_on(struct task_struct *p, struct mutex *m) static inline void __clear_task_blocked_on(struct task_struct *p, struct mutex *m) { if (m) { + struct mutex *blocked_on = p->blocked_on; + /* Currently we serialize blocked_on under the mutex::wait_lock */ lockdep_assert_held_once(&m->wait_lock); /* @@ -2172,7 +2174,7 @@ static inline void __clear_task_blocked_on(struct task_struct *p, struct mutex * * blocked_on relationships, but make sure we are not * clearing the relationship with a different lock. */ - WARN_ON_ONCE(m && p->blocked_on && p->blocked_on != m); + WARN_ON_ONCE(m && blocked_on && blocked_on != m); } p->blocked_on = NULL; } --- End result is the same, only that we avoid an unnecessary splat in this very unlikely case and save ourselves some head scratching later :) Thoughts? > + } > p->blocked_on = NULL; > } > > diff --git a/kernel/locking/ww_mutex.h b/kernel/locking/ww_mutex.h > index 086fd5487ca77..ef8ef3c28592c 100644 > --- a/kernel/locking/ww_mutex.h > +++ b/kernel/locking/ww_mutex.h > @@ -342,8 +342,12 @@ static bool __ww_mutex_wound(struct MUTEX *lock, > * When waking up the task to wound, be sure to clear the > * blocked_on pointer. Otherwise we can see circular > * blocked_on relationships that can't resolve. > + * > + * NOTE: We pass NULL here instead of lock, because we > + * are waking the lock owner, who may be currently blocked > + * on a different lock. > */ > - __clear_task_blocked_on(owner, lock); > + __clear_task_blocked_on(owner, NULL); > wake_q_add(wake_q, owner); > } > return true; -- Thanks and Regards, Prateek
Den 2025-08-01 kl. 07:09, skrev K Prateek Nayak: > Hello John, > > On 8/1/2025 1:43 AM, John Stultz wrote: > > [..snip..] > >> diff --git a/include/linux/sched.h b/include/linux/sched.h >> index 40d2fa90df425..a9a78f51f7f57 100644 >> --- a/include/linux/sched.h >> +++ b/include/linux/sched.h >> @@ -2166,15 +2166,16 @@ static inline void set_task_blocked_on(struct task_struct *p, struct mutex *m) >> >> static inline void __clear_task_blocked_on(struct task_struct *p, struct mutex *m) >> { >> - WARN_ON_ONCE(!m); >> - /* Currently we serialize blocked_on under the mutex::wait_lock */ >> - lockdep_assert_held_once(&m->wait_lock); >> - /* >> - * There may be cases where we re-clear already cleared >> - * blocked_on relationships, but make sure we are not >> - * clearing the relationship with a different lock. >> - */ >> - WARN_ON_ONCE(m && p->blocked_on && p->blocked_on != m); >> + if (m) { >> + /* Currently we serialize blocked_on under the mutex::wait_lock */ >> + lockdep_assert_held_once(&m->wait_lock); >> + /* >> + * There may be cases where we re-clear already cleared >> + * blocked_on relationships, but make sure we are not >> + * clearing the relationship with a different lock. >> + */ >> + WARN_ON_ONCE(m && p->blocked_on && p->blocked_on != m); > > Small concern since we don't hold the "owner->blocked_on->wait_lock" here > when arriving from __ww_mutex_wound() as Hillf pointed out, can we run > into a situation like: > > CPU0 CPU1 > (Owner of Mutex A, (Trying to acquire Mutex A) > trying to acquire Mutex B) > ========================== =========================== > > __mutex_lock_common(B) > ... /* B->wait_lock held */ > set_task_blocked_on(ownerA, B) > if (__mutex_trylock(B)) /* Returns true */ __mutex_lock_common(A) > goto acquired; /* Goes to below point */ ... /* A->wait_lock held */ > __clear_task_blocked_on(ownerA, B); __ww_mutex_wound(ownerA) > WARN_ON_ONCE(m /* Mutex B*/ ... > && ownerA->blocked_on /* Mutex B */ __clear_task_blocked_on(ownerA, NULL) > ... ownerA->blocked_on = NULL; > && ownerA->blocked_on /* NULL */ != m /* Mutex B */); > !!! SPLAT !!! > > > At the very least I think we should make a local copy of "p->blocked_on" > to see a consistent view throughout __clear_task_blocked_on() - task either > sees it is blocked on the mutex and clear "p->blocked_on", or it sees it is > blocked on nothing and still clears "p->blocked_on". > > (Tested lightly with syzbot's C reproducer) > > diff --git a/include/linux/sched.h b/include/linux/sched.h > index 02c340450469..f35d93cca64f 100644 > --- a/include/linux/sched.h > +++ b/include/linux/sched.h > @@ -2165,6 +2165,8 @@ static inline void set_task_blocked_on(struct task_struct *p, struct mutex *m) > static inline void __clear_task_blocked_on(struct task_struct *p, struct mutex *m) > { > if (m) { > + struct mutex *blocked_on = p->blocked_on; > + > /* Currently we serialize blocked_on under the mutex::wait_lock */ > lockdep_assert_held_once(&m->wait_lock); > /* > @@ -2172,7 +2174,7 @@ static inline void __clear_task_blocked_on(struct task_struct *p, struct mutex * > * blocked_on relationships, but make sure we are not > * clearing the relationship with a different lock. > */ > - WARN_ON_ONCE(m && p->blocked_on && p->blocked_on != m); > + WARN_ON_ONCE(m && blocked_on && blocked_on != m); > } > p->blocked_on = NULL; > } > --- > > End result is the same, only that we avoid an unnecessary splat in this > very unlikely case and save ourselves some head scratching later :) > > Thoughts? If this is required, than it should be blocked_on = READ_ONCE(p->blocked_on); Also the WARN_ON_ONCE() can have the "m && " part taken out because it's always true now. > >> + } >> p->blocked_on = NULL; >> } >> >> diff --git a/kernel/locking/ww_mutex.h b/kernel/locking/ww_mutex.h >> index 086fd5487ca77..ef8ef3c28592c 100644 >> --- a/kernel/locking/ww_mutex.h >> +++ b/kernel/locking/ww_mutex.h >> @@ -342,8 +342,12 @@ static bool __ww_mutex_wound(struct MUTEX *lock, >> * When waking up the task to wound, be sure to clear the >> * blocked_on pointer. Otherwise we can see circular >> * blocked_on relationships that can't resolve. >> + * >> + * NOTE: We pass NULL here instead of lock, because we >> + * are waking the lock owner, who may be currently blocked >> + * on a different lock. >> */ >> - __clear_task_blocked_on(owner, lock); >> + __clear_task_blocked_on(owner, NULL); >> wake_q_add(wake_q, owner); >> } >> return true; >
On Thu, Jul 31, 2025 at 10:09 PM K Prateek Nayak <kprateek.nayak@amd.com> wrote: > At the very least I think we should make a local copy of "p->blocked_on" > to see a consistent view throughout __clear_task_blocked_on() - task either > sees it is blocked on the mutex and clear "p->blocked_on", or it sees it is > blocked on nothing and still clears "p->blocked_on". > > (Tested lightly with syzbot's C reproducer) > > diff --git a/include/linux/sched.h b/include/linux/sched.h > index 02c340450469..f35d93cca64f 100644 > --- a/include/linux/sched.h > +++ b/include/linux/sched.h > @@ -2165,6 +2165,8 @@ static inline void set_task_blocked_on(struct task_struct *p, struct mutex *m) > static inline void __clear_task_blocked_on(struct task_struct *p, struct mutex *m) > { > if (m) { > + struct mutex *blocked_on = p->blocked_on; > + > /* Currently we serialize blocked_on under the mutex::wait_lock */ > lockdep_assert_held_once(&m->wait_lock); > /* > @@ -2172,7 +2174,7 @@ static inline void __clear_task_blocked_on(struct task_struct *p, struct mutex * > * blocked_on relationships, but make sure we are not > * clearing the relationship with a different lock. > */ > - WARN_ON_ONCE(m && p->blocked_on && p->blocked_on != m); > + WARN_ON_ONCE(m && blocked_on && blocked_on != m); > } > p->blocked_on = NULL; > } > --- > > End result is the same, only that we avoid an unnecessary splat in this > very unlikely case and save ourselves some head scratching later :) Good point. Thanks for suggesting this! I'll rework to include both this and Maarten's suggestions. Thank you for the feedback! -john
© 2016 - 2025 Red Hat, Inc.