Misc locking fixes:

- Fix a softirq processing delay bug in local_interrupt_disable(),
    which should mostly only affect the Rust runtime (Boqun Feng)
 
  - Remove the hardirq_disable_count() function which caused the
    previous bug and is now unused & unnecessary (Boqun Feng)
 
  - lockdep: Invalidate stale class_cache entries for zapped classes
    (Eric Dumazet)
 
  - Fix rt_mutex specific futex scheduling helpers
    (Sebastian Andrzej Siewior)
 
  - Fix rcuwait use-after-free race during futex requeue PI (Yao Kai)
 
 Signed-off-by: Ingo Molnar <mingo@kernel.org>
 -----BEGIN PGP SIGNATURE-----
 
 iQJFBAABCgAvFiEEBpT5eoXrXCwVQwEKEnMQ0APhK1gFAmqdSKMRHG1pbmdvQGtl
 cm5lbC5vcmcACgkQEnMQ0APhK1gh6hAAtIhAm10pkx7JG4Kl+SQArmoAxNMfW9X1
 grx50dn1/LFuIF0upooJwJmLMBpPMmxcpYPWbg5nTUsx+wDqcJTT0T7Aw2iZN2f+
 OdODMOGNuQU4IUxDC+qX+fkgHvRFzng0zhSXYL2kGpQJHktrFMAHn90+5aO9SEgA
 XcSjmDacTxfJADNk43snrlHy6t+yOXBEmIrhicnbh11GFNIXLbvdGW/0SObU0Q5o
 WdwGHTZRFAM3mpX7xMSFOKypM+Gt4pXdQ/uC1PQg0B/AT35p4zsYIdcht/cBMHp0
 93eTeJd9ede410t3hL1Hnsuo1zf5qHtCgBukS+UHJWoWpnSOMgT2iaBx2X3jIsev
 ptPIqV5Alq8O+tnNOSDOwD2HH2drAdUBVH+gZ/yKMDbk9jfw9eZKqRw628U2P7rG
 mD5CLzpmfj0mMAuqcOhhdqSQmPhjp2Y8Ju/8UAMSmcocvqjQBxvouNS3Tby9U5v+
 R4CsuUwtnYpMyy5xyRrnwVsaE3le5fvneXCmCokk8FtQ8as/4ATd+cG+RROSa3vW
 /AAjrB1HBQqyseE8lki4LFFmlr01Otc8u6pASy4/pxeuKMA9hSuLC3RuEfVetW6W
 BcK4BCIJBy6chcsCBxv0F7FgI/y7/bpVyBmoTkW3Sb0I23xswXzTzbucn/dPJNdl
 oqFj3KQygxw=
 =vEuZ
 -----END PGP SIGNATURE-----

Merge tag 'locking-urgent-2026-09-06' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip

Pull locking fixes from Ingo Molnar:

 - Fix a softirq processing delay bug in local_interrupt_disable(),
   which should mostly only affect the Rust runtime (Boqun Feng)

 - Remove the hardirq_disable_count() function which caused the
   previous bug and is now unused & unnecessary (Boqun Feng)

 - lockdep: Invalidate stale class_cache entries for zapped classes
   (Eric Dumazet)

 - Fix rt_mutex specific futex scheduling helpers
   (Sebastian Andrzej Siewior)

 - Fix rcuwait use-after-free race during futex requeue PI (Yao Kai)

* tag 'locking-urgent-2026-09-06' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip:
  futex: Prevent rcuwait use-after-free during requeue PI
  futex: Provide rt_mutex_.*_schedule() equivalents for futex scheduling
  locking/lockdep: Invalidate stale class_cache entries for zapped classes
  preempt: Remove hardirq_disable_count()
  interrupt: Disable interrupt before modifying hardirq_disable counter
This commit is contained in:
Linus Torvalds 2026-09-06 10:45:46 -07:00
commit c8990f3179
9 changed files with 90 additions and 51 deletions

View File

