Commit 1553dcf3 authored by Li Huafei's avatar Li Huafei
Browse files

printk: Fix the qspinlock deadloop caused by zap_locks()

hulk inclusion
category: bugfix
bugzilla: https://gitee.com/openeuler/kernel/issues/IBC1RM


CVE: NA

--------------------------------

printk calls zap_locks() to bypass logbuf_lock during panic. If the
current system uses qspinlock, it may cause a deadloop in
queued_spin_lock_slowpath(). Consider the following locking sequence:

cpu1: (0, 0, 1)
cpu2: (0, 1, 1)
cpu3: (3, 1, 1)

If zap_locks() is called at this time, cpu2 may acquire the lock first,
and then cpu3 acquires the lock. When cpu3 releases the lock, it checks
if the tail is itself. Originally, it should be, but after zap_locks(),
the tail is modified to 0. At this point, cpu3 thinks there are other
contenders, it will first set the locked bit and then wait for the
contender to set its next pointer. However, in reality, there are no
other contenders, so cpu3 will hold the lock (locked bit is 1) and loop
here, causing other CPUs that want to acquire the lock to be stuck.

To fix this problem, the lock initialization in zap_locks() can be
degraded to clear locked_pending and retain the tail, avoiding
misjudgment.

Fixes: be926ed0 ("printk/panic: Avoid deadlock in printk()")
Signed-off-by: default avatarLi Huafei <lihuafei1@huawei.com>
parent f9870b23
Loading
Loading
Loading
Loading
+10 −0
Original line number Diff line number Diff line
@@ -1634,11 +1634,21 @@ void zap_locks(void)
{
	if (raw_spin_is_locked(&logbuf_lock)) {
		debug_locks_off();
#ifdef CONFIG_QUEUED_SPINLOCKS
		/* Do not clear the tail to avoid infinite loop in qspinlock. */
		WRITE_ONCE(logbuf_lock.raw_lock.locked_pending, 0);
#else
		raw_spin_lock_init(&logbuf_lock);
#endif
	}

	if (raw_spin_is_locked(&console_owner_lock)) {
#ifdef CONFIG_QUEUED_SPINLOCKS
		/* Do not clear the tail to avoid infinite loop in qspinlock. */
		WRITE_ONCE(console_owner_lock.raw_lock.locked_pending, 0);
#else
		raw_spin_lock_init(&console_owner_lock);
#endif
		console_owner = NULL;
		console_waiter = false;
	}