x86/kprobes: Prohibit probing on compiler generated CFI checking code
authorMasami Hiramatsu <mhiramat@kernel.org>
Tue, 11 Jul 2023 01:50:58 +0000 (10:50 +0900)
committerPeter Zijlstra <peterz@infradead.org>
Wed, 2 Aug 2023 14:27:07 +0000 (16:27 +0200)
Prohibit probing on the compiler generated CFI typeid checking code
because it is used for decoding typeid when CFI error happens.

The compiler generates the following instruction sequence for indirect
call checks on x86;

   movl    -<id>, %r10d       ; 6 bytes
   addl    -4(%reg), %r10d    ; 4 bytes
   je      .Ltmp1             ; 2 bytes
   ud2                        ; <- regs->ip

And handle_cfi_failure() decodes these instructions (movl and addl)
for the typeid and the target address. Thus if we put a kprobe on
those instructions, the decode will fail and report a wrong typeid
and target address.

Signed-off-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org>
Link: https://lore.kernel.org/r/168904025785.116016.12766408611437534723.stgit@devnote2
arch/x86/kernel/kprobes/core.c
include/linux/cfi.h

index f7f6042eb7e6c0e4a3f816bb7c9b7713498047be..e8babebad7b888e3286a820c5956a2481bdd1bc4 100644 (file)
@@ -45,6 +45,7 @@
 #include <linux/vmalloc.h>
 #include <linux/pgtable.h>
 #include <linux/set_memory.h>
+#include <linux/cfi.h>
 
 #include <asm/text-patching.h>
 #include <asm/cacheflush.h>
@@ -293,7 +294,40 @@ static int can_probe(unsigned long paddr)
 #endif
                addr += insn.length;
        }
+       if (IS_ENABLED(CONFIG_CFI_CLANG)) {
+               /*
+                * The compiler generates the following instruction sequence
+                * for indirect call checks and cfi.c decodes this;
+                *
+                *   movl    -<id>, %r10d       ; 6 bytes
+                *   addl    -4(%reg), %r10d    ; 4 bytes
+                *   je      .Ltmp1             ; 2 bytes
+                *   ud2                        ; <- regs->ip
+                *   .Ltmp1:
+                *
+                * Also, these movl and addl are used for showing expected
+                * type. So those must not be touched.
+                */
+               __addr = recover_probed_instruction(buf, addr);
+               if (!__addr)
+                       return 0;
+
+               if (insn_decode_kernel(&insn, (void *)__addr) < 0)
+                       return 0;
+
+               if (insn.opcode.value == 0xBA)
+                       offset = 12;
+               else if (insn.opcode.value == 0x3)
+                       offset = 6;
+               else
+                       goto out;
+
+               /* This movl/addl is used for decoding CFI. */
+               if (is_cfi_trap(addr + offset))
+                       return 0;
+       }
 
+out:
        return (addr == paddr);
 }
 
index 5e134f4ce8b75943cb7470ae0490e84dc50daa1a..3552ec82b72561e433047b7cd0f65dae37978026 100644 (file)
@@ -19,11 +19,13 @@ static inline enum bug_trap_type report_cfi_failure_noaddr(struct pt_regs *regs,
 {
        return report_cfi_failure(regs, addr, NULL, 0);
 }
+#endif /* CONFIG_CFI_CLANG */
 
 #ifdef CONFIG_ARCH_USES_CFI_TRAPS
 bool is_cfi_trap(unsigned long addr);
+#else
+static inline bool is_cfi_trap(unsigned long addr) { return false; }
 #endif
-#endif /* CONFIG_CFI_CLANG */
 
 #ifdef CONFIG_MODULES
 #ifdef CONFIG_ARCH_USES_CFI_TRAPS