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 for Android: free password hash cracker in your pocket
[<prev] [next>] [thread-next>] [day] [month] [year] [list]
Date:	Thu, 4 Sep 2008 09:07:02 -0400
From:	Prarit Bhargava <prarit@...hat.com>
To:	linux-kernel@...r.kernel.org, arozansk@...hat.com,
	dzickus@...hat.com, Thomas.Mingarelli@...com, ak@...ux.intel.com,
	mingo@...e.hu
Cc:	Prarit Bhargava <prarit@...hat.com>
Subject: [PATCH RFC] NMI Re-introduce un[set]_nmi_callback

Andi and Ingo,

This patch is an RFC for the following changes.  If I get a positive review,
changes to the HP Watchdog timer (currently in the kernel) will also be
submitted along with this patch.

Thanks,

P.

The drivers/watchdog/hpwdt.c driver requires that all NMIs are processed
by the driver.  Currently the driver does not do this.  The first
step is to implement code to allow the default NMI handler to be replaced
by a custom NMI handler.

This patch re-implements a global set and unset NMI callback so that the
default NMI handler can be replaced with a custom NMI handler.  This
functionality was removed from the kernel a while ago and now must be
reintroduced into the kernel.

An existing unknown NMI callback already exists within the code.  This call
has been renamed to the more descriptive do_unknown_nmi_callback.

Patch was tested on x86 (32 and 64 bit) on Intel and AMD hardware.

Acked-by: Aris Rozanski <arozansk@...hat.com>
Signed-off-by: Don Zickus <dzickus@...hat.com>
Signed-off-by: Prarit Bhargava <prarit@...hat.com>

