x86/ibt,kprobes: Cure sym+0 equals fentry woes
authorPeter Zijlstra <peterz@infradead.org>
Tue, 8 Mar 2022 15:30:32 +0000 (16:30 +0100)
committerPeter Zijlstra <peterz@infradead.org>
Tue, 15 Mar 2022 09:32:38 +0000 (10:32 +0100)
In order to allow kprobes to skip the ENDBR instructions at sym+0 for
X86_KERNEL_IBT builds, change _kprobe_addr() to take an architecture
callback to inspect the function at hand and modify the offset if
needed.

This streamlines the existing interface to cover more cases and
require less hooks. Once PowerPC gets fully converted there will only
be the one arch hook.

Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org>
Acked-by: Masami Hiramatsu <mhiramat@kernel.org>
Acked-by: Josh Poimboeuf <jpoimboe@redhat.com>
Link: https://lore.kernel.org/r/20220308154318.405947704@infradead.org
arch/powerpc/kernel/kprobes.c
arch/x86/kernel/kprobes/core.c
include/linux/kprobes.h
kernel/kprobes.c

index 9a492fdec1dfbed6bb2c3afa1e40040eccb4c458..7dae0b01abfbd6ead362ed3eb1ec58d5b427e15c 100644 (file)
@@ -105,6 +105,27 @@ kprobe_opcode_t *kprobe_lookup_name(const char *name, unsigned int offset)
        return addr;
 }
 
