lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [thread-next>] [day] [month] [year] [list]
Message-Id: <20201209183337.1912-1-mark.rutland@arm.com>
Date:   Wed,  9 Dec 2020 18:33:37 +0000
From:   Mark Rutland <mark.rutland@....com>
To:     linux-kernel@...r.kernel.org
Cc:     Mark Rutland <mark.rutland@....com>,
        Andy Lutomirski <luto@...nel.org>,
        Ingo Molnar <mingo@...hat.com>,
        Juergen Gross <jgross@...e.com>,
        Peter Zijlstra <peterz@...radead.org>,
        Thomas Gleixner <tglx@...utronix.de>
Subject: [PATCH] lockdep: report broken irq restoration

We generally expect local_irq_save() and local_irq_restore() to be
paired and sanely nested, and so local_irq_restore() expects to be
called with irqs disabled. Thus, within local_irq_restore() we only
trace irq flag changes when unmasking irqs.

This means that a seuence such as:

| local_irq_disable();
| local_irq_save(flags);
| local_irq_enable();
| local_irq_restore(flags);

... is liable to break things, as the local_irq_restore() would mask
IRQs without tracing this change.

We don't consider such sequences to be a good idea, so let's define
those as forbidden, and add tooling to detect such broken cases.

This patch adds debug code to WARN() when local_irq_restore() is called
with irqs enabled. As local_irq_restore() is expected to pair with
local_irq_save(), it should never be called with interrupts enabled.

To avoid the possibility of circular header dependencies beteen
irqflags.h and bug.h, the warning is handled in a separate C file.

The new code is all conditional on a new CONFIG_DEBUG_IRQFLAGS symbol
which is independent of CONFIG_TRACE_IRQFLAGS. As noted above such cases
will confuse lockdep, so CONFIG_DEBUG_LOCKDEP now selects
CONFIG_DEBUG_IRQFLAGS.

Signed-off-by: Mark Rutland <mark.rutland@....com>
Cc: Andy Lutomirski <luto@...nel.org>
Cc: Ingo Molnar <mingo@...hat.com>
Cc: Juergen Gross <jgross@...e.com>
Cc: Peter Zijlstra <peterz@...radead.org>
Cc: Thomas Gleixner <tglx@...utronix.de>
---
 include/linux/irqflags.h       | 18 +++++++++++++++++-
 kernel/locking/Makefile        |  1 +
 kernel/locking/irqflag-debug.c | 12 ++++++++++++
 lib/Kconfig.debug              |  7 +++++++
 4 files changed, 37 insertions(+), 1 deletion(-)
 create mode 100644 kernel/locking/irqflag-debug.c

Note: as things stand this'll blow up at boot-time on x86 within the io-apic
timer_irq_works() boot-time test. I've proposed a fix for that:

https://lore.kernel.org/lkml/20201209181514.GA14235@C02TD0UTHF1T.local/

... which was sufficient for booting under QEMU without splats. I'm giving this
a soak under Syzkaller on arm64 as that booted cleanly to begin with.

Mark.

diff --git a/include/linux/irqflags.h b/include/linux/irqflags.h
index 3ed4e8771b64..bca3c6fa8270 100644
--- a/include/linux/irqflags.h
+++ b/include/linux/irqflags.h
@@ -220,10 +220,26 @@ do {						\
 
 #else /* !CONFIG_TRACE_IRQFLAGS */
 
+#ifdef CONFIG_DEBUG_IRQFLAGS
+extern void warn_bogus_irq_restore(bool *warned);
+#define check_bogus_irq_restore()				\
+	do {							\
+		static bool __section(".data.once") __warned;	\
+		if (unlikely(!raw_irqs_disabled()))		\
+			warn_bogus_irq_restore(&__warned);	\
+	} while (0)
+#else
+#define check_bogus_irq_restore() do { } while (0)
+#endif
+
 #define local_irq_enable()	do { raw_local_irq_enable(); } while (0)
 #define local_irq_disable()	do { raw_local_irq_disable(); } while (0)
 #define local_irq_save(flags)	do { raw_local_irq_save(flags); } while (0)
-#define local_irq_restore(flags) do { raw_local_irq_restore(flags); } while (0)
+#define local_irq_restore(flags)		\
+	do {					\
+		check_bogus_irq_restore();	\
+		raw_local_irq_restore(flags);	\
+	} while (0)
 #define safe_halt()		do { raw_safe_halt(); } while (0)
 
 #endif /* CONFIG_TRACE_IRQFLAGS */
diff --git a/kernel/locking/Makefile b/kernel/locking/Makefile
index 6d11cfb9b41f..8838f1d7c4a2 100644
--- a/kernel/locking/Makefile
+++ b/kernel/locking/Makefile
@@ -15,6 +15,7 @@ CFLAGS_REMOVE_mutex-debug.o = $(CC_FLAGS_FTRACE)
 CFLAGS_REMOVE_rtmutex-debug.o = $(CC_FLAGS_FTRACE)
 endif
 
+obj-$(CONFIG_DEBUG_IRQFLAGS) += irqflag-debug.o
 obj-$(CONFIG_DEBUG_MUTEXES) += mutex-debug.o
 obj-$(CONFIG_LOCKDEP) += lockdep.o
 ifeq ($(CONFIG_PROC_FS),y)
diff --git a/kernel/locking/irqflag-debug.c b/kernel/locking/irqflag-debug.c
new file mode 100644
index 000000000000..3024c6837ac2
--- /dev/null
+++ b/kernel/locking/irqflag-debug.c
@@ -0,0 +1,12 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <linux/bug.h>
+
+void warn_bogus_irq_restore(bool *warned)
+{
+	if (*warned)
+		return;
+
+	*warned = true;
+	WARN(1, "local_irq_restore() called with IRQs enabled\n");
+}
diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug
index c789b39ed527..f7895d57bb08 100644
--- a/lib/Kconfig.debug
+++ b/lib/Kconfig.debug
@@ -1312,6 +1312,7 @@ config LOCKDEP_SMALL
 config DEBUG_LOCKDEP
 	bool "Lock dependency engine debugging"
 	depends on DEBUG_KERNEL && LOCKDEP
+	select DEBUG_IRQFLAGS
 	help
 	  If you say Y here, the lock dependency engine will do
 	  additional runtime checks to debug itself, at the price
@@ -1400,6 +1401,12 @@ config TRACE_IRQFLAGS_NMI
 	depends on TRACE_IRQFLAGS
 	depends on TRACE_IRQFLAGS_NMI_SUPPORT
 
+config DEBUG_IRQFLAGS
+	bool "Interrupt mask debugging"
+	help
+	  Enables checks for potentially unsafe enabling or disabling of
+	  interrupts.
+
 config STACKTRACE
 	bool "Stack backtrace support"
 	depends on STACKTRACE_SUPPORT
-- 
2.11.0

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