diff --git a/arch/x86/kernel/nmi.c b/arch/x86/kernel/nmi.c
index abb78a2..917b351 100644
--- a/arch/x86/kernel/nmi.c
+++ b/arch/x86/kernel/nmi.c
@@ -510,7 +510,7 @@ int proc_nmi_enabled(struct ctl_table *table, int write, struct file *file,
 
 #endif /* CONFIG_SYSCTL */
 
-int do_nmi_callback(struct pt_regs *regs, int cpu)
+int do_unknown_nmi_callback(struct pt_regs *regs, int cpu)
 {
 #ifdef CONFIG_SYSCTL
 	if (unknown_nmi_panic)
diff --git a/arch/x86/kernel/traps_32.c b/arch/x86/kernel/traps_32.c
index 03df8e4..f19e414 100644
--- a/arch/x86/kernel/traps_32.c
+++ b/arch/x86/kernel/traps_32.c
@@ -796,7 +796,7 @@ static notrace __kprobes void default_do_nmi(struct pt_regs *regs)
 		 */
 		if (nmi_watchdog_tick(regs, reason))
 			return;
-		if (!do_nmi_callback(regs, cpu))
+		if (!do_unknown_nmi_callback(regs, cpu))
 			unknown_nmi_error(reason, regs);
 #else
 		unknown_nmi_error(reason, regs);
@@ -819,17 +819,61 @@ static notrace __kprobes void default_do_nmi(struct pt_regs *regs)
 	reassert_nmi();
 }
 
+static __kprobes int dummy_nmi_callback(struct pt_regs * regs, int cpu)
+{
+	return 0;
+}
+
+static nmi_callback_t nmi_callback = dummy_nmi_callback;
+static int nmi_cb_status = 0;
+
+int set_nmi_callback(nmi_callback_t callback)
+{
+	if (nmi_callback != dummy_nmi_callback) {
+		printk(KERN_WARNING
+		       "WARNING: Only one NMI callback can be registered at a "
+		       "time.\n");
+		return -EBUSY;
+	}
+
+	nmi_cb_status = atomic_read(&nmi_active);
+	if (nmi_cb_status) {
+		if (nmi_watchdog == NMI_LOCAL_APIC) {
+			disable_lapic_nmi_watchdog();
+			vmalloc_sync_all();
+		} else {
+			printk(KERN_WARNING
+			       "WARNING: NMI watchdog can only be set on "
+			       "systems with status lapic NMI.\n");
+			return -ENODEV;
+		}
+	}
+	rcu_assign_pointer(nmi_callback, callback);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(set_nmi_callback);
+
+void unset_nmi_callback(void)
+{
+	if (nmi_cb_status && (nmi_watchdog == NMI_LOCAL_APIC))
+		enable_lapic_nmi_watchdog();
+	nmi_cb_status = 0;
+	nmi_callback = dummy_nmi_callback;
+}
+
+EXPORT_SYMBOL_GPL(unset_nmi_callback);
 notrace __kprobes void do_nmi(struct pt_regs *regs, long error_code)
 {
 	int cpu;
 
 	nmi_enter();
 
-	cpu = smp_processor_id();
+	cpu = safe_smp_processor_id();
 
 	++nmi_count(cpu);
 
-	if (!ignore_nmis)
+	if (!ignore_nmis && !rcu_dereference(nmi_callback)(regs,cpu))
 		default_do_nmi(regs);
 
 	nmi_exit();
diff --git a/arch/x86/kernel/traps_64.c b/arch/x86/kernel/traps_64.c
index 513caac..0f3bd27 100644
--- a/arch/x86/kernel/traps_64.c
+++ b/arch/x86/kernel/traps_64.c
@@ -815,7 +815,7 @@ asmlinkage notrace __kprobes void default_do_nmi(struct pt_regs *regs)
 		 */
 		if (nmi_watchdog_tick(regs, reason))
 			return;
-		if (!do_nmi_callback(regs, cpu))
+		if (!do_unknown_nmi_callback(regs, cpu))
 			unknown_nmi_error(reason, regs);
 
 		return;
@@ -830,14 +830,62 @@ asmlinkage notrace __kprobes void default_do_nmi(struct pt_regs *regs)
 		io_check_error(reason, regs);
 }
 
+static __kprobes int dummy_nmi_callback(struct pt_regs * regs, int cpu)
+{
+	return 0;
+}
+
+static nmi_callback_t nmi_callback = dummy_nmi_callback;
+static int nmi_cb_status = 0;
+
+int set_nmi_callback(nmi_callback_t callback)
+{
+	if (nmi_callback != dummy_nmi_callback) {
+		printk(KERN_WARNING
+		       "WARNING: Only one NMI callback can be registered at a "
+		       "time.\n");
+		return -EBUSY;
+	}
+
+	nmi_cb_status = atomic_read(&nmi_active);
+	if (nmi_cb_status) {
+		if (nmi_watchdog == NMI_LOCAL_APIC) {
+			disable_lapic_nmi_watchdog();
+			vmalloc_sync_all();
+		} else {
+			printk(KERN_WARNING
+			       "WARNING: NMI watchdog can only be set on "
+			       "systems with status lapic NMI.\n");
+			return -ENODEV;
+		}
+	}
+	rcu_assign_pointer(nmi_callback, callback);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(set_nmi_callback);
+
+void unset_nmi_callback(void)
+{
+	if (nmi_cb_status && (nmi_watchdog == NMI_LOCAL_APIC))
+		enable_lapic_nmi_watchdog();
+	nmi_cb_status = 0;
+	nmi_callback = dummy_nmi_callback;
+}
+EXPORT_SYMBOL_GPL(unset_nmi_callback);
+
 asmlinkage notrace __kprobes void
 do_nmi(struct pt_regs *regs, long error_code)
 {
+	int cpu;
+
 	nmi_enter();
 
+	cpu = safe_smp_processor_id();
+
 	add_pda(__nmi_count, 1);
 
-	if (!ignore_nmis)
+	if (!ignore_nmis && !rcu_dereference(nmi_callback)(regs,cpu))
 		default_do_nmi(regs);
 
 	nmi_exit();
diff --git a/include/asm-x86/nmi.h b/include/asm-x86/nmi.h
index 21f8d02..3c3901a 100644
--- a/include/asm-x86/nmi.h
+++ b/include/asm-x86/nmi.h
@@ -7,13 +7,30 @@
 
 #ifdef ARCH_HAS_NMI_WATCHDOG
 
+typedef int (*nmi_callback_t)(struct pt_regs * regs, int cpu);
+
+/**
+ * set_nmi_callback
+ *
+ * Set a global handler for an NMI. Only one handler may be
+ * set. Return 1 if the NMI was handled.
+ */
+int set_nmi_callback(nmi_callback_t callback);
+
+/**
+ * unset_nmi_callback
+ *
+ * Remove the global handler previously set.
+ */
+void unset_nmi_callback(void);
+
 /**
- * do_nmi_callback
+ * do_unknown_nmi_callback
  *
  * Check to see if a callback exists and execute it.  Return 1
- * if the handler exists and was handled successfully.
+ * if the unknown handler exists and was handled successfully.
  */
-int do_nmi_callback(struct pt_regs *regs, int cpu);
+int do_unknown_nmi_callback(struct pt_regs *regs, int cpu);
 
 #ifdef CONFIG_X86_64
 extern void default_do_nmi(struct pt_regs *);
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@...r.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