{bp-19880} spinlock: fix ticket lock corruption in trylock and unlock - #20072
Merged
Conversation
Two defects in the CONFIG_TICKET_SPINLOCK paths of spinlock.h:
1. spin_trylock_notrace() passed &lock->owner as the "expected" pointer
of atomic_cmpxchg(). A failed compare-exchange writes the current
value of the target object back through that pointer, so a losing
trylock stores lock->next into lock->owner. owner then equals next,
which is the unlocked state: a lock still held by another CPU reports
itself as free, spin_is_locked() returns false and the lock can be
taken again. Every later unlock keeps incrementing owner past next,
so the ticket of a real waiter never matches and the lock stays
locked forever. Keep the expected value in a local variable.
2. spin_unlock() was wrapped in #ifdef __SP_UNLOCK_FUNCTION, a macro
that is never defined anywhere in the tree. The function body was
therefore dead code and spin_unlock() always expanded to
"do { *(l) = SP_UNLOCKED; } while (0)", which zeroes both ticket
counters instead of releasing one ticket with
atomic_fetch_add(&lock->owner, 1). That drops queued waiters, lets a
newcomer draw ticket 0 and enter the critical section, and also skips
the UP_DMB/UP_DSB/UP_SEV release barriers and the
sched_note_spinlock_unlock() note. Drop the dead #ifdef so
spin_unlock() is always the function.
Both were reproduced on qemu-armv7a:smp (cortex-a7 x4) with
CONFIG_TICKET_SPINLOCK=y, where the compare-exchange lowers to native
ldrex/strex. This confirms the root cause is the C-level aliasing of
the expected pointer, not the atomic implementation.
Refs: apache#19808
Signed-off-by: hujun5 <hujun5@xiaomi.com>
cederom
approved these changes
Sep 7, 2026
Contributor
Author
|
CI fix |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Two defects in the CONFIG_TICKET_SPINLOCK paths of spinlock.h:
spin_trylock_notrace() passed &lock->owner as the "expected" pointer of atomic_cmpxchg(). A failed compare-exchange writes the current value of the target object back through that pointer, so a losing trylock stores lock->next into lock->owner. owner then equals next, which is the unlocked state: a lock still held by another CPU reports itself as free, spin_is_locked() returns false and the lock can be taken again. Every later unlock keeps incrementing owner past next, so the ticket of a real waiter never matches and the lock stays locked forever. Keep the expected value in a local variable.
spin_unlock() was wrapped in #ifdef __SP_UNLOCK_FUNCTION, a macro that is never defined anywhere in the tree. The function body was therefore dead code and spin_unlock() always expanded to "do { *(l) = SP_UNLOCKED; } while (0)", which zeroes both ticket counters instead of releasing one ticket with atomic_fetch_add(&lock->owner, 1). That drops queued waiters, lets a newcomer draw ticket 0 and enter the critical section, and also skips the UP_DMB/UP_DSB/UP_SEV release barriers and the sched_note_spinlock_unlock() note. Drop the dead #ifdef so spin_unlock() is always the function.
Both were reproduced on qemu-armv7a:smp (cortex-a7 x4) with CONFIG_TICKET_SPINLOCK=y, where the compare-exchange lowers to native ldrex/strex. This confirms the root cause is the C-level aliasing of the expected pointer, not the atomic implementation.
Refs: #19808
Impact
RELEASE
Testing
CI