[PATCH] kprobes/x86: Fixes NMI context check on x86

From: Masami Hiramatsu
Date: Mon Aug 24 2020 - 12:37:00 EST


Since commit 0d00449c7a28 ("x86: Replace ist_enter() with nmi_enter()")
made int3 as one of NMI, in_nmi() in kprobe handlers always returns !0.
Thus the kretprobe handlers always skipped the execution on x86 if it
is using int3. (CONFIG_KPROBES_ON_FTRACE=n and
echo 0 > /proc/sys/debug/kprobe_optimization)

To avoid this issue, introduce arch_kprobe_in_nmi() and check the
in_nmi() count is bigger than 1 << NMI_SHIFT on x86 if the handler
has been invoked from kprobe_int3_handler. By default, the
arch_kprobe_in_nmi() will be same as in_nmi().

Fixes: 0d00449c7a28 ("x86: Replace ist_enter() with nmi_enter()")
Reported-by: Eddy Wu <Eddy_Wu@xxxxxxxxxxxxxx>
Signed-off-by: Masami Hiramatsu <mhiramat@xxxxxxxxxx>
Cc: stable@xxxxxxxxxxxxxxx
---
arch/x86/include/asm/kprobes.h | 1 +
arch/x86/kernel/kprobes/core.c | 18 ++++++++++++++++++
kernel/kprobes.c | 8 +++++++-
3 files changed, 26 insertions(+), 1 deletion(-)

diff --git a/arch/x86/include/asm/kprobes.h b/arch/x86/include/asm/kprobes.h
index 143bc9abe99c..ddb24feb95ad 100644
--- a/arch/x86/include/asm/kprobes.h
+++ b/arch/x86/include/asm/kprobes.h
@@ -98,6 +98,7 @@ struct kprobe_ctlblk {
unsigned long kprobe_old_flags;
unsigned long kprobe_saved_flags;
struct prev_kprobe prev_kprobe;
+ bool in_int3;
};

extern int kprobe_fault_handler(struct pt_regs *regs, int trapnr);
diff --git a/arch/x86/kernel/kprobes/core.c b/arch/x86/kernel/kprobes/core.c
index 2ca10b770cff..649d467c8231 100644
--- a/arch/x86/kernel/kprobes/core.c
+++ b/arch/x86/kernel/kprobes/core.c
@@ -583,6 +583,20 @@ static nokprobe_inline void restore_btf(void)
}
}

+bool arch_kprobe_in_nmi(void)
+{
+ struct kprobe_ctlblk *kcb = get_kprobe_ctlblk();
+
+ if (kcb->in_int3) {
+ /*
+ * Since the int3 is one of NMI, we have to check in_nmi() is
+ * bigger than 1 << NMI_SHIFT instead of !0.
+ */
+ return in_nmi() > (1 << NMI_SHIFT);
+ } else
+ return in_nmi();
+}
+
void arch_prepare_kretprobe(struct kretprobe_instance *ri, struct pt_regs *regs)
{
unsigned long *sara = stack_addr(regs);
@@ -697,6 +711,7 @@ int kprobe_int3_handler(struct pt_regs *regs)
return 1;
} else {
set_current_kprobe(p, regs, kcb);
+ kcb->in_int3 = true;
kcb->kprobe_status = KPROBE_HIT_ACTIVE;

/*
@@ -710,6 +725,7 @@ int kprobe_int3_handler(struct pt_regs *regs)
setup_singlestep(p, regs, kcb, 0);
else
reset_current_kprobe();
+ kcb->in_int3 = false;
return 1;
}
} else if (*addr != INT3_INSN_OPCODE) {
@@ -994,7 +1010,9 @@ int kprobe_debug_handler(struct pt_regs *regs)

if ((kcb->kprobe_status != KPROBE_REENTER) && cur->post_handler) {
kcb->kprobe_status = KPROBE_HIT_SSDONE;
+ kcb->in_int3 = true;
cur->post_handler(cur, regs, 0);
+ kcb->in_int3 = false;
}

/* Restore back the original saved kprobes variables and continue. */
diff --git a/kernel/kprobes.c b/kernel/kprobes.c
index 287b263c9cb9..9564928fb882 100644
--- a/kernel/kprobes.c
+++ b/kernel/kprobes.c
@@ -1927,6 +1927,12 @@ unsigned long __weak arch_deref_entry_point(void *entry)
}

#ifdef CONFIG_KRETPROBES
+
+bool __weak arch_kprobe_in_nmi(void)
+{
+ return in_nmi();
+}
+
/*
* This kprobe pre_handler is registered with every kretprobe. When probe
* hits it will set up the return probe.
@@ -1943,7 +1949,7 @@ static int pre_handler_kretprobe(struct kprobe *p, struct pt_regs *regs)
* statistical counter, so that the user is informed that
* something happened:
*/
- if (unlikely(in_nmi())) {
+ if (unlikely(arch_kprobe_in_nmi())) {
rp->nmissed++;
return 0;
}
--
2.25.1


--
Masami Hiramatsu <mhiramat@xxxxxxxxxx>