Skip to content

Commit

Permalink
x86: Stop MCEs and NMIs during code patching
Browse files Browse the repository at this point in the history
When a machine check or NMI occurs while multiple byte code is patched
the CPU could theoretically see an inconsistent instruction and crash.
Prevent this by temporarily disabling MCEs and returning early in the
NMI handler.

Based on discussion with Mathieu Desnoyers.

Cc: Mathieu Desnoyers <compudj@krystal.dyndns.org>
Cc: Jeremy Fitzhardinge <jeremy@goop.org>
Signed-off-by: Andi Kleen <ak@suse.de>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
  • Loading branch information
Andi Kleen authored and Linus Torvalds committed Jul 22, 2007
1 parent 19d36cc commit 8f4e956
Show file tree
Hide file tree
Showing 9 changed files with 86 additions and 2 deletions.
15 changes: 15 additions & 0 deletions arch/i386/kernel/alternative.c
Expand Up @@ -8,6 +8,8 @@
#include <asm/alternative.h>
#include <asm/sections.h>
#include <asm/pgtable.h>
#include <asm/mce.h>
#include <asm/nmi.h>

#ifdef CONFIG_HOTPLUG_CPU
static int smp_alt_once;
Expand Down Expand Up @@ -373,6 +375,14 @@ void __init alternative_instructions(void)
{
unsigned long flags;

/* The patching is not fully atomic, so try to avoid local interruptions
that might execute the to be patched code.
Other CPUs are not running. */
stop_nmi();
#ifdef CONFIG_MCE
stop_mce();
#endif

local_irq_save(flags);
apply_alternatives(__alt_instructions, __alt_instructions_end);

Expand Down Expand Up @@ -405,6 +415,11 @@ void __init alternative_instructions(void)
#endif
apply_paravirt(__parainstructions, __parainstructions_end);
local_irq_restore(flags);

restart_nmi();
#ifdef CONFIG_MCE
restart_mce();
#endif
}

/*
Expand Down
14 changes: 14 additions & 0 deletions arch/i386/kernel/cpu/mcheck/mce.c
Expand Up @@ -60,6 +60,20 @@ void mcheck_init(struct cpuinfo_x86 *c)
}
}

static unsigned long old_cr4 __initdata;

void __init stop_mce(void)
{
old_cr4 = read_cr4();
clear_in_cr4(X86_CR4_MCE);
}

void __init restart_mce(void)
{
if (old_cr4 & X86_CR4_MCE)
set_in_cr4(X86_CR4_MCE);
}

static int __init mcheck_disable(char *str)
{
mce_disabled = 1;
Expand Down
17 changes: 16 additions & 1 deletion arch/i386/kernel/traps.c
Expand Up @@ -775,6 +775,8 @@ static __kprobes void default_do_nmi(struct pt_regs * regs)
reassert_nmi();
}

static int ignore_nmis;

fastcall __kprobes void do_nmi(struct pt_regs * regs, long error_code)
{
int cpu;
Expand All @@ -785,11 +787,24 @@ fastcall __kprobes void do_nmi(struct pt_regs * regs, long error_code)

++nmi_count(cpu);

default_do_nmi(regs);
if (!ignore_nmis)
default_do_nmi(regs);

nmi_exit();
}

void stop_nmi(void)
{
acpi_nmi_disable();
ignore_nmis++;
}

void restart_nmi(void)
{
ignore_nmis--;
acpi_nmi_enable();
}

#ifdef CONFIG_KPROBES
fastcall void __kprobes do_int3(struct pt_regs *regs, long error_code)
{
Expand Down
14 changes: 14 additions & 0 deletions arch/x86_64/kernel/mce.c
Expand Up @@ -667,6 +667,20 @@ static struct miscdevice mce_log_device = {
&mce_chrdev_ops,
};

static unsigned long old_cr4 __initdata;

void __init stop_mce(void)
{
old_cr4 = read_cr4();
clear_in_cr4(X86_CR4_MCE);
}

void __init restart_mce(void)
{
if (old_cr4 & X86_CR4_MCE)
set_in_cr4(X86_CR4_MCE);
}

/*
* Old style boot options parsing. Only for compatibility.
*/
Expand Down
17 changes: 16 additions & 1 deletion arch/x86_64/kernel/nmi.c
Expand Up @@ -384,11 +384,14 @@ int __kprobes nmi_watchdog_tick(struct pt_regs * regs, unsigned reason)
return rc;
}

static unsigned ignore_nmis;

asmlinkage __kprobes void do_nmi(struct pt_regs * regs, long error_code)
{
nmi_enter();
add_pda(__nmi_count,1);
default_do_nmi(regs);
if (!ignore_nmis)
default_do_nmi(regs);
nmi_exit();
}

Expand All @@ -401,6 +404,18 @@ int do_nmi_callback(struct pt_regs * regs, int cpu)
return 0;
}

void stop_nmi(void)
{
acpi_nmi_disable();
ignore_nmis++;
}

void restart_nmi(void)
{
ignore_nmis--;
acpi_nmi_enable();
}

#ifdef CONFIG_SYSCTL

static int unknown_nmi_panic_callback(struct pt_regs *regs, int cpu)
Expand Down
4 changes: 4 additions & 0 deletions include/asm-i386/mce.h
Expand Up @@ -5,3 +5,7 @@ extern void mcheck_init(struct cpuinfo_x86 *c);
#endif

extern int mce_disabled;

extern void stop_mce(void);
extern void restart_mce(void);

2 changes: 2 additions & 0 deletions include/asm-i386/nmi.h
Expand Up @@ -57,5 +57,7 @@ unsigned lapic_adjust_nmi_hz(unsigned hz);
int lapic_watchdog_ok(void);
void disable_lapic_nmi_watchdog(void);
void enable_lapic_nmi_watchdog(void);
void stop_nmi(void);
void restart_nmi(void);

#endif /* ASM_NMI_H */
3 changes: 3 additions & 0 deletions include/asm-x86_64/mce.h
Expand Up @@ -107,6 +107,9 @@ extern void do_machine_check(struct pt_regs *, long);

extern int mce_notify_user(void);

extern void stop_mce(void);
extern void restart_mce(void);

#endif

#endif
2 changes: 2 additions & 0 deletions include/asm-x86_64/nmi.h
Expand Up @@ -88,5 +88,7 @@ unsigned lapic_adjust_nmi_hz(unsigned hz);
int lapic_watchdog_ok(void);
void disable_lapic_nmi_watchdog(void);
void enable_lapic_nmi_watchdog(void);
void stop_nmi(void);
void restart_nmi(void);

#endif /* ASM_NMI_H */

0 comments on commit 8f4e956

Please sign in to comment.