[llvm] [BOLT] Gadget scanner: detect non-protected indirect calls (PR #131899)
Anatoly Trosinenko via llvm-commits
llvm-commits at lists.llvm.org
Wed Mar 26 09:22:22 PDT 2025
================
@@ -0,0 +1,777 @@
+// 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
+
+// PACRET-NOT: non-protected call found in function
+
+ .text
+
+ .globl good_direct_call
+ .type good_direct_call, at function
+good_direct_call:
+// CHECK-NOT: good_direct_call
+ paciasp
+ stp x29, x30, [sp, #-16]!
+ mov x29, sp
+
+ bl callee_ext
+
+ ldp x29, x30, [sp], #16
+ autiasp
+ ret
+ .size good_direct_call, .-good_direct_call
+
+ .globl good_indirect_call_arg
+ .type good_indirect_call_arg, at function
+good_indirect_call_arg:
+// CHECK-NOT: good_indirect_call_arg
+ paciasp
+ stp x29, x30, [sp, #-16]!
+ mov x29, sp
+
+ autia x0, x1
+ blr x0
+
+ ldp x29, x30, [sp], #16
+ autiasp
+ ret
+ .size good_indirect_call_arg, .-good_indirect_call_arg
+
+ .globl good_indirect_call_mem
+ .type good_indirect_call_mem, at function
+good_indirect_call_mem:
+// CHECK-NOT: good_indirect_call_mem
+ paciasp
+ stp x29, x30, [sp, #-16]!
+ mov x29, sp
+
+ ldr x16, [x0]
+ autia x16, x0
+ blr x16
+
+ ldp x29, x30, [sp], #16
+ autiasp
+ ret
+ .size good_indirect_call_mem, .-good_indirect_call_mem
+
+ .globl good_indirect_call_arg_v83
+ .type good_indirect_call_arg_v83, at function
+good_indirect_call_arg_v83:
+// CHECK-NOT: good_indirect_call_arg_v83
+ paciasp
+ stp x29, x30, [sp, #-16]!
+ mov x29, sp
+
+ blraa x0, x1
+
+ ldp x29, x30, [sp], #16
+ autiasp
+ ret
+ .size good_indirect_call_arg_v83, .-good_indirect_call_arg_v83
+
+ .globl good_indirect_call_mem_v83
+ .type good_indirect_call_mem_v83, at function
+good_indirect_call_mem_v83:
+// CHECK-NOT: good_indirect_call_mem_v83
+ paciasp
+ stp x29, x30, [sp, #-16]!
+ mov x29, sp
+
+ ldr x16, [x0]
+ blraa x16, x0
+
+ ldp x29, x30, [sp], #16
+ autiasp
+ ret
+ .size good_indirect_call_mem_v83, .-good_indirect_call_mem_v83
+
+ .globl bad_indirect_call_arg
+ .type bad_indirect_call_arg, at function
+bad_indirect_call_arg:
+// CHECK-LABEL: GS-PAUTH: non-protected call found in function bad_indirect_call_arg, basic block {{[^,]+}}, at address
+// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: blr x0
+// CHECK-NEXT: The 0 instructions that write to the affected registers after any authentication are:
+ paciasp
+ stp x29, x30, [sp, #-16]!
+ mov x29, sp
+
+ blr x0
+
+ ldp x29, x30, [sp], #16
+ autiasp
+ ret
+ .size bad_indirect_call_arg, .-bad_indirect_call_arg
+
+ .globl bad_indirect_call_mem
+ .type bad_indirect_call_mem, at function
+bad_indirect_call_mem:
+// CHECK-LABEL: GS-PAUTH: non-protected call found in function bad_indirect_call_mem, basic block {{[^,]+}}, at address
+// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: blr x16
+// CHECK-NEXT: The 1 instructions that write to the affected registers after any authentication are:
+// CHECK-NEXT: 1. {{[0-9a-f]+}}: ldr x16, [x0]
+// 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]+}}: ldr x16, [x0]
+// CHECK-NEXT: {{[0-9a-f]+}}: blr x16
+// 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
+
+ ldr x16, [x0]
+ blr x16
+
+ ldp x29, x30, [sp], #16
+ autiasp
+ ret
+ .size bad_indirect_call_mem, .-bad_indirect_call_mem
+
+ .globl bad_indirect_call_arg_clobber
+ .type bad_indirect_call_arg_clobber, at function
+bad_indirect_call_arg_clobber:
+// CHECK-LABEL: GS-PAUTH: non-protected call found in function bad_indirect_call_arg_clobber, basic block {{[^,]+}}, at address
+// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: blr x0
+// CHECK-NEXT: The 1 instructions that write to the affected registers after any authentication are:
+// CHECK-NEXT: 1. {{[0-9a-f]+}}: mov w0, w2
+// 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]+}}: mov w0, w2
+// CHECK-NEXT: {{[0-9a-f]+}}: blr 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
+ mov w0, w2
+ blr x0
+
+ ldp x29, x30, [sp], #16
+ autiasp
+ ret
+ .size bad_indirect_call_arg_clobber, .-bad_indirect_call_arg_clobber
+
+ .globl bad_indirect_call_mem_clobber
+ .type bad_indirect_call_mem_clobber, at function
+bad_indirect_call_mem_clobber:
+// CHECK-LABEL: GS-PAUTH: non-protected call found in function bad_indirect_call_mem_clobber, basic block {{[^,]+}}, at address
+// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: blr x16
+// CHECK-NEXT: The 1 instructions that write to the affected registers after any authentication are:
+// CHECK-NEXT: 1. {{[0-9a-f]+}}: mov w16, w2
+// 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]+}}: ldr x16, [x0]
+// CHECK-NEXT: {{[0-9a-f]+}}: autia x16, x0
+// CHECK-NEXT: {{[0-9a-f]+}}: mov w16, w2
+// CHECK-NEXT: {{[0-9a-f]+}}: blr x16
+// 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
+
+ ldr x16, [x0]
+ autia x16, x0
+ mov w16, w2
+ blr x16
+
+ ldp x29, x30, [sp], #16
+ autiasp
+ ret
+ .size bad_indirect_call_mem_clobber, .-bad_indirect_call_mem_clobber
+
+ .globl good_indirect_call_mem_chain_of_auts
+ .type good_indirect_call_mem_chain_of_auts, at function
+good_indirect_call_mem_chain_of_auts:
+// CHECK-NOT: good_indirect_call_mem_chain_of_auts
+ paciasp
+ stp x29, x30, [sp, #-16]!
+ mov x29, sp
+
+ ldr x16, [x0]
+ autia x16, x1
+ ldr x16, [x16]
+ autia x16, x0
+ blr x16
+
+ ldp x29, x30, [sp], #16
+ autiasp
+ ret
+ .size good_indirect_call_mem_chain_of_auts, .-good_indirect_call_mem_chain_of_auts
+
+ .globl bad_indirect_call_mem_chain_of_auts
+ .type bad_indirect_call_mem_chain_of_auts, at function
+bad_indirect_call_mem_chain_of_auts:
+// CHECK-LABEL: GS-PAUTH: non-protected call found in function bad_indirect_call_mem_chain_of_auts, basic block {{[^,]+}}, at address
+// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: blr x16
+// CHECK-NEXT: The 1 instructions that write to the affected registers after any authentication are:
+// CHECK-NEXT: 1. {{[0-9a-f]+}}: ldr x16, [x16]
+// 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]+}}: ldr x16, [x0]
+// CHECK-NEXT: {{[0-9a-f]+}}: autia x16, x1
+// CHECK-NEXT: {{[0-9a-f]+}}: ldr x16, [x16]
+// CHECK-NEXT: {{[0-9a-f]+}}: blr x16
+// 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
+
+ ldr x16, [x0]
+ autia x16, x1
+ ldr x16, [x16]
+ // Missing AUT of x16. The fact that x16 was authenticated above has nothing to do with it.
+ blr x16
+
+ ldp x29, x30, [sp], #16
+ autiasp
+ ret
+ .size bad_indirect_call_mem_chain_of_auts, .-bad_indirect_call_mem_chain_of_auts
+
+// Multi-BB test cases.
+//
+// Positive ("good") test cases are designed so that the register is made safe
+// in one BB and used in the other. Negative ("bad") ones are designed so that
+// there are two predecessors, one of them ends with the register in a safe
+// state and the other ends with that register being unsafe.
+
+ .globl good_indirect_call_arg_multi_bb
+ .type good_indirect_call_arg_multi_bb, at function
+good_indirect_call_arg_multi_bb:
+// CHECK-NOT: good_indirect_call_arg_multi_bb
+ paciasp
+ stp x29, x30, [sp, #-16]!
+ mov x29, sp
+
+ autia x0, x1
+ cbz x2, 1f
+ blr x0
+1:
+ ldr x1, [x0] // prevent authentication oracle
+
+ ldp x29, x30, [sp], #16
+ autiasp
+ ret
+ .size good_indirect_call_arg_multi_bb, .-good_indirect_call_arg_multi_bb
+
+ .globl good_indirect_call_mem_multi_bb
+ .type good_indirect_call_mem_multi_bb, at function
+good_indirect_call_mem_multi_bb:
+// CHECK-NOT: good_indirect_call_mem_multi_bb
+ paciasp
+ stp x29, x30, [sp, #-16]!
+ mov x29, sp
+
+ ldr x16, [x0]
+ autia x16, x0
+ cbz x2, 1f
+ blr x16
+1:
+ ldr w0, [x16] // prevent authentication oracle
+
+ ldp x29, x30, [sp], #16
----------------
atrosinenko wrote:
Do you mention `good_indirect_call_arg_multi_bb` and `good_indirect_call_mem_multi_bb` test cases? No, their intended difference is whether the address to be authenticated is loaded from memory by this particular function (`_mem_` variant) or provided in `x0` by the caller (`_arg_` variant).
```
ldr w0, [x16] // prevent authentication oracle
```
is added to both test cases, as otherwise `x0` or `x16`, correspondingly, can be either jumped-to (which is good, as this would immediately crash the program abnormally if authentication failed) or leaked to the caller (which is bad, as an attacker could possibly inspect the higher bits of that register later). This line changes nothing at now, but it will _probably_ be required when authentication oracles are searched for, thus adding it now as the real-world code would _probably_ require such line as well ("probably" - because searching for signing and authentication gadgets could be made optional in case the user, for example, knows that this code is to be run by a CPU which implements `FEAT_FPAC`).
https://github.com/llvm/llvm-project/pull/131899
More information about the llvm-commits
mailing list