@ -20,11 +20,8 @@
/* Per-CPU interrupt disabling state for local_interrupt_{disable,enable}(). */
DECLARE_PER_CPU(unsigned long, local_interrupt_disable_state);
static __always_inline void __local_interrupt_disable(void)
static __always_inline void __local_interrupt_save_state(unsigned long flags)
{
unsigned long flags;
local_irq_save(flags);
raw_cpu_write(local_interrupt_disable_state, flags);
}
@ -36,9 +33,9 @@ static __always_inline void __local_interrupt_enable(void)
}
#ifndef INSTANTIATE_EXPORTED_INTERRUPT_DISABLE
static __always_inline void _local_interrupt_disable(void)
static __always_inline void _local_interrupt_save_state(unsigned long flags)
{
__local_interrupt_disable();
__local_interrupt_save_state(flags);
}
static __always_inline void _local_interrupt_enable(void)
@ -46,27 +43,30 @@ static __always_inline void _local_interrupt_enable(void)
__local_interrupt_enable();
}
#else
extern void _local_interrupt_disable(void);
extern void _local_interrupt_save_state(unsigned long flags);
extern void _local_interrupt_enable(void);
#endif
#else /* !MODULE */
extern void _local_interrupt_disable(void);
extern void _local_interrupt_save_state(unsigned long flags);
extern void _local_interrupt_enable(void);
#endif /* !MODULE */
#define hardirq_disable_enter() __preempt_count_add_return(HARDIRQ_DISABLE_OFFSET)
#define hardirq_disable_exit() __preempt_count_sub_return(HARDIRQ_DISABLE_OFFSET)
static inline void local_interrupt_disable(void)
{
int new_count;
unsigned long flags;
WARN_ON_ONCE(in_nmi());
local_irq_save(flags);
new_count = hardirq_disable_enter();
/* Interrupts can happen here, but it's OK, see __irq_exit_rcu(). */
if ((new_count & HARDIRQ_DISABLE_MASK) == HARDIRQ_DISABLE_OFFSET)
_local_interrupt_disable();
_local_interrupt_save_state(flags);
}
static inline void local_interrupt_enable(void)

View File

@ -168,10 +168,6 @@ static __always_inline unsigned char interrupt_context_level(void)
#define in_softirq() (softirq_count())
#define in_interrupt() (irq_count())
#define hardirq_disable_count() ((preempt_count() & HARDIRQ_DISABLE_MASK) >> HARDIRQ_DISABLE_SHIFT)
#define hardirq_disable_enter() __preempt_count_add_return(HARDIRQ_DISABLE_OFFSET)
#define hardirq_disable_exit() __preempt_count_sub_return(HARDIRQ_DISABLE_OFFSET)
/*
* The preempt_count offset after preempt_disable();
*/

View File

