mirror of
https://github.com/torvalds/linux.git
synced 2026-09-09 09:22:02 +02:00
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:
commit
c8990f3179
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user