[libunwind] [PAC][libunwind][AArch64] Keep LR signed when stored in context struct (PR #171717)

Daniil Kovalev via cfe-commits cfe-commits at lists.llvm.org
Wed Dec 10 17:10:02 PST 2025


================
@@ -683,42 +684,139 @@ DEFINE_LIBUNWIND_FUNCTION(__libunwind_Registers_arm64_jumpto)
   ldp    x28,x29, [x0, #0x0E0]
 
 #if defined(__ARM_FP) && __ARM_FP != 0
-  ldp    d0, d1,  [x0, #0x110]
-  ldp    d2, d3,  [x0, #0x120]
-  ldp    d4, d5,  [x0, #0x130]
-  ldp    d6, d7,  [x0, #0x140]
-  ldp    d8, d9,  [x0, #0x150]
-  ldp    d10,d11, [x0, #0x160]
-  ldp    d12,d13, [x0, #0x170]
-  ldp    d14,d15, [x0, #0x180]
-  ldp    d16,d17, [x0, #0x190]
-  ldp    d18,d19, [x0, #0x1A0]
-  ldp    d20,d21, [x0, #0x1B0]
-  ldp    d22,d23, [x0, #0x1C0]
-  ldp    d24,d25, [x0, #0x1D0]
-  ldp    d26,d27, [x0, #0x1E0]
-  ldp    d28,d29, [x0, #0x1F0]
-  ldr    d30,     [x0, #0x200]
-  ldr    d31,     [x0, #0x208]
+  ldp    d0, d1,  [x0, #0x120]
+  ldp    d2, d3,  [x0, #0x130]
+  ldp    d4, d5,  [x0, #0x140]
+  ldp    d6, d7,  [x0, #0x150]
+  ldp    d8, d9,  [x0, #0x160]
+  ldp    d10,d11, [x0, #0x170]
+  ldp    d12,d13, [x0, #0x180]
+  ldp    d14,d15, [x0, #0x190]
+  ldp    d16,d17, [x0, #0x1A0]
+  ldp    d18,d19, [x0, #0x1B0]
+  ldp    d20,d21, [x0, #0x1C0]
+  ldp    d22,d23, [x0, #0x1D0]
+  ldp    d24,d25, [x0, #0x1E0]
+  ldp    d26,d27, [x0, #0x1F0]
+  ldr    d28,     [x0, #0x200]
+  ldr    d29,     [x0, #0x208]
+  ldr    d30,     [x0, #0x210]
+  ldr    d31,     [x0, #0x218]
 #endif
+
+  // The lr value stored in __pc might be signed with IA/IB key (as described
+  // by __ra_sign.__use_b_key field) and &__pc address as a modifier.
+  // If the lr value is signed, re-sign that with the corresponding key
+  // and initial signing scheme using __sp as a modifier and potentially
+  // involving a second modifier (as described by (__ra_sign.__state & 2)).
+
+  ldr    x14,     [x0, #0x0F8]  // x14 = __sp
----------------
kovdan01 wrote:

This mimics existing behavior of Apple's arm64e and Linux's pauthtest. Particularly, if the pointer is signed, we re-sign that so it is signed with the original signing scheme (rather then with `&__pc` address as a modifier). After that, we authenticate re-signed pointer at return.

This is bulky and we can probably do it more elegant. Would be glad to see thoughts of anyone interested regarding this. See my thoughts below.

When implementing this, I was focused on maintaining the following three properties of current implementation.

1. We should not do any loads from stack after `sp` is restored (see d5323f6a707e376aab20ebe7864a78fc38fcedaa).
2. The only scratch registers which are allowed to be clobbered are x16 and x17, all other registers are restored from the context structure.
3. Since we are using hint-encoded instructions to maintain compatibility with old hardware, authenticated return is done with regular `ret` following `auti*` instruction. I assumed that we must keep these two instructions together without inserting anything else between them to reduce the time when `lr` remains unsigned.

If we could weaken property 2 (e.g. allow x14 and x15 to be clobbered) or property 3 (e.g. allow code between `auti*` and `ret`), we might make the whole implementation much simpler. Particularly, instead of doing a re-sign with almost immediate auth, we might just do one auth. The three limitations listed below do not allow us to have a nice implementation because we need to deal with three conditions for choosing between 5 possible configurations of RA sign state (not signed, signed with IA and no PC modifier, signed with IB and no PC modifier, signed with IA and PC modifier, signed with IB and PC modifier).

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


More information about the cfe-commits mailing list