@ -52,8 +52,10 @@ static inline bool rt_or_dl_task_policy(struct task_struct *tsk)
#ifdef CONFIG_RT_MUTEXES
extern void rt_mutex_pre_schedule(void);
extern void rt_mutex_futex_pre_schedule(void);
extern void rt_mutex_schedule(void);
extern void rt_mutex_post_schedule(void);
extern void rt_mutex_futex_post_schedule(void);
/*
* Must hold either p->pi_lock or task_rq(p)->lock.

View File

@ -1070,17 +1070,11 @@ int futex_lock_pi(u32 __user *uaddr, unsigned int flags, ktime_t *time, int tryl
* Caution; releasing @hb in-scope. The hb->lock is still locked
* while the reference is dropped. The reference can not be dropped
* after the unlock because if a user initiated resize is in progress
* then we might need to wake him. This can not be done after the
* rt_mutex_pre_schedule() invocation. The hb will remain valid because
* the thread, performing resize, will block on hb->lock during
* the requeue.
* then we might need to wake him. The hb will remain valid
* because the thread, performing resize, will block on
* hb->lock during the requeue.
*/
futex_private_hash_put(no_free_ptr(hbr.fph));
/*
* Must be done before we enqueue the waiter, here is unfortunately
* under the hb lock, but that *should* work because it does nothing.
*/
rt_mutex_pre_schedule();
rt_mutex_init_waiter(&rt_waiter);
@ -1146,10 +1140,6 @@ int futex_lock_pi(u32 __user *uaddr, unsigned int flags, ktime_t *time, int tryl
* the
*/
futex_q_lockptr_lock(&q);
/*
* Waiter is unqueued.
*/
rt_mutex_post_schedule();
no_block:
/*
* Fixup the pi_state owner and possibly acquire the lock if we

View File

@ -154,8 +154,16 @@ static inline void futex_requeue_pi_complete(struct futex_q *q, int locked)
} while (!atomic_try_cmpxchg(&q->requeue_state, &old, new));
#ifdef CONFIG_PREEMPT_RT
/* If the waiter interleaved with the requeue let it know */
if (unlikely(old == Q_REQUEUE_PI_WAIT))
/*
* The waiter in futex_requeue_pi_wakeup_sync() can interleave with the
* wake below: It will assign Q_REQUEUE_PI_IN_PROGRESS and here it will
* be updated to Q_REQUEUE_PI_LOCKED (locked = 1). The rcuwait_wait_event()
* will already read Q_REQUEUE_PI_LOCKED and skip the schedule() invocation,
* leading to an access of futex_q::requeue_wait after the waiter returned.
* In this case only we skip the wake here and rely on following wake in
* requeue_pi_wake_futex() to perform the wake if needed.
*/
if (unlikely(old == Q_REQUEUE_PI_WAIT) && new != Q_REQUEUE_PI_LOCKED)
rcuwait_wake_up(&q->requeue_wait);
#endif
}

View File