+static bool arch_kprobe_on_func_entry(unsigned long offset)
+{
+#ifdef PPC64_ELF_ABI_v2
+#ifdef CONFIG_KPROBES_ON_FTRACE
+       return offset <= 16;
+#else
+       return offset <= 8;
+#endif
+#else
+       return !offset;
+#endif
+}
+
+/* XXX try and fold the magic of kprobe_lookup_name() in this */
+kprobe_opcode_t *arch_adjust_kprobe_addr(unsigned long addr, unsigned long offset,
+                                        bool *on_func_entry)
+{
+       *on_func_entry = arch_kprobe_on_func_entry(offset);
+       return (kprobe_opcode_t *)(addr + offset);
+}
+
 void *alloc_insn_page(void)
 {
        void *page;
@@ -218,19 +239,6 @@ static nokprobe_inline void set_current_kprobe(struct kprobe *p, struct pt_regs
        kcb->kprobe_saved_msr = regs->msr;
 }
 
-bool arch_kprobe_on_func_entry(unsigned long offset)
-{
-#ifdef PPC64_ELF_ABI_v2
-#ifdef CONFIG_KPROBES_ON_FTRACE
-       return offset <= 16;
-#else
-       return offset <= 8;
-#endif
-#else
-       return !offset;
-#endif
-}
-
 void arch_prepare_kretprobe(struct kretprobe_instance *ri, struct pt_regs *regs)
 {
        ri->ret_addr = (kprobe_opcode_t *)regs->link;
index 4d8086a1627e6b2ee579e62e1497a86b84d22953..9ea0e3e798961e061d9e49cfefd6cdefc23fa9fe 100644 (file)
@@ -52,6 +52,7 @@
 #include <asm/insn.h>
 #include <asm/debugreg.h>
 #include <asm/set_memory.h>
+#include <asm/ibt.h>
 
 #include "common.h"
 
@@ -294,6 +295,22 @@ static int can_probe(unsigned long paddr)
        return (addr == paddr);
 }
 
+/* If x86 supports IBT (ENDBR) it must be skipped. */
+kprobe_opcode_t *arch_adjust_kprobe_addr(unsigned long addr, unsigned long offset,
+                                        bool *on_func_entry)
+{
+       if (is_endbr(*(u32 *)addr)) {
+               *on_func_entry = !offset || offset == 4;
+               if (*on_func_entry)
+                       offset = 4;
+
+       } else {
+               *on_func_entry = !offset;
+       }
+
+       return (kprobe_opcode_t *)(addr + offset);
+}
+
 /*
  * Copy an instruction with recovering modified instruction by kprobes
  * and adjust the displacement if the instruction uses the %rip-relative
index 19b884353b159ae9fefdee3ae42684ce9384dcb0..9c28f7a0ef4268fd03ad3cf50a72a6170d9476f1 100644 (file)
@@ -265,7 +265,6 @@ extern int arch_init_kprobes(void);
 extern void kprobes_inc_nmissed_count(struct kprobe *p);
 extern bool arch_within_kprobe_blacklist(unsigned long addr);
 extern int arch_populate_kprobe_blacklist(void);
-extern bool arch_kprobe_on_func_entry(unsigned long offset);
 extern int kprobe_on_func_entry(kprobe_opcode_t *addr, const char *sym, unsigned long offset);
 
 extern bool within_kprobe_blacklist(unsigned long addr);
@@ -384,6 +383,8 @@ static inline struct kprobe_ctlblk *get_kprobe_ctlblk(void)
 }
 
 kprobe_opcode_t *kprobe_lookup_name(const char *name, unsigned int offset);
+kprobe_opcode_t *arch_adjust_kprobe_addr(unsigned long addr, unsigned long offset, bool *on_func_entry);
+
 int register_kprobe(struct kprobe *p);
 void unregister_kprobe(struct kprobe *p);
 int register_kprobes(struct kprobe **kps, int num);
index 6d1e11cda4f1871dbf1c15b643179afd5b8db1bb..185badc780b7c71923027cda873f5ec3c52bb204 100644 (file)
@@ -1488,25 +1488,69 @@ bool within_kprobe_blacklist(unsigned long addr)
        return false;
 }
 
+/*
+ * arch_adjust_kprobe_addr - adjust the address
+ * @addr: symbol base address
+ * @offset: offset within the symbol
+ * @on_func_entry: was this @addr+@offset on the function entry
+ *
+ * Typically returns @addr + @offset, except for special cases where the
+ * function might be prefixed by a CFI landing pad, in that case any offset
+ * inside the landing pad is mapped to the first 'real' instruction of the
+ * symbol.
+ *
+ * Specifically, for things like IBT/BTI, skip the resp. ENDBR/BTI.C
+ * instruction at +0.
+ */
+kprobe_opcode_t *__weak arch_adjust_kprobe_addr(unsigned long addr,
+                                               unsigned long offset,
+                                               bool *on_func_entry)
+{
+       *on_func_entry = !offset;
+       return (kprobe_opcode_t *)(addr + offset);
+}
+
 /*
  * If 'symbol_name' is specified, look it up and add the 'offset'
  * to it. This way, we can specify a relative address to a symbol.
  * This returns encoded errors if it fails to look up symbol or invalid
  * combination of parameters.
  */
-static kprobe_opcode_t *_kprobe_addr(kprobe_opcode_t *addr,
-                       const char *symbol_name, unsigned int offset)
+static kprobe_opcode_t *
+_kprobe_addr(kprobe_opcode_t *addr, const char *symbol_name,
+            unsigned long offset, bool *on_func_entry)
 {
        if ((symbol_name && addr) || (!symbol_name && !addr))
                goto invalid;
 
        if (symbol_name) {
+               /*
+                * Input: @sym + @offset
+                * Output: @addr + @offset
+                *
+                * NOTE: kprobe_lookup_name() does *NOT* fold the offset
+                *       argument into it's output!
+                */
                addr = kprobe_lookup_name(symbol_name, offset);
                if (!addr)
                        return ERR_PTR(-ENOENT);
        }
 
-       addr = (kprobe_opcode_t *)(((char *)addr) + offset);
+       /*
+        * So here we have @addr + @offset, displace it into a new
+        * @addr' + @offset' where @addr' is the symbol start address.
+        */
+       addr = (void *)addr + offset;
+       if (!kallsyms_lookup_size_offset((unsigned long)addr, NULL, &offset))
+               return ERR_PTR(-ENOENT);
+       addr = (void *)addr - offset;
+
+       /*
+        * Then ask the architecture to re-combine them, taking care of
+        * magical function entry details while telling us if this was indeed
+        * at the start of the function.
+        */
+       addr = arch_adjust_kprobe_addr((unsigned long)addr, offset, on_func_entry);
        if (addr)
                return addr;
 
@@ -1516,7 +1560,8 @@ invalid:
 
 static kprobe_opcode_t *kprobe_addr(struct kprobe *p)
 {
-       return _kprobe_addr(p->addr, p->symbol_name, p->offset);
+       bool on_func_entry;
+       return _kprobe_addr(p->addr, p->symbol_name, p->offset, &on_func_entry);
 }
 
 /*
@@ -2043,11 +2088,6 @@ static int pre_handler_kretprobe(struct kprobe *p, struct pt_regs *regs)
 }
 NOKPROBE_SYMBOL(pre_handler_kretprobe);
 
-bool __weak arch_kprobe_on_func_entry(unsigned long offset)
-{
-       return !offset;
-}
-
 /**
  * kprobe_on_func_entry() -- check whether given address is function entry
  * @addr: Target address
@@ -2063,15 +2103,13 @@ bool __weak arch_kprobe_on_func_entry(unsigned long offset)
  */
 int kprobe_on_func_entry(kprobe_opcode_t *addr, const char *sym, unsigned long offset)
 {
-       kprobe_opcode_t *kp_addr = _kprobe_addr(addr, sym, offset);
+       bool on_func_entry;
+       kprobe_opcode_t *kp_addr = _kprobe_addr(addr, sym, offset, &on_func_entry);
 
        if (IS_ERR(kp_addr))
                return PTR_ERR(kp_addr);
 
-       if (!kallsyms_lookup_size_offset((unsigned long)kp_addr, NULL, &offset))
-               return -ENOENT;
-
-       if (!arch_kprobe_on_func_entry(offset))
+       if (!on_func_entry)
                return -EINVAL;
 
        return 0;