arch/x86/kernel/alternative.c | 54 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 51 insertions(+), 3 deletions(-) diff --git a/arch/x86/kernel/alternative.c b/arch/x86/kernel/alternative.c index 9a79c7808f9c..92b59958cff3 100644 --- a/arch/x86/kernel/alternative.c +++ b/arch/x86/kernel/alternative.c @@ -739,7 +739,11 @@ static void do_sync_core(void *info) } static bool bp_patching_in_progress; -static void *bp_int3_handler, *bp_int3_addr; +static void *bp_int3_handler_irqoff, *bp_int3_handler_irqon, *bp_int3_addr; +static void *bp_int3_call_target, *bp_int3_call_return; + +static DEFINE_PER_CPU(void *, bp_call_return); +static DEFINE_PER_CPU(void *, bp_call_target); int poke_int3_handler(struct pt_regs *regs) { @@ -762,7 +766,22 @@ int poke_int3_handler(struct pt_regs *regs) return 0; /* set up the specified breakpoint handler */ - regs->ip = (unsigned long) bp_int3_handler; + regs->ip = (unsigned long) bp_int3_handler_irqon; + + /* + * If we want an irqoff irq3 handler, and interrupts were + * on, we turn them off and use the special irqoff handler + * instead. + */ + if (bp_int3_handler_irqoff) { + this_cpu_write(bp_call_target, bp_int3_call_target); + this_cpu_write(bp_call_return, bp_int3_call_return); + + if (regs->flags & X86_EFLAGS_IF) { + regs->flags &= ~X86_EFLAGS_IF; + regs->ip = (unsigned long) bp_int3_handler_irqoff; + } + } return 1; } @@ -792,7 +811,7 @@ void *text_poke_bp(void *addr, const void *opcode, size_t len, void *handler) { unsigned char int3 = 0xcc; - bp_int3_handler = handler; + bp_int3_handler_irqon = handler; bp_int3_addr = (u8 *)addr + sizeof(int3); bp_patching_in_progress = true; @@ -830,7 +849,36 @@ void *text_poke_bp(void *addr, const void *opcode, size_t len, void *handler) * the writing of the new instruction. */ bp_patching_in_progress = false; + bp_int3_handler_irqoff = NULL; return addr; } +extern asmlinkage void emulate_call_irqon(void); +extern asmlinkage void emulate_call_irqoff(void); + +asm( + ".text\n" + ".global emulate_call_irqoff\n" + ".type emulate_call_irqoff, @function\n" + "emulate_call_irqoff:\n\t" + "push %gs:bp_call_return\n\t" + "sti\n\t" + "jmp *%gs:bp_call_target\n" + ".size emulate_call_irqoff, .-emulate_call_irqoff\n" + + ".global emulate_call_irqon\n" + ".type emulate_call_irqon, @function\n" + "emulate_call_irqon:\n\t" + "push %gs:bp_call_return\n\t" + "jmp *%gs:bp_call_target\n" + ".size emulate_call_irqon, .-emulate_call_irqon\n" + ".previous\n"); + +void replace_call(void *addr, const void *opcode, size_t len, void *target) +{ + bp_int3_call_target = target; + bp_int3_call_return = addr + len; + bp_int3_handler_irqoff = emulate_call_irqoff; + text_poke_bp(addr, opcode, len, emulate_call_irqon); +}