]> git.itanic.dy.fi Git - linux-stable/commitdiff
parisc: Add lightweight spinlock checks
authorHelge Deller <deller@gmx.de>
Mon, 22 May 2023 13:11:16 +0000 (15:11 +0200)
committerHelge Deller <deller@gmx.de>
Wed, 24 May 2023 12:23:07 +0000 (14:23 +0200)
Add a lightweight spinlock check which uses only two instructions
per spinlock call. It detects if a spinlock has been trashed by
some memory corruption and then halts the kernel. It will not detect
uninitialized spinlocks, for which CONFIG_DEBUG_SPINLOCK needs to
be enabled.

This lightweight spinlock check shouldn't influence runtime, so it's
safe to enable it by default.

The __ARCH_SPIN_LOCK_UNLOCKED_VAL constant has been choosen small enough
to be able to be loaded by one LDI assembler statement.

Signed-off-by: Helge Deller <deller@gmx.de>
arch/parisc/Kconfig.debug
arch/parisc/include/asm/spinlock.h
arch/parisc/include/asm/spinlock_types.h
arch/parisc/kernel/traps.c

index f66554cd5c4518cf919abf321ce9137e8b2be59d..3a059cb5e112fb09fe701fe362c07c84cec247e2 100644 (file)
@@ -1 +1,12 @@
 # SPDX-License-Identifier: GPL-2.0
+#
+config LIGHTWEIGHT_SPINLOCK_CHECK
+       bool "Enable lightweight spinlock checks"
+       depends on SMP && !DEBUG_SPINLOCK
+       default y
+       help
+         Add checks with low performance impact to the spinlock functions
+         to catch memory overwrites at runtime. For more advanced
+         spinlock debugging you should choose the DEBUG_SPINLOCK option
+         which will detect unitialized spinlocks too.
+         If unsure say Y here.
index a6e5d66a76562aaa8e89bab4b55e4aa39d13ad43..edfcb9858bcb7ca0bf2fee2cfeef304836beb4a5 100644 (file)
@@ -7,10 +7,26 @@
 #include <asm/processor.h>
 #include <asm/spinlock_types.h>
 
+#define SPINLOCK_BREAK_INSN    0x0000c006      /* break 6,6 */
+
+static inline void arch_spin_val_check(int lock_val)
+{
+       if (IS_ENABLED(CONFIG_LIGHTWEIGHT_SPINLOCK_CHECK))
+               asm volatile(   "andcm,= %0,%1,%%r0\n"
+                               ".word %2\n"
+               : : "r" (lock_val), "r" (__ARCH_SPIN_LOCK_UNLOCKED_VAL),
+                       "i" (SPINLOCK_BREAK_INSN));
+}
+
 static inline int arch_spin_is_locked(arch_spinlock_t *x)
 {
-       volatile unsigned int *a = __ldcw_align(x);
-       return READ_ONCE(*a) == 0;
+       volatile unsigned int *a;
+       int lock_val;
+
+       a = __ldcw_align(x);
+       lock_val = READ_ONCE(*a);
+       arch_spin_val_check(lock_val);
+       return (lock_val == 0);
 }
 
 static inline void arch_spin_lock(arch_spinlock_t *x)
@@ -18,9 +34,18 @@ static inline void arch_spin_lock(arch_spinlock_t *x)
        volatile unsigned int *a;
 
        a = __ldcw_align(x);
-       while (__ldcw(a) == 0)
+       do {
+               int lock_val_old;
+
+               lock_val_old = __ldcw(a);
+               arch_spin_val_check(lock_val_old);
+               if (lock_val_old)
+                       return; /* got lock */
+
+               /* wait until we should try to get lock again */
                while (*a == 0)
                        continue;
+       } while (1);
 }
 
 static inline void arch_spin_unlock(arch_spinlock_t *x)
@@ -29,15 +54,19 @@ static inline void arch_spin_unlock(arch_spinlock_t *x)
 
        a = __ldcw_align(x);
        /* Release with ordered store. */
-       __asm__ __volatile__("stw,ma %0,0(%1)" : : "r"(1), "r"(a) : "memory");
+       __asm__ __volatile__("stw,ma %0,0(%1)"
+               : : "r"(__ARCH_SPIN_LOCK_UNLOCKED_VAL), "r"(a) : "memory");
 }
 
 static inline int arch_spin_trylock(arch_spinlock_t *x)
 {
        volatile unsigned int *a;
+       int lock_val;
 
        a = __ldcw_align(x);
-       return __ldcw(a) != 0;
+       lock_val = __ldcw(a);
+       arch_spin_val_check(lock_val);
+       return lock_val != 0;
 }
 
 /*
index ca39ee350c3f422eee3ff28fa39cd5090600464e..d65934079ebdb032967f86976da3fc8963a84f80 100644 (file)
@@ -2,13 +2,17 @@
 #ifndef __ASM_SPINLOCK_TYPES_H
 #define __ASM_SPINLOCK_TYPES_H
 
+#define __ARCH_SPIN_LOCK_UNLOCKED_VAL  0x1a46
+
 typedef struct {
 #ifdef CONFIG_PA20
        volatile unsigned int slock;
-# define __ARCH_SPIN_LOCK_UNLOCKED { 1 }
+# define __ARCH_SPIN_LOCK_UNLOCKED { __ARCH_SPIN_LOCK_UNLOCKED_VAL }
 #else
        volatile unsigned int lock[4];
-# define __ARCH_SPIN_LOCK_UNLOCKED     { { 1, 1, 1, 1 } }
+# define __ARCH_SPIN_LOCK_UNLOCKED     \
+       { { __ARCH_SPIN_LOCK_UNLOCKED_VAL, __ARCH_SPIN_LOCK_UNLOCKED_VAL, \
+           __ARCH_SPIN_LOCK_UNLOCKED_VAL, __ARCH_SPIN_LOCK_UNLOCKED_VAL } }
 #endif
 } arch_spinlock_t;
 
index f9696fbf646c473cffbe26f316b1bcd289054431..13df6169f9e27f9cc144aa92fdf1cdc0cff6b037 100644 (file)
 #include <linux/kgdb.h>
 #include <linux/kprobes.h>
 
+#if defined(CONFIG_LIGHTWEIGHT_SPINLOCK_CHECK)
+#include <asm/spinlock.h>
+#endif
+
 #include "../math-emu/math-emu.h"      /* for handle_fpe() */
 
 static void parisc_show_stack(struct task_struct *task,
@@ -309,6 +313,12 @@ static void handle_break(struct pt_regs *regs)
        }
 #endif
 
+#ifdef CONFIG_LIGHTWEIGHT_SPINLOCK_CHECK
+        if ((iir == SPINLOCK_BREAK_INSN) && !user_mode(regs)) {
+               die_if_kernel("Spinlock was trashed", regs, 1);
+       }
+#endif
+
        if (unlikely(iir != GDB_BREAK_INSN))
                parisc_printk_ratelimited(0, regs,
                        KERN_DEBUG "break %d,%d: pid=%d command='%s'\n",