[llvm] [BOLT] Gadget scanner: detect authentication oracles (PR #135663)
Kristof Beyls via llvm-commits
llvm-commits at lists.llvm.org
Wed May 28 02:22:43 PDT 2025
================
@@ -0,0 +1,739 @@
+// RUN: %clang %cflags -march=armv8.3-a %s -o %t.exe
+// RUN: llvm-bolt-binary-analysis --scanners=pacret %t.exe 2>&1 | FileCheck -check-prefix=PACRET %s
+// RUN: llvm-bolt-binary-analysis --scanners=pauth %t.exe 2>&1 | FileCheck %s
+
+// The detection of compiler-generated explicit pointer checks is tested in
+// gs-pauth-address-checks.s, for that reason only test here "dummy-load" and
+// "high-bits-notbi" checkers, as the shortest examples of checkers that are
+// detected per-instruction and per-BB.
+
+// PACRET-NOT: authentication oracle found in function
+
+ .text
+
+ .type sym, at function
+sym:
+ ret
+ .size sym, .-sym
+
+ .globl callee
+ .type callee, at function
+callee:
+ ret
+ .size callee, .-callee
+
+ .globl good_ret
+ .type good_ret, at function
+good_ret:
+// CHECK-NOT: good_ret
+ autia x0, x1
+ ret x0
+ .size good_ret, .-good_ret
+
+ .globl good_call
+ .type good_call, at function
+good_call:
+// CHECK-NOT: good_call
+ paciasp
+ stp x29, x30, [sp, #-16]!
+ mov x29, sp
+
+ autia x0, x1
+ blr x0
+
+ ldp x29, x30, [sp], #16
+ autiasp
+ ret
+ .size good_call, .-good_call
+
+ .globl good_branch
+ .type good_branch, at function
+good_branch:
+// CHECK-NOT: good_branch
+ autia x0, x1
+ br x0
+ .size good_branch, .-good_branch
+
+ .globl good_load_other_reg
+ .type good_load_other_reg, at function
+good_load_other_reg:
+// CHECK-NOT: good_load_other_reg
+ autia x0, x1
+ ldr x2, [x0]
+ ret
+ .size good_load_other_reg, .-good_load_other_reg
+
+ .globl good_load_same_reg
+ .type good_load_same_reg, at function
+good_load_same_reg:
+// CHECK-NOT: good_load_same_reg
+ autia x0, x1
+ ldr x0, [x0]
+ ret
+ .size good_load_same_reg, .-good_load_same_reg
+
+ .globl good_explicit_check
+ .type good_explicit_check, at function
+good_explicit_check:
+// CHECK-NOT: good_explicit_check
+ autia x0, x1
+ eor x16, x0, x0, lsl #1
+ tbz x16, #62, 1f
+ brk 0x1234
+1:
+ ret
+ .size good_explicit_check, .-good_explicit_check
+
+ .globl bad_unchecked
+ .type bad_unchecked, at function
+bad_unchecked:
+// CHECK-LABEL: GS-PAUTH: authentication oracle found in function bad_unchecked, basic block {{[^,]+}}, at address
+// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: autia x0, x1
+// CHECK-NEXT: The 0 instructions that leak the affected registers are:
+ autia x0, x1
+ ret
+ .size bad_unchecked, .-bad_unchecked
+
+ .globl bad_leaked_to_subroutine
+ .type bad_leaked_to_subroutine, at function
+bad_leaked_to_subroutine:
+// CHECK-LABEL: GS-PAUTH: authentication oracle found in function bad_leaked_to_subroutine, basic block {{[^,]+}}, at address
+// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: autia x0, x1
+// CHECK-NEXT: The 1 instructions that leak the affected registers are:
+// CHECK-NEXT: 1. {{[0-9a-f]+}}: bl callee
+// CHECK-NEXT: This happens in the following basic block:
+// CHECK-NEXT: {{[0-9a-f]+}}: paciasp
+// CHECK-NEXT: {{[0-9a-f]+}}: stp x29, x30, [sp, #-0x10]!
+// CHECK-NEXT: {{[0-9a-f]+}}: mov x29, sp
+// CHECK-NEXT: {{[0-9a-f]+}}: autia x0, x1
+// CHECK-NEXT: {{[0-9a-f]+}}: bl callee
+// CHECK-NEXT: {{[0-9a-f]+}}: ldr x2, [x0]
+// CHECK-NEXT: {{[0-9a-f]+}}: ldp x29, x30, [sp], #0x10
+// CHECK-NEXT: {{[0-9a-f]+}}: autiasp
+// CHECK-NEXT: {{[0-9a-f]+}}: ret
+ paciasp
+ stp x29, x30, [sp, #-16]!
+ mov x29, sp
+
+ autia x0, x1
+ bl callee
+ ldr x2, [x0]
+
+ ldp x29, x30, [sp], #16
+ autiasp
+ ret
+ .size bad_leaked_to_subroutine, .-bad_leaked_to_subroutine
+
+ .globl bad_unknown_usage_read
+ .type bad_unknown_usage_read, at function
+bad_unknown_usage_read:
+// CHECK-LABEL: GS-PAUTH: authentication oracle found in function bad_unknown_usage_read, basic block {{[^,]+}}, at address
+// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: autia x0, x1
+// CHECK-NEXT: The 1 instructions that leak the affected registers are:
+// CHECK-NEXT: 1. {{[0-9a-f]+}}: mul x3, x0, x1
+// CHECK-NEXT: This happens in the following basic block:
+// CHECK-NEXT: {{[0-9a-f]+}}: autia x0, x1
+// CHECK-NEXT: {{[0-9a-f]+}}: mul x3, x0, x1
+// CHECK-NEXT: {{[0-9a-f]+}}: ldr x2, [x0]
+// CHECK-NEXT: {{[0-9a-f]+}}: ret
+ autia x0, x1
+ mul x3, x0, x1
+ ldr x2, [x0]
+ ret
+ .size bad_unknown_usage_read, .-bad_unknown_usage_read
+
+ .globl bad_unknown_usage_subreg_read
+ .type bad_unknown_usage_subreg_read, at function
+bad_unknown_usage_subreg_read:
+// CHECK-LABEL: GS-PAUTH: authentication oracle found in function bad_unknown_usage_subreg_read, basic block {{[^,]+}}, at address
+// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: autia x0, x1
+// CHECK-NEXT: The 1 instructions that leak the affected registers are:
+// CHECK-NEXT: 1. {{[0-9a-f]+}}: mul w3, w0, w1
+// CHECK-NEXT: This happens in the following basic block:
+// CHECK-NEXT: {{[0-9a-f]+}}: autia x0, x1
+// CHECK-NEXT: {{[0-9a-f]+}}: mul w3, w0, w1
+// CHECK-NEXT: {{[0-9a-f]+}}: ldr x2, [x0]
+// CHECK-NEXT: {{[0-9a-f]+}}: ret
+ autia x0, x1
+ mul w3, w0, w1
+ ldr x2, [x0]
+ ret
+ .size bad_unknown_usage_subreg_read, .-bad_unknown_usage_subreg_read
+
+ .globl bad_unknown_usage_update
+ .type bad_unknown_usage_update, at function
+bad_unknown_usage_update:
+// CHECK-LABEL: GS-PAUTH: authentication oracle found in function bad_unknown_usage_update, basic block {{[^,]+}}, at address
+// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: autia x0, x1
+// CHECK-NEXT: The 1 instructions that leak the affected registers are:
+// CHECK-NEXT: 1. {{[0-9a-f]+}}: movk x0, #0x2a, lsl #16
+// CHECK-NEXT: This happens in the following basic block:
+// CHECK-NEXT: {{[0-9a-f]+}}: autia x0, x1
+// CHECK-NEXT: {{[0-9a-f]+}}: movk x0, #0x2a, lsl #16
+// CHECK-NEXT: {{[0-9a-f]+}}: ldr x2, [x0]
+// CHECK-NEXT: {{[0-9a-f]+}}: ret
+ autia x0, x1
+ movk x0, #42, lsl #16 // does not overwrite x0 completely
+ ldr x2, [x0]
+ ret
+ .size bad_unknown_usage_update, .-bad_unknown_usage_update
+
+ .globl good_unknown_overwrite
+ .type good_unknown_overwrite, at function
+good_unknown_overwrite:
+// CHECK-NOT: good_unknown_overwrite
+ autia x0, x1
+ mul x0, x1, x2
+ ret
+ .size good_unknown_overwrite, .-good_unknown_overwrite
+
+// This is a false positive: when a general-purpose register is written to as
+// a 32-bit register, its top 32 bits are zeroed, but according to LLVM
+// representation, the instruction only overwrites the Wn register.
+ .globl good_unknown_wreg_overwrite
+ .type good_unknown_wreg_overwrite, at function
+good_unknown_wreg_overwrite:
+// CHECK-LABEL: GS-PAUTH: authentication oracle found in function good_unknown_wreg_overwrite, basic block {{[^,]+}}, at address
+// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: autia x0, x1
+ autia x0, x1
+ mul w0, w1, w2
+ ret
+ .size good_unknown_wreg_overwrite, .-good_unknown_wreg_overwrite
+
+ .globl good_address_arith
+ .type good_address_arith, at function
+good_address_arith:
+// CHECK-NOT: good_address_arith
+ autia x0, x1
+
+ add x1, x0, #8
+ sub x2, x1, #16
+ mov x3, x2
+
+ ldr x4, [x3]
+ mov x0, #0
+ mov x1, #0
+ mov x2, #0
+
+ ret
+ .size good_address_arith, .-good_address_arith
+
+ .globl good_ret_multi_bb
+ .type good_ret_multi_bb, at function
+good_ret_multi_bb:
+// CHECK-NOT: good_ret_multi_bb
+ autia x0, x1
+ cbz x1, 1f
+ nop
+1:
+ ret x0
+ .size good_ret_multi_bb, .-good_ret_multi_bb
+
+ .globl good_call_multi_bb
+ .type good_call_multi_bb, at function
+good_call_multi_bb:
+// CHECK-NOT: good_call_multi_bb
+ paciasp
+ stp x29, x30, [sp, #-16]!
+ mov x29, sp
+
+ autia x0, x1
+ cbz x1, 1f
+ nop
+1:
+ blr x0
+ cbz x1, 2f
+ nop
+2:
+ ldp x29, x30, [sp], #16
+ autiasp
+ ret
+ .size good_call_multi_bb, .-good_call_multi_bb
+
+ .globl good_branch_multi_bb
+ .type good_branch_multi_bb, at function
+good_branch_multi_bb:
+// CHECK-NOT: good_branch_multi_bb
+ autia x0, x1
+ cbz x1, 1f
+ nop
+1:
+ br x0
+ .size good_branch_multi_bb, .-good_branch_multi_bb
+
+ .globl good_load_other_reg_multi_bb
+ .type good_load_other_reg_multi_bb, at function
+good_load_other_reg_multi_bb:
+// CHECK-NOT: good_load_other_reg_multi_bb
+ autia x0, x1
+ cbz x1, 1f
+ nop
+1:
+ ldr x2, [x0]
+ cbz x1, 2f
+ nop
+2:
+ ret
+ .size good_load_other_reg_multi_bb, .-good_load_other_reg_multi_bb
+
+ .globl good_load_same_reg_multi_bb
+ .type good_load_same_reg_multi_bb, at function
+good_load_same_reg_multi_bb:
+// CHECK-NOT: good_load_same_reg_multi_bb
+ autia x0, x1
+ cbz x1, 1f
+ nop
+1:
+ ldr x0, [x0]
+ cbz x1, 2f
+ nop
+2:
+ ret
+ .size good_load_same_reg_multi_bb, .-good_load_same_reg_multi_bb
+
+ .globl good_explicit_check_multi_bb
+ .type good_explicit_check_multi_bb, at function
+good_explicit_check_multi_bb:
+// CHECK-NOT: good_explicit_check_multi_bb
+ autia x0, x1
+ cbz x1, 1f
+ nop
+1:
+ eor x16, x0, x0, lsl #1
+ tbz x16, #62, 2f
+ brk 0x1234
+2:
+ cbz x1, 3f
+ nop
+3:
+ ret
+ .size good_explicit_check_multi_bb, .-good_explicit_check_multi_bb
+
+ .globl bad_unchecked_multi_bb
+ .type bad_unchecked_multi_bb, at function
+bad_unchecked_multi_bb:
+// CHECK-LABEL: GS-PAUTH: authentication oracle found in function bad_unchecked_multi_bb, basic block {{[^,]+}}, at address
+// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: autia x0, x1
+// CHECK-NEXT: The 0 instructions that leak the affected registers are:
+ autia x0, x1
+ cbz x1, 1f
+ ldr x2, [x0]
+1:
+ ret
----------------
kbeyls wrote:
(very minor nitpick)
I guess in this case, it would be slightly nicer if the diagnostic would indicate the `ret` instruction as leaking the affected register? It just seemed a bit weird to me for the diagnostic to say that there's an authentication oracle, but no instruction is leaking it).
I'm just mentioning it in case it would be trivial to adjust the analysis to report the return instruction as the one leaking the register.
https://github.com/llvm/llvm-project/pull/135663
More information about the llvm-commits
mailing list