[llvm] [AArch64] Codegen for AArch64 Return Address Signing Hardening (PR #176187)

Daniil Kovalev via llvm-commits llvm-commits at lists.llvm.org
Mon Feb 9 06:03:35 PST 2026


================
@@ -0,0 +1,305 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 6
+;       The 'ret' instructions' regexps have been modified to check they aren't followed by a PAUTH
+;       suffix like 'aa' or 'ab'
+; RUN: llc -mtriple=aarch64 < %s                      | FileCheck %s --check-prefix=CHECK-NO-PAUTH
+; RUN: llc -mtriple=aarch64 -mattr=+v8.3a,-pauth < %s | FileCheck %s --check-prefix=CHECK-NO-PAUTH
+; RUN: llc -mtriple=aarch64 -mattr=+v8.3a < %s        | FileCheck %s --check-prefix=CHECK-PAUTH
+; RUN: llc -mtriple=aarch64 -mattr=+pauth < %s        | FileCheck %s --check-prefix=CHECK-PAUTH
+
+declare i32 @foo(i32)
+
+define i32 @f0(i32 %a) #0 {
+; CHECK-NO-PAUTH-LABEL: f0:
+; CHECK-NO-PAUTH:       // %bb.0: // %entry
+; CHECK-NO-PAUTH-NEXT:    hint #25
+; CHECK-NO-PAUTH-NEXT:    .cfi_negate_ra_state
+; CHECK-NO-PAUTH-NEXT:    add w0, w0, #1
+; CHECK-NO-PAUTH-NEXT:    hint #29
+; CHECK-NO-PAUTH-NEXT:    mov x8, x30
+; CHECK-NO-PAUTH-NEXT:    hint #7
+; CHECK-NO-PAUTH-NEXT:    ldr w30, [x30]
+; CHECK-NO-PAUTH-NEXT:    mov x30, x8
+; CHECK-NO-PAUTH-NEXT:    ret{{$}}
+;
+; CHECK-PAUTH-LABEL: f0:
+; CHECK-PAUTH:       // %bb.0: // %entry
+; CHECK-PAUTH-NEXT:    paciasp
+; CHECK-PAUTH-NEXT:    .cfi_negate_ra_state
+; CHECK-PAUTH-NEXT:    add w0, w0, #1
+; CHECK-PAUTH-NEXT:    autiasp
+; CHECK-PAUTH-NEXT:    mov x8, x30
+; CHECK-PAUTH-NEXT:    xpaci x8
+; CHECK-PAUTH-NEXT:    ldr w8, [x8]
+; CHECK-PAUTH-NEXT:    ret{{$}}
+entry:
+  %add = add nsw i32 %a, 1
+  ret i32 %add
+}
+
+define i32 @f1(i32 %a) #1 {
+; CHECK-NO-PAUTH-LABEL: f1:
+; CHECK-NO-PAUTH:       // %bb.0: // %entry
+; CHECK-NO-PAUTH-NEXT:    .cfi_b_key_frame
+; CHECK-NO-PAUTH-NEXT:    hint #27
+; CHECK-NO-PAUTH-NEXT:    .cfi_negate_ra_state
+; CHECK-NO-PAUTH-NEXT:    add w0, w0, #1
+; CHECK-NO-PAUTH-NEXT:    hint #31
+; CHECK-NO-PAUTH-NEXT:    mov x8, x30
+; CHECK-NO-PAUTH-NEXT:    hint #7
+; CHECK-NO-PAUTH-NEXT:    ldr w30, [x30]
+; CHECK-NO-PAUTH-NEXT:    mov x30, x8
+; CHECK-NO-PAUTH-NEXT:    ret{{$}}
+;
+; CHECK-PAUTH-LABEL: f1:
+; CHECK-PAUTH:       // %bb.0: // %entry
+; CHECK-PAUTH-NEXT:    .cfi_b_key_frame
+; CHECK-PAUTH-NEXT:    pacibsp
+; CHECK-PAUTH-NEXT:    .cfi_negate_ra_state
+; CHECK-PAUTH-NEXT:    add w0, w0, #1
+; CHECK-PAUTH-NEXT:    autibsp
+; CHECK-PAUTH-NEXT:    mov x8, x30
+; CHECK-PAUTH-NEXT:    xpaci x8
+; CHECK-PAUTH-NEXT:    ldr w8, [x8]
+; CHECK-PAUTH-NEXT:    ret{{$}}
+entry:
+  %add = add nsw i32 %a, 1
+  ret i32 %add
+}
+
+define i32 @f2(i32 %a) #2 {
+; CHECK-NO-PAUTH-LABEL: f2:
+; CHECK-NO-PAUTH:       // %bb.0: // %entry
+; CHECK-NO-PAUTH-NEXT:    hint #25
+; CHECK-NO-PAUTH-NEXT:    .cfi_negate_ra_state
+; CHECK-NO-PAUTH-NEXT:    str x30, [sp, #-16]! // 8-byte Folded Spill
+; CHECK-NO-PAUTH-NEXT:    .cfi_def_cfa_offset 16
+; CHECK-NO-PAUTH-NEXT:    .cfi_offset w30, -16
+; CHECK-NO-PAUTH-NEXT:    bl foo
+; CHECK-NO-PAUTH-NEXT:    ldr x30, [sp], #16 // 8-byte Folded Reload
+; CHECK-NO-PAUTH-NEXT:    hint #29
+; CHECK-NO-PAUTH-NEXT:    mov x8, x30
+; CHECK-NO-PAUTH-NEXT:    hint #7
+; CHECK-NO-PAUTH-NEXT:    ldr w30, [x30]
+; CHECK-NO-PAUTH-NEXT:    mov x30, x8
+; CHECK-NO-PAUTH-NEXT:    ret{{$}}
+;
+; CHECK-PAUTH-LABEL: f2:
+; CHECK-PAUTH:       // %bb.0: // %entry
+; CHECK-PAUTH-NEXT:    paciasp
+; CHECK-PAUTH-NEXT:    .cfi_negate_ra_state
+; CHECK-PAUTH-NEXT:    str x30, [sp, #-16]! // 8-byte Folded Spill
+; CHECK-PAUTH-NEXT:    .cfi_def_cfa_offset 16
+; CHECK-PAUTH-NEXT:    .cfi_offset w30, -16
+; CHECK-PAUTH-NEXT:    bl foo
+; CHECK-PAUTH-NEXT:    ldr x30, [sp], #16 // 8-byte Folded Reload
+; CHECK-PAUTH-NEXT:    autiasp
+; CHECK-PAUTH-NEXT:    mov x8, x30
+; CHECK-PAUTH-NEXT:    xpaci x8
+; CHECK-PAUTH-NEXT:    ldr w8, [x8]
+; CHECK-PAUTH-NEXT:    ret{{$}}
+entry:
+  %call = call i32 @foo(i32 %a)
+  ret i32 %call
+}
+
+define i32 @f3(i32 %a) #3 {
+; CHECK-NO-PAUTH-LABEL: f3:
+; CHECK-NO-PAUTH:       // %bb.0: // %entry
+; CHECK-NO-PAUTH-NEXT:    .cfi_b_key_frame
+; CHECK-NO-PAUTH-NEXT:    hint #27
+; CHECK-NO-PAUTH-NEXT:    .cfi_negate_ra_state
+; CHECK-NO-PAUTH-NEXT:    str x30, [sp, #-16]! // 8-byte Folded Spill
+; CHECK-NO-PAUTH-NEXT:    .cfi_def_cfa_offset 16
+; CHECK-NO-PAUTH-NEXT:    .cfi_offset w30, -16
+; CHECK-NO-PAUTH-NEXT:    bl foo
+; CHECK-NO-PAUTH-NEXT:    ldr x30, [sp], #16 // 8-byte Folded Reload
+; CHECK-NO-PAUTH-NEXT:    hint #31
+; CHECK-NO-PAUTH-NEXT:    mov x8, x30
+; CHECK-NO-PAUTH-NEXT:    hint #7
+; CHECK-NO-PAUTH-NEXT:    ldr w30, [x30]
+; CHECK-NO-PAUTH-NEXT:    mov x30, x8
+; CHECK-NO-PAUTH-NEXT:    ret{{$}}
+;
+; CHECK-PAUTH-LABEL: f3:
+; CHECK-PAUTH:       // %bb.0: // %entry
+; CHECK-PAUTH-NEXT:    .cfi_b_key_frame
+; CHECK-PAUTH-NEXT:    pacibsp
+; CHECK-PAUTH-NEXT:    .cfi_negate_ra_state
+; CHECK-PAUTH-NEXT:    str x30, [sp, #-16]! // 8-byte Folded Spill
+; CHECK-PAUTH-NEXT:    .cfi_def_cfa_offset 16
+; CHECK-PAUTH-NEXT:    .cfi_offset w30, -16
+; CHECK-PAUTH-NEXT:    bl foo
+; CHECK-PAUTH-NEXT:    ldr x30, [sp], #16 // 8-byte Folded Reload
+; CHECK-PAUTH-NEXT:    autibsp
+; CHECK-PAUTH-NEXT:    mov x8, x30
+; CHECK-PAUTH-NEXT:    xpaci x8
+; CHECK-PAUTH-NEXT:    ldr w8, [x8]
+; CHECK-PAUTH-NEXT:    ret{{$}}
+entry:
+  %call = call i32 @foo(i32 %a)
+  ret i32 %call
+}
+
+define i32 @f4(i32 %a) #4 {
+; CHECK-NO-PAUTH-LABEL: f4:
+; CHECK-NO-PAUTH:       // %bb.0: // %entry
+; CHECK-NO-PAUTH-NEXT:    hint #25
+; CHECK-NO-PAUTH-NEXT:    .cfi_negate_ra_state
+; CHECK-NO-PAUTH-NEXT:    str x30, [sp, #-16]! // 8-byte Folded Spill
+; CHECK-NO-PAUTH-NEXT:    .cfi_def_cfa_offset 16
+; CHECK-NO-PAUTH-NEXT:    .cfi_offset w30, -16
+; CHECK-NO-PAUTH-NEXT:    bl foo
+; CHECK-NO-PAUTH-NEXT:    ldr x30, [sp], #16 // 8-byte Folded Reload
+; CHECK-NO-PAUTH-NEXT:    hint #29
+; CHECK-NO-PAUTH-NEXT:    ret{{$}}
+;
+; CHECK-PAUTH-LABEL: f4:
+; CHECK-PAUTH:       // %bb.0: // %entry
+; CHECK-PAUTH-NEXT:    paciasp
+; CHECK-PAUTH-NEXT:    .cfi_negate_ra_state
+; CHECK-PAUTH-NEXT:    str x30, [sp, #-16]! // 8-byte Folded Spill
+; CHECK-PAUTH-NEXT:    .cfi_def_cfa_offset 16
+; CHECK-PAUTH-NEXT:    .cfi_offset w30, -16
+; CHECK-PAUTH-NEXT:    bl foo
+; CHECK-PAUTH-NEXT:    ldr x30, [sp], #16 // 8-byte Folded Reload
+; CHECK-PAUTH-NEXT:    retaa
+entry:
+  %call = call i32 @foo(i32 %a)
+  ret i32 %call
+}
+
+define i32 @f5(i32 %a) #5 {
+; CHECK-NO-PAUTH-LABEL: f5:
+; CHECK-NO-PAUTH:       // %bb.0: // %entry
+; CHECK-NO-PAUTH-NEXT:    .cfi_b_key_frame
+; CHECK-NO-PAUTH-NEXT:    hint #27
+; CHECK-NO-PAUTH-NEXT:    .cfi_negate_ra_state
+; CHECK-NO-PAUTH-NEXT:    str x30, [sp, #-16]! // 8-byte Folded Spill
+; CHECK-NO-PAUTH-NEXT:    .cfi_def_cfa_offset 16
+; CHECK-NO-PAUTH-NEXT:    .cfi_offset w30, -16
+; CHECK-NO-PAUTH-NEXT:    bl foo
+; CHECK-NO-PAUTH-NEXT:    ldr x30, [sp], #16 // 8-byte Folded Reload
+; CHECK-NO-PAUTH-NEXT:    hint #31
+; CHECK-NO-PAUTH-NEXT:    ret{{$}}
+;
+; CHECK-PAUTH-LABEL: f5:
+; CHECK-PAUTH:       // %bb.0: // %entry
+; CHECK-PAUTH-NEXT:    .cfi_b_key_frame
+; CHECK-PAUTH-NEXT:    pacibsp
+; CHECK-PAUTH-NEXT:    .cfi_negate_ra_state
+; CHECK-PAUTH-NEXT:    str x30, [sp, #-16]! // 8-byte Folded Spill
+; CHECK-PAUTH-NEXT:    .cfi_def_cfa_offset 16
+; CHECK-PAUTH-NEXT:    .cfi_offset w30, -16
+; CHECK-PAUTH-NEXT:    bl foo
+; CHECK-PAUTH-NEXT:    ldr x30, [sp], #16 // 8-byte Folded Reload
+; CHECK-PAUTH-NEXT:    retab
+entry:
+  %call = call i32 @foo(i32 %a)
+  ret i32 %call
+}
+
+; Check that we don't harden functions which "return" with a
+; branch rather than a ret instruction.
+define i32 @f6(i32 %a) #0 {
+; CHECK-NO-PAUTH-LABEL: f6:
+; CHECK-NO-PAUTH:       // %bb.0: // %entry
+; CHECK-NO-PAUTH-NEXT:    hint #25
+; CHECK-NO-PAUTH-NEXT:    .cfi_negate_ra_state
+; CHECK-NO-PAUTH-NEXT:    hint #29
+; CHECK-NO-PAUTH-NEXT:    b foo
+;
+; CHECK-PAUTH-LABEL: f6:
+; CHECK-PAUTH:       // %bb.0: // %entry
+; CHECK-PAUTH-NEXT:    paciasp
+; CHECK-PAUTH-NEXT:    .cfi_negate_ra_state
+; CHECK-PAUTH-NEXT:    autiasp
+; CHECK-PAUTH-NEXT:    b foo
+entry:
+  %call = tail call i32 @foo(i32 %a)
+  ret i32 %call
+}
+
+define i32 @f7(ptr %fnptr) #0 {
+; CHECK-NO-PAUTH-LABEL: f7:
+; CHECK-NO-PAUTH:       // %bb.0: // %entry
+; CHECK-NO-PAUTH-NEXT:    hint #25
+; CHECK-NO-PAUTH-NEXT:    .cfi_negate_ra_state
+; CHECK-NO-PAUTH-NEXT:    hint #29
+; CHECK-NO-PAUTH-NEXT:    br x0
+;
+; CHECK-PAUTH-LABEL: f7:
+; CHECK-PAUTH:       // %bb.0: // %entry
+; CHECK-PAUTH-NEXT:    paciasp
+; CHECK-PAUTH-NEXT:    .cfi_negate_ra_state
+; CHECK-PAUTH-NEXT:    autiasp
+; CHECK-PAUTH-NEXT:    br x0
+entry:
+  %call = tail call i32 %fnptr()
+  ret i32 %call
+}
+
+; Check to see if pac-ret hardening is compatible with stack protection
+define void @stackprotector() #6 {
+; CHECK-NO-PAUTH-LABEL: stackprotector:
+; CHECK-NO-PAUTH:       // %bb.0:
+; CHECK-NO-PAUTH-NEXT:    hint #25
+; CHECK-NO-PAUTH-NEXT:    .cfi_negate_ra_state
+; CHECK-NO-PAUTH-NEXT:    sub sp, sp, #32
+; CHECK-NO-PAUTH-NEXT:    str x30, [sp, #16] // 8-byte Spill
+; CHECK-NO-PAUTH-NEXT:    .cfi_def_cfa_offset 32
+; CHECK-NO-PAUTH-NEXT:    .cfi_offset w30, -16
+; CHECK-NO-PAUTH-NEXT:    adrp x8, __stack_chk_guard
+; CHECK-NO-PAUTH-NEXT:    ldr x8, [x8, :lo12:__stack_chk_guard]
+; CHECK-NO-PAUTH-NEXT:    str x8, [sp, #8]
+; CHECK-NO-PAUTH-NEXT:    ldr x9, [sp, #8]
+; CHECK-NO-PAUTH-NEXT:    cmp x8, x9
+; CHECK-NO-PAUTH-NEXT:    b.ne .LBB8_2
+; CHECK-NO-PAUTH-NEXT:  // %bb.1:
+; CHECK-NO-PAUTH-NEXT:    ldr x30, [sp, #16] // 8-byte Reload
+; CHECK-NO-PAUTH-NEXT:    add sp, sp, #32
+; CHECK-NO-PAUTH-NEXT:    hint #29
+; CHECK-NO-PAUTH-NEXT:    mov x8, x30
+; CHECK-NO-PAUTH-NEXT:    hint #7
+; CHECK-NO-PAUTH-NEXT:    ldr w30, [x30]
+; CHECK-NO-PAUTH-NEXT:    mov x30, x8
+; CHECK-NO-PAUTH-NEXT:    ret{{$}}
+; CHECK-NO-PAUTH-NEXT:  .LBB8_2:
+; CHECK-NO-PAUTH-NEXT:    bl __stack_chk_fail
+;
+; CHECK-PAUTH-LABEL: stackprotector:
+; CHECK-PAUTH:       // %bb.0:
+; CHECK-PAUTH-NEXT:    paciasp
+; CHECK-PAUTH-NEXT:    .cfi_negate_ra_state
+; CHECK-PAUTH-NEXT:    sub sp, sp, #32
+; CHECK-PAUTH-NEXT:    str x30, [sp, #16] // 8-byte Spill
+; CHECK-PAUTH-NEXT:    .cfi_def_cfa_offset 32
+; CHECK-PAUTH-NEXT:    .cfi_offset w30, -16
+; CHECK-PAUTH-NEXT:    adrp x8, __stack_chk_guard
+; CHECK-PAUTH-NEXT:    ldr x8, [x8, :lo12:__stack_chk_guard]
+; CHECK-PAUTH-NEXT:    str x8, [sp, #8]
+; CHECK-PAUTH-NEXT:    ldr x9, [sp, #8]
+; CHECK-PAUTH-NEXT:    cmp x8, x9
+; CHECK-PAUTH-NEXT:    b.ne .LBB8_2
+; CHECK-PAUTH-NEXT:  // %bb.1:
+; CHECK-PAUTH-NEXT:    ldr x30, [sp, #16] // 8-byte Reload
+; CHECK-PAUTH-NEXT:    add sp, sp, #32
+; CHECK-PAUTH-NEXT:    autiasp
+; CHECK-PAUTH-NEXT:    mov x8, x30
+; CHECK-PAUTH-NEXT:    xpaci x8
+; CHECK-PAUTH-NEXT:    ldr w8, [x8]
+; CHECK-PAUTH-NEXT:    ret{{$}}
+; CHECK-PAUTH-NEXT:  .LBB8_2:
+; CHECK-PAUTH-NEXT:    bl __stack_chk_fail
+  ret void
+}
+
+attributes #0 = { "sign-return-address"="all" "sign-return-address-harden"="load-return-address" "sign-return-address-key"="a_key" }
+attributes #1 = { "sign-return-address"="all" "sign-return-address-harden"="load-return-address" "sign-return-address-key"="b_key" }
+
+attributes #2 = { "sign-return-address"="non-leaf" "sign-return-address-harden"="load-return-address" "sign-return-address-key"="a_key" }
+attributes #3 = { "sign-return-address"="non-leaf" "sign-return-address-harden"="load-return-address" "sign-return-address-key"="b_key" }
+
+attributes #4 = { "sign-return-address"="non-leaf" "sign-return-address-harden"="none" "sign-return-address-key"="a_key" }
+attributes #5 = { "sign-return-address"="non-leaf" "sign-return-address-harden"="none" "sign-return-address-key"="b_key" }
+
+attributes #6 = { sspreq "sign-return-address"="all" "sign-return-address-harden"="load-return-address" "sign-return-address-key"="a_key" }
----------------
kovdan01 wrote:

I suppose it might be worth to add a test ensuring that the hardening works properly when we use `"ptrauth-returns"` attr instead of `"sign-return-address"` (the first one corresponds to `-fptrauth-returns` flag implicitly enables as part of Linux's pauthtest Apple's arm64e, the second one corresponds to `mbranch-protection=pac-ret`). The codegen is the same for both cases, so this should just work w/o need for any modifications in this patch - let's just cover it with a test.

https://github.com/llvm/llvm-project/pull/176187


More information about the llvm-commits mailing list