@ -963,6 +963,34 @@ look_up_lock_class(const struct lockdep_map *lock, unsigned int subclass)
return NULL;
}
static __always_inline bool lock_class_cache_is_valid(const struct lockdep_map *lock,
const struct lock_class *class,
unsigned int subclass)
{
unsigned int class_subclass;
if (!class)
return false;
if (unlikely(class < lock_classes || class >= lock_classes + MAX_LOCKDEP_KEYS))
return false;
if (unlikely(!arch_test_bit(class - lock_classes, lock_classes_in_use)))
return false;
if (unlikely(!lock->key))
return false;
class_subclass = subclass ? subclass : class->subclass;
if (unlikely(class_subclass >= MAX_LOCKDEP_SUBCLASSES))
return false;
if (unlikely(READ_ONCE(class->key) != lock->key->subkeys + class_subclass))
return false;
return true;
}
/*
* Static locks do not have their class-keys yet - for them the key is
* the lock object itself. If the lock is in the per cpu area, the
@ -1395,9 +1423,9 @@ register_lock_class(struct lockdep_map *lock, unsigned int subclass, int force)
out_set_class_cache:
if (!subclass || force)
lock->class_cache[0] = class;
WRITE_ONCE(lock->class_cache[0], class);
else if (subclass < NR_LOCKDEP_CACHING_CLASSES)
lock->class_cache[subclass] = class;
WRITE_ONCE(lock->class_cache[subclass], class);
/*
* Hash collision, did we smoke some? We found a class with a matching
@ -4957,7 +4985,7 @@ void lockdep_init_map_type(struct lockdep_map *lock, const char *name,
int i;
for (i = 0; i < NR_LOCKDEP_CACHING_CLASSES; i++)
lock->class_cache[i] = NULL;
WRITE_ONCE(lock->class_cache[i], NULL);
#ifdef CONFIG_LOCK_STAT
lock->cpu = raw_smp_processor_id();
@ -5022,12 +5050,15 @@ EXPORT_SYMBOL_GPL(__lockdep_no_track__);
void lockdep_set_lock_cmp_fn(struct lockdep_map *lock, lock_cmp_fn cmp_fn,
lock_print_fn print_fn)
{
struct lock_class *class = lock->class_cache[0];
struct lock_class *class = READ_ONCE(lock->class_cache[0]);
unsigned long flags;
raw_local_irq_save(flags);
lockdep_recursion_inc();
if (!lock_class_cache_is_valid(lock, class, 0))
class = NULL;
if (!class)
class = register_lock_class(lock, 0, 0);
@ -5119,8 +5150,11 @@ static int __lock_acquire(struct lockdep_map *lock, unsigned int subclass,
if (DEBUG_LOCKS_WARN_ON(subclass >= MAX_LOCKDEP_SUBCLASSES))
return 0;
if (subclass < NR_LOCKDEP_CACHING_CLASSES)
class = lock->class_cache[subclass];
if (subclass < NR_LOCKDEP_CACHING_CLASSES) {
class = READ_ONCE(lock->class_cache[subclass]);
if (!lock_class_cache_is_valid(lock, class, subclass))
class = NULL;
}
/*
* Not cached?
*/
@ -5324,9 +5358,9 @@ static noinstr int match_held_lock(const struct held_lock *hlock,
return 1;
if (hlock->references) {
const struct lock_class *class = lock->class_cache[0];
const struct lock_class *class = READ_ONCE(lock->class_cache[0]);
if (!class)
if (!lock_class_cache_is_valid(lock, class, 0))
class = look_up_lock_class(lock, 0);
/*

View File

@ -423,6 +423,7 @@ int __sched rt_mutex_wait_proxy_lock(struct rt_mutex_base *lock,
{
int ret;
rt_mutex_futex_pre_schedule();
raw_spin_lock_irq(&lock->wait_lock);
/* sleep on the mutex */
set_current_state(TASK_INTERRUPTIBLE);
@ -433,6 +434,7 @@ int __sched rt_mutex_wait_proxy_lock(struct rt_mutex_base *lock,
*/
fixup_rt_mutex_waiters(lock, true);
raw_spin_unlock_irq(&lock->wait_lock);
rt_mutex_futex_post_schedule();
return ret;
}

View File

@ -7637,6 +7637,17 @@ void rt_mutex_pre_schedule(void)
sched_submit_work(current);
}
/*
* Used within the futex syscall context, skips sched_submit_work() because none
* its work will be done. Asserts ensure that it is indeed the case.
*/
void rt_mutex_futex_pre_schedule(void)
{
lockdep_assert(!(current->flags & (PF_WQ_WORKER | PF_IO_WORKER)));
lockdep_assert(!current->plug);
lockdep_assert(!fetch_and_set(current->sched_rt_mutex, 1));
}
void rt_mutex_schedule(void)
{
lockdep_assert(current->sched_rt_mutex);
@ -7649,6 +7660,11 @@ void rt_mutex_post_schedule(void)
lockdep_assert(fetch_and_set(current->sched_rt_mutex, 0));
}
void rt_mutex_futex_post_schedule(void)
{
lockdep_assert(fetch_and_set(current->sched_rt_mutex, 0));
}
/*
* rt_mutex_setprio - set the current priority of a task
* @p: task to boost

View File

@ -91,11 +91,11 @@ EXPORT_PER_CPU_SYMBOL_GPL(hardirq_context);
DEFINE_PER_CPU(unsigned long, local_interrupt_disable_state);
void _local_interrupt_disable(void)
void _local_interrupt_save_state(unsigned long flags)
{
__local_interrupt_disable();
__local_interrupt_save_state(flags);
}
EXPORT_SYMBOL(_local_interrupt_disable);
EXPORT_SYMBOL(_local_interrupt_save_state);
void _local_interrupt_enable(void)
{
@ -749,16 +749,7 @@ static inline void __irq_exit_rcu(void)
#endif
account_hardirq_exit(current);
preempt_count_sub(HARDIRQ_OFFSET);
/*
* Interrupts may happen between hardirq_disable_enter() and
* local_irq_save() in local_interrupt_disable(), if irq_exit() invokes
* softirq here, we may have a softirq handler calling
* local_interrupt_disable() but it won't disable the IRQ because
* hardirq disabling count is already 1, hence we need to prevent
* invoking softirq when a local_interrupt_disable() is ongoing.
*/
if (!in_interrupt() && !hardirq_disable_count() &&
local_softirq_pending()) {
if (!in_interrupt() && local_softirq_pending()) {
/*
* If we left hrtimers unarmed, make sure to arm them now,
* before enabling interrupts to run softirq.