https://github.com/kovdan01 created https://github.com/llvm/llvm-project/pull/173878
Please pay no attention to this PR, its submitted to trigger build jobs and ensure that WIP work is not breaking platforms I can't test locally >From b824caa6c6cb306e0e024fe9aa872a9a5e3f35bb Mon Sep 17 00:00:00 2001 From: Daniil Kovalev <[email protected]> Date: Mon, 8 Dec 2025 16:31:10 +0300 Subject: [PATCH 1/2] [PAC][libunwind][AArch64] Keep LR signed when stored in context struct There are two ways of return address signing: pac-ret (enabled via `-mbranch-protection=pac-ret`) and ptrauth-returns (enabled as part of Apple's arm64e or experimental pauthtest ABI on Linux). Previously, signed LR was handled in libunwind as follows: 1. For pac-ret, the signed LR value was authenticated, and the resulting unsigned LR value was stored in the context structure. 2. For ptrauth-returns (which is assumed to be a part of a full-fledged PAuth ABI like arm64e or pauthtest), the signed LR value was re-signed using the same key (IB) and address of the `__pc` field in the context structure as a modifier. This patch unifies the signed LR handling logic by keeping LR signed for pac-ret similarly to ptrauth-returns. It makes LR substitution in the context structure harder. Note that LR signed state or signing scheme for pac-ret might differ between stack frames. The behavior differs from ptrauth-returns, which has a fixed signing scheme and is enabled as a part of bigger PAuth ABI which is either present everywhere or not. In order to handle different signing schemes across stack frames, new subfields `__state`, `__second_modifier` and `__use_b_key` of the field `__ra_sign` in the context structure are used. When stored in the context structure, the pointer is resigned with maintaining original key and using the address of the `__pc` field in the structure as a modifier. --- libunwind/include/__libunwind_config.h | 4 +- libunwind/include/libunwind.h | 6 + libunwind/src/DwarfInstructions.hpp | 92 +++-------- libunwind/src/Registers.hpp | 207 +++++++++++++++++++++---- libunwind/src/UnwindCursor.hpp | 8 +- libunwind/src/UnwindLevel1.c | 58 ++++++- libunwind/src/UnwindRegistersRestore.S | 163 +++++++++++++++---- libunwind/src/UnwindRegistersSave.S | 104 ++++++++++--- libunwind/src/libunwind.cpp | 11 ++ 9 files changed, 482 insertions(+), 171 deletions(-) diff --git a/libunwind/include/__libunwind_config.h b/libunwind/include/__libunwind_config.h index 980d11ef5d4f2..3327272c2a6bc 100644 --- a/libunwind/include/__libunwind_config.h +++ b/libunwind/include/__libunwind_config.h @@ -73,11 +73,11 @@ # define _LIBUNWIND_HIGHEST_DWARF_REGISTER _LIBUNWIND_HIGHEST_DWARF_REGISTER_PPC # elif defined(__aarch64__) # define _LIBUNWIND_TARGET_AARCH64 1 -#define _LIBUNWIND_CONTEXT_SIZE 67 +#define _LIBUNWIND_CONTEXT_SIZE 69 # if defined(__SEH__) # define _LIBUNWIND_CURSOR_SIZE 164 # else -#define _LIBUNWIND_CURSOR_SIZE 79 +#define _LIBUNWIND_CURSOR_SIZE 81 # endif # define _LIBUNWIND_HIGHEST_DWARF_REGISTER _LIBUNWIND_HIGHEST_DWARF_REGISTER_ARM64 # elif defined(__arm__) diff --git a/libunwind/include/libunwind.h b/libunwind/include/libunwind.h index 56ca7110274a3..6a56548867642 100644 --- a/libunwind/include/libunwind.h +++ b/libunwind/include/libunwind.h @@ -642,6 +642,12 @@ enum { // reserved block UNW_AARCH64_RA_SIGN_STATE = 34, + // The following two registers are not real Dwarf registers and use numbers + // which are not occupied by real Dwarf registers according to + // https://github.com/ARM-software/abi-aa/blob/2025Q1/aadwarf64/aadwarf64.rst + UNW_AARCH64_RA_SIGN_SECOND_MODIFIER = 128, + UNW_AARCH64_RA_SIGN_USE_B_KEY = 129, + // FP/vector registers UNW_AARCH64_V0 = 64, UNW_AARCH64_V1 = 65, diff --git a/libunwind/src/DwarfInstructions.hpp b/libunwind/src/DwarfInstructions.hpp index d2822e8be29ef..ad1555db3abd2 100644 --- a/libunwind/src/DwarfInstructions.hpp +++ b/libunwind/src/DwarfInstructions.hpp @@ -75,10 +75,8 @@ class DwarfInstructions { __builtin_unreachable(); } #if defined(_LIBUNWIND_TARGET_AARCH64) - static bool isReturnAddressSigned(A &addressSpace, R registers, pint_t cfa, - PrologInfo &prolog); - static bool isReturnAddressSignedWithPC(A &addressSpace, R registers, - pint_t cfa, PrologInfo &prolog); + static pint_t getRASignState(A &addressSpace, const R ®isters, pint_t cfa, + const PrologInfo &prolog); #endif }; @@ -176,34 +174,13 @@ v128 DwarfInstructions<A, R>::getSavedVectorRegister( } #if defined(_LIBUNWIND_TARGET_AARCH64) template <typename A, typename R> -bool DwarfInstructions<A, R>::isReturnAddressSigned(A &addressSpace, - R registers, pint_t cfa, - PrologInfo &prolog) { - pint_t raSignState; - auto regloc = prolog.savedRegisters[UNW_AARCH64_RA_SIGN_STATE]; - if (regloc.location == CFI_Parser<A>::kRegisterUnused) - raSignState = static_cast<pint_t>(regloc.value); - else - raSignState = getSavedRegister(addressSpace, registers, cfa, regloc); - - // Only bit[0] is meaningful. - return raSignState & 0x01; -} - -template <typename A, typename R> -bool DwarfInstructions<A, R>::isReturnAddressSignedWithPC(A &addressSpace, - R registers, - pint_t cfa, - PrologInfo &prolog) { - pint_t raSignState; +typename A::pint_t +DwarfInstructions<A, R>::getRASignState(A &addressSpace, const R ®isters, + pint_t cfa, const PrologInfo &prolog) { auto regloc = prolog.savedRegisters[UNW_AARCH64_RA_SIGN_STATE]; if (regloc.location == CFI_Parser<A>::kRegisterUnused) - raSignState = static_cast<pint_t>(regloc.value); - else - raSignState = getSavedRegister(addressSpace, registers, cfa, regloc); - - // Only bit[1] is meaningful. - return raSignState & 0x02; + return static_cast<pint_t>(regloc.value); + return getSavedRegister(addressSpace, registers, cfa, regloc); } #endif @@ -302,54 +279,25 @@ int DwarfInstructions<A, R>::stepWithDwarf(A &addressSpace, isSignalFrame = cieInfo.isSignalFrame; -#if defined(_LIBUNWIND_TARGET_AARCH64) && \ - !defined(_LIBUNWIND_TARGET_AARCH64_AUTHENTICATED_UNWINDING) - // There are two ways of return address signing: pac-ret (enabled via - // -mbranch-protection=pac-ret) and ptrauth-returns (enabled as part of - // Apple's arm64e or experimental pauthtest ABI on Linux). The code - // below handles signed RA for pac-ret, while ptrauth-returns uses - // different logic. - // TODO: unify logic for both cases, see - // https://github.com/llvm/llvm-project/issues/160110 - // +#if defined(_LIBUNWIND_TARGET_AARCH64) // If the target is aarch64 then the return address may have been signed - // using the v8.3 pointer authentication extensions. The original - // return address needs to be authenticated before the return address is - // restored. autia1716 is used instead of autia as autia1716 assembles - // to a NOP on pre-v8.3a architectures. - if ((R::getArch() == REGISTERS_ARM64) && - isReturnAddressSigned(addressSpace, registers, cfa, prolog) && + // using the v8.3 pointer authentication extensions. In order to + // store signed return address in the registers context structure, we + // need to save the signing scheme for this address. + pint_t raSignState = getRASignState(addressSpace, registers, cfa, prolog); + bool isReturnAddressSigned = (raSignState & 1); + if ((R::getArch() == REGISTERS_ARM64) && isReturnAddressSigned && returnAddress != 0) { #if !defined(_LIBUNWIND_IS_NATIVE_ONLY) return UNW_ECROSSRASIGNING; #else - register unsigned long long x17 __asm("x17") = returnAddress; - register unsigned long long x16 __asm("x16") = cfa; - - // We use the hint versions of the authentication instructions below to - // ensure they're assembled by the compiler even for targets with no - // FEAT_PAuth/FEAT_PAuth_LR support. - if (isReturnAddressSignedWithPC(addressSpace, registers, cfa, prolog)) { - register unsigned long long x15 __asm("x15") = - prolog.ptrAuthDiversifier; - if (cieInfo.addressesSignedWithBKey) { - asm("hint 0x27\n\t" // pacm - "hint 0xe" - : "+r"(x17) - : "r"(x16), "r"(x15)); // autib1716 - } else { - asm("hint 0x27\n\t" // pacm - "hint 0xc" - : "+r"(x17) - : "r"(x16), "r"(x15)); // autia1716 - } - } else { - if (cieInfo.addressesSignedWithBKey) - asm("hint 0xe" : "+r"(x17) : "r"(x16)); // autib1716 - else - asm("hint 0xc" : "+r"(x17) : "r"(x16)); // autia1716 + newRegisters.setRegister(UNW_AARCH64_RA_SIGN_STATE, raSignState); + if (newRegisters.isReturnAddressSignedWithPC()) { + newRegisters.setRegister(UNW_AARCH64_RA_SIGN_SECOND_MODIFIER, + prolog.ptrAuthDiversifier); } - returnAddress = x17; + newRegisters.setRegister(UNW_AARCH64_RA_SIGN_USE_B_KEY, + cieInfo.addressesSignedWithBKey ? 1 : 0); #endif } #endif diff --git a/libunwind/src/Registers.hpp b/libunwind/src/Registers.hpp index 45a2b0921ea3b..2245ccba61e4f 100644 --- a/libunwind/src/Registers.hpp +++ b/libunwind/src/Registers.hpp @@ -1877,49 +1877,179 @@ class _LIBUNWIND_HIDDEN Registers_arm64 { uint64_t getSP() const { return _registers.__sp; } void setSP(uint64_t value) { _registers.__sp = value; } + uint64_t getIP() const { uint64_t value = _registers.__pc; -#if defined(_LIBUNWIND_TARGET_AARCH64_AUTHENTICATED_UNWINDING) + + if (!isReturnAddressSigned()) + return value; + +#if !defined(_LIBUNWIND_IS_NATIVE_ONLY) + abortCrossRASigning(); +#else // Note the value of the PC was signed to its address in the register state // but everyone else expects it to be sign by the SP, so convert on return. - value = (uint64_t)ptrauth_auth_and_resign((void *)_registers.__pc, - ptrauth_key_return_address, - &_registers.__pc, - ptrauth_key_return_address, - getSP()); + register uint64_t x17 __asm("x17") = value; + register uint64_t x16 __asm("x16") = + reinterpret_cast<uint64_t>(&_registers.__pc); + register uint64_t x14 __asm("x14") = getSP(); + if (isReturnAddressSignedWithPC()) { + register uint64_t x15 __asm("x15") = + _registers.__ra_sign.__second_modifier; + if (isReturnAddressSignedWithBKey()) { + asm("hint 0xe \n\t" // autib1716 + "mov x16, x14\n\t" + "hint 0x27 \n\t" // pacm + "hint 0xa " // pacib1716 + : "+r"(x17) + : "r"(x16), "r"(x15), "r"(x14)); + } else { + asm("hint 0xc \n\t" // autia1716 + "mov x16, x14\n\t" + "hint 0x27 \n\t" // pacm + "hint 0x8 " // pacia1716 + : "+r"(x17) + : "r"(x16), "r"(x15), "r"(x14)); + } + } else { + if (isReturnAddressSignedWithBKey()) { + asm("hint 0xe \n\t" // autib1716 + "mov x16, x14\n\t" + "hint 0xa " // pacib1716 + : "+r"(x17) + : "r"(x16), "r"(x14)); + } else { + asm("hint 0xc \n\t" // autia1716 + "mov x16, x14\n\t" + "hint 0x8 " // pacia1716 + : "+r"(x17) + : "r"(x16), "r"(x14)); + } + } + return x17; #endif - return value; } + void setIP(uint64_t value) { -#if defined(_LIBUNWIND_TARGET_AARCH64_AUTHENTICATED_UNWINDING) + if (!isReturnAddressSigned()) { + _registers.__pc = value; + return; + } + +#if !defined(_LIBUNWIND_IS_NATIVE_ONLY) + abortCrossRASigning(); +#else // Note the value which was set should have been signed with the SP. // We then resign with the slot we are being stored in to so that both SP // and LR can't be spoofed at the same time. - value = (uint64_t)ptrauth_auth_and_resign((void *)value, - ptrauth_key_return_address, - getSP(), - ptrauth_key_return_address, - &_registers.__pc); + register uint64_t x17 __asm("x17") = value; + register uint64_t x16 __asm("x16") = getSP(); + register uint64_t x14 __asm("x14") = + reinterpret_cast<uint64_t>(&_registers.__pc); + if (isReturnAddressSignedWithPC()) { + register uint64_t x15 __asm("x15") = + _registers.__ra_sign.__second_modifier; + if (isReturnAddressSignedWithBKey()) { + asm("hint 0x27 \n\t" // pacm + "hint 0xe \n\t" // autib1716 + "mov x16, x14\n\t" + "hint 0xa " // pacib1716 + : "+r"(x17) + : "r"(x16), "r"(x15), "r"(x14)); + } else { + asm("hint 0x27 \n\t" // pacm + "hint 0xc \n\t" // autia1716 + "mov x16, x14\n\t" + "hint 0x8 " // pacia1716 + : "+r"(x17) + : "r"(x16), "r"(x15), "r"(x14)); + } + } else { + if (isReturnAddressSignedWithBKey()) { + asm("hint 0xe \n\t" // autib1716 + "mov x16, x14\n\t" + "hint 0xa " // pacib1716 + : "+r"(x17) + : "r"(x16), "r"(x14)); + } else { + asm("hint 0xc \n\t" // autia1716 + "mov x16, x14\n\t" + "hint 0x8 " // pacia1716 + : "+r"(x17) + : "r"(x16), "r"(x14)); + } + } + _registers.__pc = x17; #endif - _registers.__pc = value; } + uint64_t getFP() const { return _registers.__fp; } void setFP(uint64_t value) { _registers.__fp = value; } -#if defined(_LIBUNWIND_TARGET_AARCH64_AUTHENTICATED_UNWINDING) + // NOTE: For full-fledged PAuth ABIs like Apple's arm64e and Linux's + // pauthtest link_reg_t is __ptrauth-qualified. So, LR is re-signed with + // link_reg_t signing scheme after it is authenticated with this function. + // When just pac-ret is used, link_reg_t is not __ptrauth-qualified and LR + // remains unsigned after authentication. + // TODO: avoid exposing unsigned LR in absence of full-fledged PAuth ABI. void loadAndAuthenticateLinkRegister(reg_t inplaceAuthedLinkRegister, link_reg_t *referenceAuthedLinkRegister) { - // If we are in an arm64/arm64e frame, then the PC should have been signed - // with the SP - *referenceAuthedLinkRegister = - (uint64_t)ptrauth_auth_data((void *)inplaceAuthedLinkRegister, - ptrauth_key_return_address, - _registers.__sp); - } + if (!isReturnAddressSigned()) { + *referenceAuthedLinkRegister = inplaceAuthedLinkRegister; + return; + } + +#if !defined(_LIBUNWIND_IS_NATIVE_ONLY) + abortCrossRASigning(); +#else + register reg_t x17 __asm("x17") = inplaceAuthedLinkRegister; + register reg_t x16 __asm("x16") = getSP(); + if (isReturnAddressSignedWithPC()) { + register reg_t x15 __asm("x15") = _registers.__ra_sign.__second_modifier; + if (isReturnAddressSignedWithBKey()) { + asm("hint 0x27\n\t" // pacm + "hint 0xe " // autib1716 + : "+r"(x17) + : "r"(x16), "r"(x15)); + } else { + asm("hint 0x27\n\t" // pacm + "hint 0xc " // autia1716 + : "+r"(x17) + : "r"(x16), "r"(x15)); + } + } else { + if (isReturnAddressSignedWithBKey()) { + asm("hint 0xe" : "+r"(x17) : "r"(x16)); // autib1716 + } else { + asm("hint 0xc" : "+r"(x17) : "r"(x16)); // autia1716 + } + } + *referenceAuthedLinkRegister = x17; #endif + } + + bool isReturnAddressSigned() const { + return _registers.__ra_sign.__state & 1; + } + bool isReturnAddressSignedWithPC() const { + return _registers.__ra_sign.__state & 2; + } + bool isReturnAddressSignedWithBKey() const { + return _registers.__ra_sign.__use_b_key; + } private: +#if !defined(_LIBUNWIND_IS_NATIVE_ONLY) + void abortCrossRASigning() const { + // We should never go here since non-null RA signed state is either set + // by architecture-specific __unw_getcontext or by stepWithDwarf which + // already contains a corresponding check and should have already + // emitted the UNW_ECROSSRASIGNING error. + _LIBUNWIND_ABORT("UNW_ECROSSRASIGNING"); + } +#endif + uint64_t lazyGetVG() const; void zaDisable() const { @@ -1945,7 +2075,12 @@ class _LIBUNWIND_HIDDEN Registers_arm64 { uint64_t __lr = 0; // Link register x30 uint64_t __sp = 0; // Stack pointer x31 uint64_t __pc = 0; // Program counter - uint64_t __ra_sign_state = 0; // RA sign state register + struct RASign { + uint64_t __state = 0; // RA sign state register + uint64_t __second_modifier = 0; // Additional modifier used for RA + // signing with FEAT_PAuth_LR + uint64_t __use_b_key = 0; // 0 for IA key, 1 for IB key + } __ra_sign; }; struct Misc { @@ -1971,14 +2106,13 @@ inline Registers_arm64::Registers_arm64(const void *registers) { static_assert((check_fit<Registers_arm64, unw_context_t>::does_fit), "arm64 registers do not fit into unw_context_t"); memcpy(&_registers, registers, sizeof(_registers)); - static_assert(sizeof(GPRs) == 0x110, - "expected VFP registers to be at offset 272"); + static_assert(sizeof(GPRs) == 0x120, + "expected VFP registers to be at offset 288"); memcpy(_vectorHalfRegisters, static_cast<const uint8_t *>(registers) + sizeof(GPRs), sizeof(_vectorHalfRegisters)); _misc_registers.__vg = 0; -#if defined(_LIBUNWIND_TARGET_AARCH64_AUTHENTICATED_UNWINDING) // We have to do some pointer authentication fixups after this copy, // and as part of that we need to load the source pc without // authenticating so that we maintain the signature for the resigning @@ -1987,7 +2121,6 @@ inline Registers_arm64::Registers_arm64(const void *registers) { memmove(&pcRegister, ((uint8_t *)&_registers) + offsetof(GPRs, __pc), sizeof(pcRegister)); setIP(pcRegister); -#endif } inline Registers_arm64::Registers_arm64(const Registers_arm64 &other) { @@ -2008,12 +2141,16 @@ inline bool Registers_arm64::validRegister(int regNum) const { return true; if (regNum == UNW_REG_SP) return true; + if (regNum == UNW_AARCH64_RA_SIGN_STATE) + return true; + if (regNum == UNW_AARCH64_RA_SIGN_SECOND_MODIFIER) + return true; + if (regNum == UNW_AARCH64_RA_SIGN_USE_B_KEY) + return true; if (regNum < 0) return false; if (regNum > 95) return false; - if (regNum == UNW_AARCH64_RA_SIGN_STATE) - return true; if (regNum == UNW_AARCH64_VG) return true; if ((regNum > 32) && (regNum < 64)) @@ -2041,7 +2178,11 @@ inline uint64_t Registers_arm64::getRegister(int regNum) const { if (regNum == UNW_REG_SP || regNum == UNW_AARCH64_SP) return _registers.__sp; if (regNum == UNW_AARCH64_RA_SIGN_STATE) - return _registers.__ra_sign_state; + return _registers.__ra_sign.__state; + if (regNum == UNW_AARCH64_RA_SIGN_SECOND_MODIFIER) + return _registers.__ra_sign.__second_modifier; + if (regNum == UNW_AARCH64_RA_SIGN_USE_B_KEY) + return _registers.__ra_sign.__use_b_key; if (regNum == UNW_AARCH64_FP) return getFP(); if (regNum == UNW_AARCH64_LR) @@ -2059,7 +2200,11 @@ inline void Registers_arm64::setRegister(int regNum, uint64_t value) { else if (regNum == UNW_REG_SP || regNum == UNW_AARCH64_SP) _registers.__sp = value; else if (regNum == UNW_AARCH64_RA_SIGN_STATE) - _registers.__ra_sign_state = value; + _registers.__ra_sign.__state = value; + else if (regNum == UNW_AARCH64_RA_SIGN_SECOND_MODIFIER) + _registers.__ra_sign.__second_modifier = value; + else if (regNum == UNW_AARCH64_RA_SIGN_USE_B_KEY) + _registers.__ra_sign.__use_b_key = value; else if (regNum == UNW_AARCH64_FP) setFP(value); else if (regNum == UNW_AARCH64_LR) diff --git a/libunwind/src/UnwindCursor.hpp b/libunwind/src/UnwindCursor.hpp index afa0cae790377..60ef722ae08e5 100644 --- a/libunwind/src/UnwindCursor.hpp +++ b/libunwind/src/UnwindCursor.hpp @@ -1065,7 +1065,7 @@ class UnwindCursor : public AbstractUnwindCursor{ const UnwindInfoSections §s, uint32_t fdeSectionOffsetHint = 0); int stepWithDwarfFDE(bool stage2) { -#if defined(_LIBUNWIND_TARGET_AARCH64_AUTHENTICATED_UNWINDING) +#if defined(_LIBUNWIND_TARGET_AARCH64) typename R::reg_t rawPC = this->getReg(UNW_REG_IP); typename R::link_reg_t pc; _registers.loadAndAuthenticateLinkRegister(rawPC, &pc); @@ -2735,7 +2735,8 @@ void UnwindCursor<A, R>::setInfoBasedOnIPRegister(bool isReturnAddress) { #endif typename R::link_reg_t pc; -#if defined(_LIBUNWIND_TARGET_AARCH64_AUTHENTICATED_UNWINDING) +#if defined(_LIBUNWIND_TARGET_AARCH64) && \ + !(defined(_LIBUNWIND_SUPPORT_SEH_UNWIND) && defined(_WIN32)) _registers.loadAndAuthenticateLinkRegister(rawPC, &pc); #else pc = rawPC; @@ -3305,7 +3306,8 @@ void UnwindCursor<A, R>::getInfo(unw_proc_info_t *info) { template <typename A, typename R> bool UnwindCursor<A, R>::getFunctionName(char *buf, size_t bufLen, unw_word_t *offset) { -#if defined(_LIBUNWIND_TARGET_AARCH64_AUTHENTICATED_UNWINDING) +#if defined(_LIBUNWIND_TARGET_AARCH64) && \ + !(defined(_LIBUNWIND_SUPPORT_SEH_UNWIND) && defined(_WIN32)) typename R::reg_t rawPC = this->getReg(UNW_REG_IP); typename R::link_reg_t pc; _registers.loadAndAuthenticateLinkRegister(rawPC, &pc); diff --git a/libunwind/src/UnwindLevel1.c b/libunwind/src/UnwindLevel1.c index 7368b3cb80336..361bf6c3c1575 100644 --- a/libunwind/src/UnwindLevel1.c +++ b/libunwind/src/UnwindLevel1.c @@ -615,14 +615,58 @@ _LIBUNWIND_EXPORT uintptr_t _Unwind_GetIP(struct _Unwind_Context *context) { unw_word_t result; __unw_get_reg(cursor, UNW_REG_IP, &result); -#if defined(_LIBUNWIND_TARGET_AARCH64_AUTHENTICATED_UNWINDING) - // If we are in an arm64e frame, then the PC should have been signed with the - // sp +#if defined(_LIBUNWIND_TARGET_AARCH64) && \ + !(defined(_LIBUNWIND_SUPPORT_SEH_UNWIND) && defined(_WIN32)) { - unw_word_t sp; - __unw_get_reg(cursor, UNW_REG_SP, &sp); - result = (unw_word_t)ptrauth_auth_data((void *)result, - ptrauth_key_return_address, sp); + unw_word_t raSignState, raSignUseBKey; + __unw_get_reg(cursor, UNW_AARCH64_RA_SIGN_STATE, &raSignState); + __unw_get_reg(cursor, UNW_AARCH64_RA_SIGN_USE_B_KEY, &raSignUseBKey); + + bool isReturnAddressSigned = (raSignState & 1); + bool isReturnAddressSignedWithPC = (raSignState & 2); + + if (isReturnAddressSigned) { +#if !defined(_LIBUNWIND_IS_NATIVE_ONLY) + // We should never go here since non-null RA signed state is either set + // by architecture-specific __unw_getcontext or by stepWithDwarf which + // already contains a corresponding check and should have already + // emitted the UNW_ECROSSRASIGNING error. + _LIBUNWIND_ABORT("UNW_ECROSSRASIGNING"); +#else + unw_word_t sp; + __unw_get_reg(cursor, UNW_REG_SP, &sp); + + register uint64_t x17 __asm("x17") = result; + register uint64_t x16 __asm("x16") = sp; + + if (isReturnAddressSignedWithPC) { + unw_word_t raSignSecondModifier; + __unw_get_reg(cursor, UNW_AARCH64_RA_SIGN_SECOND_MODIFIER, + &raSignSecondModifier); + + register uint64_t x15 __asm("x15") = raSignSecondModifier; + + if (raSignUseBKey) { + __asm__("hint 0x27\n\t" // pacm + "hint 0xe " // autib1716 + : "+r"(x17) + : "r"(x16), "r"(x15)); + } else { + __asm__("hint 0x27\n\t" // pacm + "hint 0xc " // autia1716 + : "+r"(x17) + : "r"(x16), "r"(x15)); + } + } else { + if (raSignUseBKey) { + __asm__("hint 0xe" : "+r"(x17) : "r"(x16)); // autib1716 + } else { + __asm__("hint 0xc" : "+r"(x17) : "r"(x16)); // autia1716 + } + } + result = x17; +#endif + } } #endif diff --git a/libunwind/src/UnwindRegistersRestore.S b/libunwind/src/UnwindRegistersRestore.S index 76a80344034f7..a98b9cb85de3c 100644 --- a/libunwind/src/UnwindRegistersRestore.S +++ b/libunwind/src/UnwindRegistersRestore.S @@ -672,7 +672,8 @@ DEFINE_LIBUNWIND_FUNCTION(__libunwind_Registers_arm64_jumpto) ldp x8, x9, [x0, #0x040] ldp x10,x11, [x0, #0x050] ldp x12,x13, [x0, #0x060] - ldp x14,x15, [x0, #0x070] + // x14 and x15 are used as scratch registers in this function + // and will be restored later. // x16 and x17 were clobbered by the call into the unwinder, so no point in // restoring them. ldp x18,x19, [x0, #0x090] @@ -683,42 +684,143 @@ 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 + ldr x15, [x0, #0x110] // x15 = __ra_sign.__second_modifier + add x16, x0, #0x100 // x16 = &__pc + ldr x17, [x0, #0x100] // x17 = __pc + + ldr x1, [x0, #0x108] // x1 = __ra_sign.__state + and x1, x1, #1 + cbz x1, .Lresign_end + + // lr needs resign + ldr x1, [x0, #0x108] // x1 = __ra_sign.__state + and x1, x1, #2 + cbnz x1, .Lresign_with_pc + + // lr needs resign without pc as a second modifier + ldr x1, [x0, #0x118] // x1 = __ra_sign.__use_b_key + cbnz x1, .Lresign_with_b_key + + // lr needs resign with A key and without pc as a second modifier + hint 0xc // autia1716 + mov x16, x14 // x16 = __sp + hint 0x8 // pacia1716 + b .Lresign_end + +.Lresign_with_b_key: + // lr needs resign with B key and without pc as a second modifier + hint 0xe // autib1716 + mov x16, x14 // x16 = __sp + hint 0xa // pacib1716 + b .Lresign_end + +.Lresign_with_pc: + // lr needs resign with pc as a second modifier + ldr x1, [x0, #0x118] // x1 = __ra_sign.__use_b_key + cbnz x1, .Lresign_with_pc_b_key + + // lr needs resign with A key and pc as a second modifier + hint 0xc // autia1716 + mov x16, x14 // x16 = __sp + hint 0x27 // pacm + hint 0x8 // pacia1716 + b .Lresign_end + +.Lresign_with_pc_b_key: + // lr needs resign with B key and pc as a second modifier + hint 0xe // autib1716 + mov x16, x14 // x16 = __sp + hint 0x27 // pacm + hint 0xa // pacib1716 + b .Lresign_end + +.Lresign_end: + mov lr, x17 // assign final lr value + ldp x14,x15, [x0, #0x070] // restore x14,x15 + ldr x16, [x0, #0x110] // second modifier for auti{a|b}sp with PACM + // Finally, restore sp. This must be done after the last read from the // context struct, because it is allocated on the stack, and an exception // could clobber the de-allocated portion of the stack after sp has been // restored. + ldr x17, [x0, #0x0F8] // load sp into scratch - ldr x16, [x0, #0x0F8] // load sp into scratch - ldr lr, [x0, #0x100] // restore pc into lr + ldr x1, [x0, #0x108] // x1 = __ra_sign.__state + and x1, x1, #1 + cbz x1, .Lauth_end -#if __has_feature(ptrauth_calls) - // The LR is signed with its address inside the register state. Time - // to resign to be a regular ROP protected signed pointer - add x1, x0, #0x100 - autib lr, x1 - pacib lr, x16 // signed the scratch register for sp -#endif + // lr is signed + ldr x1, [x0, #0x108] // x1 = __ra_sign.__state + and x1, x1, #2 + cbnz x1, .Lauth_with_pc + + // lr is signed without pc as a second modifier + ldr x1, [x0, #0x118] // x1 = __ra_sign.__use_b_key + cbnz x1, .Lauth_with_b_key + + // lr is signed with A key and without pc as a second modifier + ldp x0, x1, [x0, #0x000] // restore x0,x1 + mov sp,x17 // restore sp + hint 0x1d // autiasp + ret x30 + +.Lauth_with_b_key: + // lr is signed with B key and without pc as a second modifier + ldp x0, x1, [x0, #0x000] // restore x0,x1 + mov sp,x17 // restore sp + hint 0x1f // autibsp + ret x30 +.Lauth_with_pc: + // lr is signed with pc as a second modifier + ldr x1, [x0, #0x118] // x1 = __ra_sign.__use_b_key + cbnz x1, .Lauth_with_pc_b_key + + // lr is signed with A key and pc as a second modifier + ldp x0, x1, [x0, #0x000] // restore x0,x1 + mov sp,x17 // restore sp + hint 0x27 // pacm + hint 0x1d // autiasp + ret x30 + +.Lauth_with_pc_b_key: + // lr is signed with B key and pc as a second modifier + ldp x0, x1, [x0, #0x000] // restore x0,x1 + mov sp,x17 // restore sp + hint 0x27 // pacm + hint 0x1f // autibsp + ret x30 + +.Lauth_end: ldp x0, x1, [x0, #0x000] // restore x0,x1 - mov sp,x16 // restore sp + mov sp,x17 // restore sp #if defined(__ARM_FEATURE_GCS_DEFAULT) // If GCS is enabled we need to push the address we're returning to onto the // GCS stack. We can't just return using br, as there won't be a BTI landing @@ -729,12 +831,7 @@ DEFINE_LIBUNWIND_FUNCTION(__libunwind_Registers_arm64_jumpto) gcspushm x30 Lnogcs: #endif - -#if __has_feature(ptrauth_calls) - retab -#else ret x30 // jump to pc -#endif #elif defined(__arm__) && !defined(__APPLE__) diff --git a/libunwind/src/UnwindRegistersSave.S b/libunwind/src/UnwindRegistersSave.S index f988fd461def1..252353b7861f7 100644 --- a/libunwind/src/UnwindRegistersSave.S +++ b/libunwind/src/UnwindRegistersSave.S @@ -769,6 +769,20 @@ LnoR2Fix: #define __has_feature(__feature) 0 #endif +#ifdef __ARM_FEATURE_PAC_DEFAULT +# define UNW_ARM_FEATURE_PAC_DEFAULT __ARM_FEATURE_PAC_DEFAULT +#else +# if __has_feature(ptrauth_returns) +# define UNW_ARM_FEATURE_PAC_DEFAULT 2 +# else +# define UNW_ARM_FEATURE_PAC_DEFAULT 0 +# endif +#endif + +#define UNW_RA_SIGNED ((UNW_ARM_FEATURE_PAC_DEFAULT & 3) != 0) +#define UNW_RA_SIGNED_WITH_B_KEY ((UNW_ARM_FEATURE_PAC_DEFAULT & 2) != 0) +#define UNW_RA_SIGNED_WITH_PC ((UNW_ARM_FEATURE_PAC_DEFAULT & 8) != 0) + // // extern int __unw_getcontext(unw_context_t* thread_state) // @@ -778,8 +792,16 @@ LnoR2Fix: .p2align 2 DEFINE_LIBUNWIND_FUNCTION(__unw_getcontext) -#if __has_feature(ptrauth_calls) - pacibsp +#if UNW_RA_SIGNED +# if UNW_RA_SIGNED_WITH_PC + hint 0x27 // pacm +.L__unw_getcontext_start: +# endif +# if UNW_RA_SIGNED_WITH_B_KEY + hint 0x1b // pacibsp +# else + hint 0x19 // paciasp +# endif #endif stp x0, x1, [x0, #0x000] @@ -801,33 +823,69 @@ DEFINE_LIBUNWIND_FUNCTION(__unw_getcontext) mov x1,sp str x1, [x0, #0x0F8] str x30, [x0, #0x100] // store return address as pc + + // Fill subfields of __ra_sign field +#if UNW_RA_SIGNED +# if UNW_RA_SIGNED_WITH_PC + adrp x16, .L__unw_getcontext_start + add x16, x16, :lo12:.L__unw_getcontext_start + str x16, [x0, #0x110] // __second_modifier = x16 +# else + str xzr, [x0, #0x110] // __second_modifier = 0 +# endif + + mov x16, #1 + str x16, [x0, #0x108] // __state = 1 + +# if UNW_RA_SIGNED_WITH_B_KEY + str x16, [x0, #0x118] // __use_b_key = 1 +# else + str xzr, [x0, #0x118] // __use_b_key = 0 +# endif + + ldr x16, [x0, #0x080] // restore x16 after clobber +#else + str xzr, [x0, #0x108] // __state = 0 + str xzr, [x0, #0x110] // __second_modifier = 0 + str xzr, [x0, #0x118] // __use_b_key = 0 +#endif + // skip cpsr #if defined(__ARM_FP) && __ARM_FP != 0 - stp d0, d1, [x0, #0x110] - stp d2, d3, [x0, #0x120] - stp d4, d5, [x0, #0x130] - stp d6, d7, [x0, #0x140] - stp d8, d9, [x0, #0x150] - stp d10,d11, [x0, #0x160] - stp d12,d13, [x0, #0x170] - stp d14,d15, [x0, #0x180] - stp d16,d17, [x0, #0x190] - stp d18,d19, [x0, #0x1A0] - stp d20,d21, [x0, #0x1B0] - stp d22,d23, [x0, #0x1C0] - stp d24,d25, [x0, #0x1D0] - stp d26,d27, [x0, #0x1E0] - stp d28,d29, [x0, #0x1F0] - str d30, [x0, #0x200] - str d31, [x0, #0x208] + stp d0, d1, [x0, #0x120] + stp d2, d3, [x0, #0x130] + stp d4, d5, [x0, #0x140] + stp d6, d7, [x0, #0x150] + stp d8, d9, [x0, #0x160] + stp d10,d11, [x0, #0x170] + stp d12,d13, [x0, #0x180] + stp d14,d15, [x0, #0x190] + stp d16,d17, [x0, #0x1A0] + stp d18,d19, [x0, #0x1B0] + stp d20,d21, [x0, #0x1C0] + stp d22,d23, [x0, #0x1D0] + stp d24,d25, [x0, #0x1E0] + stp d26,d27, [x0, #0x1F0] + str d28, [x0, #0x200] + str d29, [x0, #0x208] + str d30, [x0, #0x210] + str d31, [x0, #0x218] #endif mov x0, #0 // return UNW_ESUCCESS -#if __has_feature(ptrauth_calls) - retab -#else - ret +#if UNW_RA_SIGNED +# if UNW_RA_SIGNED_WITH_PC + adrp x16, .L__unw_getcontext_start + add x16, x16, :lo12:.L__unw_getcontext_start + hint 0x27 // pacm +# endif +# if UNW_RA_SIGNED_WITH_B_KEY + hint 0x1f // autibsp +# else + hint 0x1d // autiasp +# endif #endif + ret // // extern "C" int64_t __libunwind_Registers_arm64_za_disable() diff --git a/libunwind/src/libunwind.cpp b/libunwind/src/libunwind.cpp index b3036396c379d..9fdeca0cdbe4b 100644 --- a/libunwind/src/libunwind.cpp +++ b/libunwind/src/libunwind.cpp @@ -162,6 +162,17 @@ _LIBUNWIND_HIDDEN int __unw_set_reg(unw_cursor_t *cursor, unw_regnum_t regNum, _LIBUNWIND_ABORT("Bad unwind through arm64e"); } } +#elif defined(_LIBUNWIND_TARGET_AARCH64) && \ + !(defined(_LIBUNWIND_SUPPORT_SEH_UNWIND) && defined(_WIN32)) + // We expect IP register value to be signed only for a full-fledged + // PAuth ABI such as Apple's arm64e or Linux's pauthtest. Otherwise, + // the value to be assigned to the IP register is an unsigned pointer, + // so we need to update RA sign info and mark the pointer as unsigned. + // This prevents attempts of unsigned pointer authentication in case + // if previously a signed RA was stored in the IP register field. + co->setReg(UNW_AARCH64_RA_SIGN_STATE, 0); + co->setReg(UNW_AARCH64_RA_SIGN_SECOND_MODIFIER, 0); + co->setReg(UNW_AARCH64_RA_SIGN_USE_B_KEY, 0); #endif // If the original call expects stack adjustment, perform this now. >From a9430a2f4fdb449cd460702dc343853d5bb668da Mon Sep 17 00:00:00 2001 From: Daniil Kovalev <[email protected]> Date: Sun, 21 Dec 2025 23:47:33 +0300 Subject: [PATCH 2/2] Check signing scheme flags integrity --- libunwind/include/libunwind.h | 10 +- libunwind/src/DwarfInstructions.hpp | 25 +- libunwind/src/DwarfParser.hpp | 2 +- libunwind/src/Registers.hpp | 516 ++++++++++++++++++------- libunwind/src/UnwindLevel1.c | 140 ++++--- libunwind/src/UnwindRegistersRestore.S | 190 +++++---- libunwind/src/UnwindRegistersSave.S | 69 ++-- libunwind/src/libunwind.cpp | 7 +- 8 files changed, 645 insertions(+), 314 deletions(-) diff --git a/libunwind/include/libunwind.h b/libunwind/include/libunwind.h index 6a56548867642..d711b0cccbfcb 100644 --- a/libunwind/include/libunwind.h +++ b/libunwind/include/libunwind.h @@ -642,11 +642,13 @@ enum { // reserved block UNW_AARCH64_RA_SIGN_STATE = 34, - // The following two registers are not real Dwarf registers and use numbers - // which are not occupied by real Dwarf registers according to + // Warning: the two registers below are intended for internal libunwind use + // only and should not be used by external code. These are not real Dwarf + // registers and they use numbers which are not occupied by real Dwarf + // registers according to // https://github.com/ARM-software/abi-aa/blob/2025Q1/aadwarf64/aadwarf64.rst - UNW_AARCH64_RA_SIGN_SECOND_MODIFIER = 128, - UNW_AARCH64_RA_SIGN_USE_B_KEY = 129, + UNW_AARCH64_RA_SIGNING_SCHEME_SECOND_MODIFIER = 128, + UNW_AARCH64_RA_SIGNING_SCHEME_FLAGS = 129, // FP/vector registers UNW_AARCH64_V0 = 64, diff --git a/libunwind/src/DwarfInstructions.hpp b/libunwind/src/DwarfInstructions.hpp index ad1555db3abd2..2d5b50031c924 100644 --- a/libunwind/src/DwarfInstructions.hpp +++ b/libunwind/src/DwarfInstructions.hpp @@ -75,8 +75,8 @@ class DwarfInstructions { __builtin_unreachable(); } #if defined(_LIBUNWIND_TARGET_AARCH64) - static pint_t getRASignState(A &addressSpace, const R ®isters, pint_t cfa, - const PrologInfo &prolog); + static pint_t getRASignedState(A &addressSpace, const R ®isters, + pint_t cfa, const PrologInfo &prolog); #endif }; @@ -174,9 +174,8 @@ v128 DwarfInstructions<A, R>::getSavedVectorRegister( } #if defined(_LIBUNWIND_TARGET_AARCH64) template <typename A, typename R> -typename A::pint_t -DwarfInstructions<A, R>::getRASignState(A &addressSpace, const R ®isters, - pint_t cfa, const PrologInfo &prolog) { +typename A::pint_t DwarfInstructions<A, R>::getRASignedState( + A &addressSpace, const R ®isters, pint_t cfa, const PrologInfo &prolog) { auto regloc = prolog.savedRegisters[UNW_AARCH64_RA_SIGN_STATE]; if (regloc.location == CFI_Parser<A>::kRegisterUnused) return static_cast<pint_t>(regloc.value); @@ -284,20 +283,16 @@ int DwarfInstructions<A, R>::stepWithDwarf(A &addressSpace, // using the v8.3 pointer authentication extensions. In order to // store signed return address in the registers context structure, we // need to save the signing scheme for this address. - pint_t raSignState = getRASignState(addressSpace, registers, cfa, prolog); - bool isReturnAddressSigned = (raSignState & 1); - if ((R::getArch() == REGISTERS_ARM64) && isReturnAddressSigned && + pint_t raSignState = + getRASignedState(addressSpace, registers, cfa, prolog); + if ((R::getArch() == REGISTERS_ARM64) && raSignState != 0 && returnAddress != 0) { #if !defined(_LIBUNWIND_IS_NATIVE_ONLY) return UNW_ECROSSRASIGNING; #else - newRegisters.setRegister(UNW_AARCH64_RA_SIGN_STATE, raSignState); - if (newRegisters.isReturnAddressSignedWithPC()) { - newRegisters.setRegister(UNW_AARCH64_RA_SIGN_SECOND_MODIFIER, - prolog.ptrAuthDiversifier); - } - newRegisters.setRegister(UNW_AARCH64_RA_SIGN_USE_B_KEY, - cieInfo.addressesSignedWithBKey ? 1 : 0); + newRegisters.setRASigningScheme( + raSignState, cieInfo.addressesSignedWithBKey, + prolog.ptrAuthDiversifier); #endif } #endif diff --git a/libunwind/src/DwarfParser.hpp b/libunwind/src/DwarfParser.hpp index 2b04ae2831f9a..1b657f290b3ac 100644 --- a/libunwind/src/DwarfParser.hpp +++ b/libunwind/src/DwarfParser.hpp @@ -97,7 +97,7 @@ class CFI_Parser { uint32_t spExtraArgSize; RegisterLocation savedRegisters[kMaxRegisterNumber + 1]; #if defined(_LIBUNWIND_TARGET_AARCH64) - pint_t ptrAuthDiversifier; + pint_t ptrAuthDiversifier = 0; #endif enum class InitializeTime { kLazy, kNormal }; diff --git a/libunwind/src/Registers.hpp b/libunwind/src/Registers.hpp index 2245ccba61e4f..69c057758c944 100644 --- a/libunwind/src/Registers.hpp +++ b/libunwind/src/Registers.hpp @@ -1842,6 +1842,78 @@ extern "C" void *__libunwind_shstk_get_jump_target() { } #endif +#if defined(_LIBUNWIND_IS_NATIVE_ONLY) + +#define STRING_IMPL(x) #x +#define STRING(x) STRING_IMPL(x) + +#define RUN_IF_PAUTH_FEATURE_PRESENT(scratchReg, code) \ + "mrs " #scratchReg ", ID_AA64ISAR1_EL1" "\n\t" \ + "lsr " #scratchReg ", " #scratchReg ", #24" "\n\t" \ + "ands " #scratchReg ", " #scratchReg ", #255" "\n\t" \ + "cbnz " #scratchReg ", .Lcheck_pac_code" STRING(__LINE__) "\n\t" \ + "mrs " #scratchReg ", ID_AA64ISAR2_EL1" "\n\t" \ + "lsr " #scratchReg ", " #scratchReg ", #8" "\n\t" \ + "ands " #scratchReg ", " #scratchReg ", #15" "\n\t" \ + "cbnz " #scratchReg ", .Lcheck_pac_code" STRING(__LINE__) "\n\t" \ + "b .Lcheck_pac_end" STRING(__LINE__) "\n\t" \ + ".Lcheck_pac_code" STRING(__LINE__) ":" "\n\t" \ + code "\n\t" \ + ".Lcheck_pac_end" STRING(__LINE__) ":" "\n\t" + +// The '0xc470 + KeyID' trap code is used by LLVM codegen to abort execution +// on auth failure w/o FPAC. Mimic this behavior assuming GA key ID is 4. +#define PACGA_TRAP "brk #0xc474\n\t" + +#define CHECK_SIGNING_SCHEME_FLAGS_INTEGRITY(schemeReg, schemePacReg, \ + modifierReg, scratchReg) \ + RUN_IF_PAUTH_FEATURE_PRESENT( \ + scratchReg, \ + "pacga " #modifierReg ", " #schemeReg ", " #modifierReg "\n\t" \ + "cmp " #modifierReg ", " #schemePacReg "\n\t" \ + "b.eq .Lcheck_integrity_success_" STRING(__LINE__) "\n\t" \ + PACGA_TRAP "\n\t" \ + ".Lcheck_integrity_success_" STRING(__LINE__) ":" "\n\t" \ + ) + +#define CHECK_SIGNING_SCHEME_FLAGS_INTEGRITY_FOR_PAUTHABI(schemeReg) \ + "cmp " #schemeReg ", 5" "\n\t" \ + "b.eq .Lcheck_integrity_for_pauthabi_success_" STRING(__LINE__) "\n\t" \ + PACGA_TRAP "\n\t" \ + ".Lcheck_integrity_for_pauthabi_success_" STRING(__LINE__) ":" "\n\t" \ + +#define SIGNING_SCHEME_FLAGS_SWITCH(schemeReg, codeIf0, codeIf1, \ + codeIf3, codeIf5, codeIf7) \ + "cmp " #schemeReg ", #0" "\n\t" \ + "b.ne .Lswitch_1_" STRING(__LINE__) "\n\t" \ + codeIf0 "\n\t" \ + "b .Lswitch_end_" STRING(__LINE__) "\n\t" \ + ".Lswitch_1_" STRING(__LINE__) ":" "\n\t" \ + "cmp " #schemeReg ", #1" "\n\t" \ + "b.ne .Lswitch_3_" STRING(__LINE__) "\n\t" \ + codeIf1 "\n\t" \ + "b .Lswitch_end_" STRING(__LINE__) "\n\t" \ + ".Lswitch_3_" STRING(__LINE__) ":" "\n\t" \ + "cmp " #schemeReg ", #3" "\n\t" \ + "b.ne .Lswitch_5_" STRING(__LINE__) "\n\t" \ + codeIf3 "\n\t" \ + "b .Lswitch_end_" STRING(__LINE__) "\n\t" \ + ".Lswitch_5_" STRING(__LINE__) ":" "\n\t" \ + "cmp " #schemeReg ", #5" "\n\t" \ + "b.ne .Lswitch_7_" STRING(__LINE__) "\n\t" \ + codeIf5 "\n\t" \ + "b .Lswitch_end_" STRING(__LINE__) "\n\t" \ + ".Lswitch_7_" STRING(__LINE__) ":" "\n\t" \ + "cmp " #schemeReg ", #7" "\n\t" \ + "b.ne .Lswitch_unexpected" STRING(__LINE__) "\n\t" \ + codeIf7 "\n\t" \ + "b .Lswitch_end_" STRING(__LINE__) "\n\t" \ + ".Lswitch_unexpected" STRING(__LINE__) ":" "\n\t" \ + PACGA_TRAP "\n\t" \ + ".Lswitch_end_" STRING(__LINE__) ":" "\n\t" + +#endif + class _LIBUNWIND_HIDDEN Registers_arm64 { public: Registers_arm64() = default; @@ -1878,108 +1950,112 @@ class _LIBUNWIND_HIDDEN Registers_arm64 { uint64_t getSP() const { return _registers.__sp; } void setSP(uint64_t value) { _registers.__sp = value; } - uint64_t getIP() const { + uint64_t getIP() const { uint64_t value = _registers.__pc; - if (!isReturnAddressSigned()) - return value; - -#if !defined(_LIBUNWIND_IS_NATIVE_ONLY) - abortCrossRASigning(); -#else +#if defined(_LIBUNWIND_IS_NATIVE_ONLY) // Note the value of the PC was signed to its address in the register state // but everyone else expects it to be sign by the SP, so convert on return. register uint64_t x17 __asm("x17") = value; register uint64_t x16 __asm("x16") = reinterpret_cast<uint64_t>(&_registers.__pc); - register uint64_t x14 __asm("x14") = getSP(); - if (isReturnAddressSignedWithPC()) { - register uint64_t x15 __asm("x15") = - _registers.__ra_sign.__second_modifier; - if (isReturnAddressSignedWithBKey()) { - asm("hint 0xe \n\t" // autib1716 - "mov x16, x14\n\t" - "hint 0x27 \n\t" // pacm - "hint 0xa " // pacib1716 - : "+r"(x17) - : "r"(x16), "r"(x15), "r"(x14)); - } else { - asm("hint 0xc \n\t" // autia1716 - "mov x16, x14\n\t" - "hint 0x27 \n\t" // pacm - "hint 0x8 " // pacia1716 - : "+r"(x17) - : "r"(x16), "r"(x15), "r"(x14)); - } - } else { - if (isReturnAddressSignedWithBKey()) { - asm("hint 0xe \n\t" // autib1716 - "mov x16, x14\n\t" - "hint 0xa " // pacib1716 - : "+r"(x17) - : "r"(x16), "r"(x14)); - } else { - asm("hint 0xc \n\t" // autia1716 - "mov x16, x14\n\t" - "hint 0x8 " // pacia1716 - : "+r"(x17) - : "r"(x16), "r"(x14)); - } - } + register uint64_t x15 __asm("x15") = + _registers.__ra_signing_scheme.__second_modifier; + register uint64_t x14 __asm("x14") = _registers.__sp; + register uint64_t x13 __asm("x13") = + reinterpret_cast<uint64_t>(&_registers.__ra_signing_scheme.__flags); + register uint64_t x12 __asm("x12") = _registers.__ra_signing_scheme.__flags; + register uint64_t x11 __asm("x11") = + _registers.__ra_signing_scheme.__flags_pac; + + asm(CHECK_SIGNING_SCHEME_FLAGS_INTEGRITY( + /*schemeReg=*/x12, /*schemePacReg=*/x11, + /*modifierReg=*/x13, /*scratchReg=*/x10) +#if defined(_LIBUNWIND_TARGET_AARCH64_AUTHENTICATED_UNWINDING) + CHECK_SIGNING_SCHEME_FLAGS_INTEGRITY_FOR_PAUTHABI(/*schemeReg=*/x12) +#endif + SIGNING_SCHEME_FLAGS_SWITCH( + /*schemeReg=*/x12, + /*codeIf0=*/"", + /*codeIf1=*/"hint 0xc \n\t" // autia1716 + "mov x16, x14\n\t" + "hint 0x8 \n\t", // pacia1716 + /*codeIf3=*/"hint 0x27 \n\t" // pacm + "hint 0xc \n\t" // autia1716 + "mov x16, x14\n\t" + "hint 0x27 \n\t" // pacm + "hint 0x8 \n\t", // pacia1716 + /*codeIf5=*/"hint 0xe \n\t" // autib1716 + "mov x16, x14\n\t" + "hint 0xa \n\t", // pacib1716 + /*codeIf7=*/"hint 0x27 \n\t" // pacm + "hint 0xe \n\t" // autib1716 + "mov x16, x14\n\t" + "hint 0x27 \n\t" // pacm + "hint 0xa \n\t" // pacib1716 + ) + : "+r"(x17) + : "r"(x16), "r"(x15), "r"(x14), "r"(x13), "r"(x12), "r"(x11) + ); return x17; +#else + if (_registers.__ra_signing_scheme.__flags != 0) + abortCrossRASigningSchemeing(); + return value; #endif } void setIP(uint64_t value) { - if (!isReturnAddressSigned()) { - _registers.__pc = value; - return; - } - -#if !defined(_LIBUNWIND_IS_NATIVE_ONLY) - abortCrossRASigning(); -#else +#if defined(_LIBUNWIND_IS_NATIVE_ONLY) // Note the value which was set should have been signed with the SP. // We then resign with the slot we are being stored in to so that both SP // and LR can't be spoofed at the same time. register uint64_t x17 __asm("x17") = value; - register uint64_t x16 __asm("x16") = getSP(); + register uint64_t x16 __asm("x16") = _registers.__sp; + register uint64_t x15 __asm("x15") = + _registers.__ra_signing_scheme.__second_modifier; register uint64_t x14 __asm("x14") = reinterpret_cast<uint64_t>(&_registers.__pc); - if (isReturnAddressSignedWithPC()) { - register uint64_t x15 __asm("x15") = - _registers.__ra_sign.__second_modifier; - if (isReturnAddressSignedWithBKey()) { - asm("hint 0x27 \n\t" // pacm - "hint 0xe \n\t" // autib1716 - "mov x16, x14\n\t" - "hint 0xa " // pacib1716 - : "+r"(x17) - : "r"(x16), "r"(x15), "r"(x14)); - } else { - asm("hint 0x27 \n\t" // pacm - "hint 0xc \n\t" // autia1716 - "mov x16, x14\n\t" - "hint 0x8 " // pacia1716 - : "+r"(x17) - : "r"(x16), "r"(x15), "r"(x14)); - } - } else { - if (isReturnAddressSignedWithBKey()) { - asm("hint 0xe \n\t" // autib1716 - "mov x16, x14\n\t" - "hint 0xa " // pacib1716 - : "+r"(x17) - : "r"(x16), "r"(x14)); - } else { - asm("hint 0xc \n\t" // autia1716 - "mov x16, x14\n\t" - "hint 0x8 " // pacia1716 - : "+r"(x17) - : "r"(x16), "r"(x14)); - } - } + register uint64_t x13 __asm("x13") = + reinterpret_cast<uint64_t>(&_registers.__ra_signing_scheme.__flags); + register uint64_t x12 __asm("x12") = _registers.__ra_signing_scheme.__flags; + register uint64_t x11 __asm("x11") = + _registers.__ra_signing_scheme.__flags_pac; + + asm(CHECK_SIGNING_SCHEME_FLAGS_INTEGRITY( + /*schemeReg=*/x12, /*schemePacReg=*/x11, /*modifierReg=*/x13, + /*scratchReg=*/x10) +#if defined(_LIBUNWIND_TARGET_AARCH64_AUTHENTICATED_UNWINDING) + CHECK_SIGNING_SCHEME_FLAGS_INTEGRITY_FOR_PAUTHABI(/*schemeReg=*/x12) +#endif + SIGNING_SCHEME_FLAGS_SWITCH( + /*schemeReg=*/x12, + /*codeIf0=*/"", + /*codeIf1=*/"hint 0xc \n\t" // autia1716 + "mov x16, x14\n\t" + "hint 0x8 \n\t", // pacia1716 + /*codeIf3=*/"hint 0x27 \n\t" // pacm + "hint 0xc \n\t" // autia1716 + "mov x16, x14\n\t" + "hint 0x27 \n\t" // pacm + "hint 0x8 \n\t", // pacia1716 + /*codeIf5=*/"hint 0xe \n\t" // autib1716 + "mov x16, x14\n\t" + "hint 0xa \n\t", // pacib1716 + /*codeIf7=*/"hint 0x27 \n\t" // pacm + "hint 0xe \n\t" // autib1716 + "mov x16, x14\n\t" + "hint 0x27 \n\t" // pacm + "hint 0xa \n\t" // pacib1716 + ) + : "+r"(x17) + : "r"(x16), "r"(x15), "r"(x14), "r"(x13), "r"(x12), "r"(x11) + ); _registers.__pc = x17; +#else + if (_registers.__ra_signing_scheme.__flags != 0) + abortCrossRASigningSchemeing(); + _registers.__pc = value; #endif } @@ -1995,60 +2071,172 @@ class _LIBUNWIND_HIDDEN Registers_arm64 { void loadAndAuthenticateLinkRegister(reg_t inplaceAuthedLinkRegister, link_reg_t *referenceAuthedLinkRegister) { - if (!isReturnAddressSigned()) { - *referenceAuthedLinkRegister = inplaceAuthedLinkRegister; - return; - } - -#if !defined(_LIBUNWIND_IS_NATIVE_ONLY) - abortCrossRASigning(); -#else +#if defined(_LIBUNWIND_IS_NATIVE_ONLY) register reg_t x17 __asm("x17") = inplaceAuthedLinkRegister; - register reg_t x16 __asm("x16") = getSP(); - if (isReturnAddressSignedWithPC()) { - register reg_t x15 __asm("x15") = _registers.__ra_sign.__second_modifier; - if (isReturnAddressSignedWithBKey()) { - asm("hint 0x27\n\t" // pacm - "hint 0xe " // autib1716 - : "+r"(x17) - : "r"(x16), "r"(x15)); - } else { - asm("hint 0x27\n\t" // pacm - "hint 0xc " // autia1716 - : "+r"(x17) - : "r"(x16), "r"(x15)); - } - } else { - if (isReturnAddressSignedWithBKey()) { - asm("hint 0xe" : "+r"(x17) : "r"(x16)); // autib1716 - } else { - asm("hint 0xc" : "+r"(x17) : "r"(x16)); // autia1716 - } - } + register reg_t x16 __asm("x16") = _registers.__sp; + register uint64_t x15 __asm("x15") = + _registers.__ra_signing_scheme.__second_modifier; + register uint64_t x13 __asm("x13") = + reinterpret_cast<uint64_t>(&_registers.__ra_signing_scheme.__flags); + register uint64_t x12 __asm("x12") = _registers.__ra_signing_scheme.__flags; + register uint64_t x11 __asm("x11") = + _registers.__ra_signing_scheme.__flags_pac; + + asm(CHECK_SIGNING_SCHEME_FLAGS_INTEGRITY( + /*schemeReg=*/x12, /*schemePacReg=*/x11, /*modifierReg=*/x13, + /*scratchReg=*/x10) +#if defined(_LIBUNWIND_TARGET_AARCH64_AUTHENTICATED_UNWINDING) + CHECK_SIGNING_SCHEME_FLAGS_INTEGRITY_FOR_PAUTHABI(/*schemeReg=*/x12) +#endif + SIGNING_SCHEME_FLAGS_SWITCH( + /*schemeReg=*/x12, + /*codeIf0=*/"", + /*codeIf1=*/"hint 0xc \n\t", // autia1716 + /*codeIf3=*/"hint 0x27\n\t" // pacm + "hint 0xc \n\t", // autia1716 + /*codeIf5=*/"hint 0xe \n\t", // autib1716 + /*codeIf7=*/"hint 0x27\n\t" // pacm + "hint 0xe \n\t" // autib1716 + ) + : "+r"(x17) + : "r"(x16), "r"(x15), "r"(x13), "r"(x12), "r"(x11) + ); *referenceAuthedLinkRegister = x17; +#else + if (_registers.__ra_signing_scheme.__flags != 0) + abortCrossRASigningSchemeing(); + *referenceAuthedLinkRegister = inplaceAuthedLinkRegister; #endif } - bool isReturnAddressSigned() const { - return _registers.__ra_sign.__state & 1; - } - bool isReturnAddressSignedWithPC() const { - return _registers.__ra_sign.__state & 2; - } - bool isReturnAddressSignedWithBKey() const { - return _registers.__ra_sign.__use_b_key; +#if defined(_LIBUNWIND_IS_NATIVE_ONLY) + void setRASigningScheme(uint64_t raSignState, + bool isRASigningSchemeedWithBKey, + uint64_t secondModifier) { + register uint64_t x16 __asm("x16") = + raSignState + (isRASigningSchemeedWithBKey ? 4 : 0); + register uint64_t x17 __asm("x17") = + reinterpret_cast<uint64_t>(&_registers.__ra_signing_scheme.__flags); + register uint64_t x15 __asm("x15") = secondModifier; + + asm( +#if defined(_LIBUNWIND_TARGET_AARCH64_AUTHENTICATED_UNWINDING) + CHECK_SIGNING_SCHEME_FLAGS_INTEGRITY_FOR_PAUTHABI(/*schemeReg=*/x16) +#endif + SIGNING_SCHEME_FLAGS_SWITCH( + /*schemeReg=*/x16, + /*codeIf0=*/"cbnz x15, .Lsetscheme_unexpected\n\t", + /*codeIf1=*/"cbnz x15, .Lsetscheme_unexpected\n\t", + /*codeIf3=*/"cbz x15, .Lsetscheme_unexpected\n\t", + /*codeIf5=*/"cbnz x15, .Lsetscheme_unexpected\n\t", + /*codeIf7=*/"cbz x15, .Lsetscheme_unexpected\n\t" + ) + "b .Lsetscheme_ok \n\t" + ".Lsetscheme_unexpected:\n\t" + PACGA_TRAP "\n\t" + ".Lsetscheme_ok: \n\t" + "str x16, [x17, #0] \n\t" + RUN_IF_PAUTH_FEATURE_PRESENT( + /*scratchReg=*/x14, + "pacga x16, x16, x17 \n\t" + "str x16, [x17, #8]\n\t" + ) + "str x15, [x17, #16]\n\t" + : + : "r"(x17), "r"(x16), "r"(x15) + ); } +#endif // defined(_LIBUNWIND_IS_NATIVE_ONLY) private: #if !defined(_LIBUNWIND_IS_NATIVE_ONLY) - void abortCrossRASigning() const { + void abortCrossRASigningSchemeing() const { // We should never go here since non-null RA signed state is either set // by architecture-specific __unw_getcontext or by stepWithDwarf which // already contains a corresponding check and should have already // emitted the UNW_ECROSSRASIGNING error. _LIBUNWIND_ABORT("UNW_ECROSSRASIGNING"); } +#else + void setZeroSigningScheme() { + register uint64_t x17 __asm("x17") = + reinterpret_cast<uint64_t>(&_registers.__ra_signing_scheme.__flags); + + asm(// Ensure that second modifier is zero + "ldr x16, [x17, #16]\n\t" + "cbz .Lsetzero_ok \n\t" + PACGA_TRAP "\n\t" + ".Lsetzero_ok: \n\t" + // Assign zero to signing scheme flags field + "str xzr, [x17, #0] \n\t" + // Compute PAC for signing scheme flags field + RUN_IF_PAUTH_FEATURE_PRESENT( + /*scratchReg=*/x14, + "pacga x16, xzr, x17 \n\t" + "str x16, [x17, #8]\n\t" + ) + : + : "r"(x17) + ); + } + + void recomputeSigningSchemeFlagsPAC(const uint8_t *oldModifier) { + register uint64_t x17 __asm("x17") = + reinterpret_cast<uint64_t>(oldModifier); + register uint64_t x16 __asm("x16") = _registers.__ra_signing_scheme.__flags; + register uint64_t x15 __asm("x15") = + _registers.__ra_signing_scheme.__flags_pac; + register uint64_t x14 __asm("x14") = + reinterpret_cast<uint64_t>(&_registers.__ra_signing_scheme.__flags); + asm(CHECK_SIGNING_SCHEME_FLAGS_INTEGRITY( + /*schemeReg=*/x16, /*schemePacReg=*/x15, + /*modifierReg=*/x17, /*scratchReg=*/x13) +#if defined(_LIBUNWIND_TARGET_AARCH64_AUTHENTICATED_UNWINDING) + CHECK_SIGNING_SCHEME_FLAGS_INTEGRITY_FOR_PAUTHABI(/*schemeReg=*/x16) +#endif + RUN_IF_PAUTH_FEATURE_PRESENT( + /*scratchReg=*/x13, + "pacga x16, x16, x14 \n\t" + "str x16, [x14, #8]\n\t" + ) + : + : "r"(x17), "r"(x16), "r"(x15), "r"(x14) + ); + } + + // Is PAuth is available, returns signing scheme flags value with embedded + // PAC computed with GA key and SP modifier. The PAC computed by PACGA only + // occupies most-significant 32 bits of 64-bit value, which are not + // overlapping with bits occupied by valid flags values. + // If PAuth is not available, just returns signing scheme flags. + uint64_t getRASigningSchemeFlags() const { + register uint64_t x17 __asm("x17") = + reinterpret_cast<uint64_t>(&_registers.__ra_signing_scheme.__flags); + register uint64_t x16 __asm("x16") = _registers.__ra_signing_scheme.__flags; + register uint64_t x15 __asm("x15") = + _registers.__ra_signing_scheme.__flags_pac; + register uint64_t x14 __asm("x14") = _registers.__sp; + + // TODO: reuse code from CHECK_SIGNING_SCHEME_FLAGS_INTEGRITY macro. + asm( +#if defined(_LIBUNWIND_TARGET_AARCH64_AUTHENTICATED_UNWINDING) + CHECK_SIGNING_SCHEME_FLAGS_INTEGRITY_FOR_PAUTHABI(/*schemeReg=*/x16) #endif + RUN_IF_PAUTH_FEATURE_PRESENT( + /*scratchReg=*/x13, + "pacga x17, x16, x17 \n\t" + "cmp x17, x15 \n\t" + "b.eq .Lget_ra_scheme_success\n\t" + PACGA_TRAP "\n\t" + ".Lget_ra_scheme_success: \n\t" + "pacga x14, x16, x14 \n\t" + "orr x16, x14, x16 \n\t" + ) + : "+r"(x16) + : "r"(x17), "r"(x15), "r"(x14)); + return x16; + } +#endif // !defined(_LIBUNWIND_IS_NATIVE_ONLY) uint64_t lazyGetVG() const; @@ -2075,12 +2263,25 @@ class _LIBUNWIND_HIDDEN Registers_arm64 { uint64_t __lr = 0; // Link register x30 uint64_t __sp = 0; // Stack pointer x31 uint64_t __pc = 0; // Program counter - struct RASign { - uint64_t __state = 0; // RA sign state register - uint64_t __second_modifier = 0; // Additional modifier used for RA - // signing with FEAT_PAuth_LR - uint64_t __use_b_key = 0; // 0 for IA key, 1 for IB key - } __ra_sign; + struct RASigningScheme { + // Bitmask for RA signing parameters: + // - bit 0: is RA signed; + // - bit 1: is RA signed with second modifier; + // - bit 2: is RA signed with B key. + // Valid values: 0, 1, 3, 5, 7. Note that it does not directly + // correspond to any physical or Dwarf registers. + uint64_t __flags = 0; + + // Pointer authentication code for the `__flags` value computed with GA + // key (if `pacga` instruction is available) and address diversity. It is + // crucial to check integrity of `__flags` since the signing scheme is + // defined at runtime and attacker can substitute both the pointer and the + // signing scheme. + uint64_t __flags_pac = 0; + + // Additional modifier used for RA signing with FEAT_PAuth_LR. + uint64_t __second_modifier = 0; + } __ra_signing_scheme; }; struct Misc { @@ -2120,6 +2321,10 @@ inline Registers_arm64::Registers_arm64(const void *registers) { uint64_t pcRegister = 0; memmove(&pcRegister, ((uint8_t *)&_registers) + offsetof(GPRs, __pc), sizeof(pcRegister)); +#if defined(_LIBUNWIND_IS_NATIVE_ONLY) + recomputeSigningSchemeFlagsPAC( + (const uint8_t *)registers + offsetof(GPRs, __ra_signing_scheme.__flags)); +#endif setIP(pcRegister); } @@ -2130,6 +2335,9 @@ inline Registers_arm64::Registers_arm64(const Registers_arm64 &other) { inline Registers_arm64 & Registers_arm64::operator=(const Registers_arm64 &other) { memmove(static_cast<void *>(this), &other, sizeof(*this)); +#if defined(_LIBUNWIND_IS_NATIVE_ONLY) + recomputeSigningSchemeFlagsPAC((const uint8_t *)&other._registers.__ra_signing_scheme.__flags); +#endif // We perform this step to ensure that we correctly authenticate and re-sign // the pc after the bitwise copy. setIP(other.getIP()); @@ -2141,12 +2349,16 @@ inline bool Registers_arm64::validRegister(int regNum) const { return true; if (regNum == UNW_REG_SP) return true; + // UNW_AARCH64_RA_SIGN_STATE is a Dwarf pseudo-register and is not stored in + // the context struct. if (regNum == UNW_AARCH64_RA_SIGN_STATE) + return false; +#if defined(_LIBUNWIND_IS_NATIVE_ONLY) + if (regNum == UNW_AARCH64_RA_SIGNING_SCHEME_SECOND_MODIFIER) return true; - if (regNum == UNW_AARCH64_RA_SIGN_SECOND_MODIFIER) - return true; - if (regNum == UNW_AARCH64_RA_SIGN_USE_B_KEY) + if (regNum == UNW_AARCH64_RA_SIGNING_SCHEME_FLAGS) return true; +#endif if (regNum < 0) return false; if (regNum > 95) @@ -2177,12 +2389,12 @@ inline uint64_t Registers_arm64::getRegister(int regNum) const { return getIP(); if (regNum == UNW_REG_SP || regNum == UNW_AARCH64_SP) return _registers.__sp; - if (regNum == UNW_AARCH64_RA_SIGN_STATE) - return _registers.__ra_sign.__state; - if (regNum == UNW_AARCH64_RA_SIGN_SECOND_MODIFIER) - return _registers.__ra_sign.__second_modifier; - if (regNum == UNW_AARCH64_RA_SIGN_USE_B_KEY) - return _registers.__ra_sign.__use_b_key; +#if defined(_LIBUNWIND_IS_NATIVE_ONLY) + if (regNum == UNW_AARCH64_RA_SIGNING_SCHEME_SECOND_MODIFIER) + return _registers.__ra_signing_scheme.__second_modifier; + if (regNum == UNW_AARCH64_RA_SIGNING_SCHEME_FLAGS) + return getRASigningSchemeFlags(); +#endif if (regNum == UNW_AARCH64_FP) return getFP(); if (regNum == UNW_AARCH64_LR) @@ -2199,12 +2411,21 @@ inline void Registers_arm64::setRegister(int regNum, uint64_t value) { setIP(value); else if (regNum == UNW_REG_SP || regNum == UNW_AARCH64_SP) _registers.__sp = value; - else if (regNum == UNW_AARCH64_RA_SIGN_STATE) - _registers.__ra_sign.__state = value; - else if (regNum == UNW_AARCH64_RA_SIGN_SECOND_MODIFIER) - _registers.__ra_sign.__second_modifier = value; - else if (regNum == UNW_AARCH64_RA_SIGN_USE_B_KEY) - _registers.__ra_sign.__use_b_key = value; +#if defined(_LIBUNWIND_IS_NATIVE_ONLY) +#if defined(_LIBUNWIND_TARGET_AARCH64_AUTHENTICATED_UNWINDING) + else if (regNum == UNW_AARCH64_RA_SIGNING_SCHEME_SECOND_MODIFIER || + regNum == UNW_AARCH64_RA_SIGNING_SCHEME_FLAGS) + _LIBUNWIND_ABORT("Cannot change signing scheme for PAuth-enabled ABI"); +#else + else if (regNum == UNW_AARCH64_RA_SIGNING_SCHEME_SECOND_MODIFIER) { + assert(value == 0 && "Expect value 0 when called from __unw_set_reg"); + _registers.__ra_signing_scheme.__second_modifier = 0; + } else if (regNum == UNW_AARCH64_RA_SIGNING_SCHEME_FLAGS) { + assert(value == 0 && "Expect value 0 when called from __unw_set_reg"); + setZeroSigningScheme(); + } +#endif +#endif else if (regNum == UNW_AARCH64_FP) setFP(value); else if (regNum == UNW_AARCH64_LR) @@ -2387,6 +2608,17 @@ inline v128 Registers_arm64::getVectorRegister(int) const { inline void Registers_arm64::setVectorRegister(int, v128) { _LIBUNWIND_ABORT("no arm64 vector register support yet"); } + +#if defined(_LIBUNWIND_IS_NATIVE_ONLY) +#undef SIGNING_SCHEME_FLAGS_SWITCH +#undef CHECK_SIGNING_SCHEME_FLAGS_INTEGRITY_FOR_PAUTHABI +#undef CHECK_SIGNING_SCHEME_FLAGS_INTEGRITY +#undef PACGA_TRAP +#undef RUN_IF_PAUTH_FEATURE_PRESENT +#undef STRING +#undef STRING_IMPL +#endif + #endif // _LIBUNWIND_TARGET_AARCH64 #if defined(_LIBUNWIND_TARGET_ARM) diff --git a/libunwind/src/UnwindLevel1.c b/libunwind/src/UnwindLevel1.c index 361bf6c3c1575..8c7a5f20fcc4f 100644 --- a/libunwind/src/UnwindLevel1.c +++ b/libunwind/src/UnwindLevel1.c @@ -616,57 +616,101 @@ _LIBUNWIND_EXPORT uintptr_t _Unwind_GetIP(struct _Unwind_Context *context) { __unw_get_reg(cursor, UNW_REG_IP, &result); #if defined(_LIBUNWIND_TARGET_AARCH64) && \ + defined(_LIBUNWIND_IS_NATIVE_ONLY) && \ !(defined(_LIBUNWIND_SUPPORT_SEH_UNWIND) && defined(_WIN32)) { - unw_word_t raSignState, raSignUseBKey; - __unw_get_reg(cursor, UNW_AARCH64_RA_SIGN_STATE, &raSignState); - __unw_get_reg(cursor, UNW_AARCH64_RA_SIGN_USE_B_KEY, &raSignUseBKey); - - bool isReturnAddressSigned = (raSignState & 1); - bool isReturnAddressSignedWithPC = (raSignState & 2); - - if (isReturnAddressSigned) { -#if !defined(_LIBUNWIND_IS_NATIVE_ONLY) - // We should never go here since non-null RA signed state is either set - // by architecture-specific __unw_getcontext or by stepWithDwarf which - // already contains a corresponding check and should have already - // emitted the UNW_ECROSSRASIGNING error. - _LIBUNWIND_ABORT("UNW_ECROSSRASIGNING"); -#else - unw_word_t sp; - __unw_get_reg(cursor, UNW_REG_SP, &sp); - - register uint64_t x17 __asm("x17") = result; - register uint64_t x16 __asm("x16") = sp; - - if (isReturnAddressSignedWithPC) { - unw_word_t raSignSecondModifier; - __unw_get_reg(cursor, UNW_AARCH64_RA_SIGN_SECOND_MODIFIER, - &raSignSecondModifier); - - register uint64_t x15 __asm("x15") = raSignSecondModifier; - - if (raSignUseBKey) { - __asm__("hint 0x27\n\t" // pacm - "hint 0xe " // autib1716 - : "+r"(x17) - : "r"(x16), "r"(x15)); - } else { - __asm__("hint 0x27\n\t" // pacm - "hint 0xc " // autia1716 - : "+r"(x17) - : "r"(x16), "r"(x15)); - } - } else { - if (raSignUseBKey) { - __asm__("hint 0xe" : "+r"(x17) : "r"(x16)); // autib1716 - } else { - __asm__("hint 0xc" : "+r"(x17) : "r"(x16)); // autia1716 - } - } - result = x17; + unw_word_t raSigningSchemeFlagsWithPAC; + __unw_get_reg(cursor, UNW_AARCH64_RA_SIGNING_SCHEME_FLAGS, + &raSigningSchemeFlagsWithPAC); + unw_word_t raSigningSchemeFlags = + raSigningSchemeFlagsWithPAC & 0x00000000ffffffffull; + unw_word_t raSigningSchemeFlagsPAC = + raSigningSchemeFlagsWithPAC & 0xffffffff00000000ull; + + unw_word_t sp; + __unw_get_reg(cursor, UNW_REG_SP, &sp); + + unw_word_t raSigningSchemeSecondModifier; + __unw_get_reg(cursor, UNW_AARCH64_RA_SIGNING_SCHEME_SECOND_MODIFIER, + &raSigningSchemeSecondModifier); + + register uint64_t x17 __asm("x17") = result; + register uint64_t x16 __asm("x16") = sp; + register uint64_t x15 __asm("x15") = raSigningSchemeSecondModifier; + register uint64_t x14 __asm("x14") = raSigningSchemeFlags; + register uint64_t x13 __asm("x13") = raSigningSchemeFlagsPAC; + + // TODO: make similar code from Registers.hpp reusable and adopt it here. + __asm__( + // Check if PAuth feature is available. See also + // RUN_IF_PAUTH_FEATURE_PRESENT in Registers.hpp + "mrs x12, ID_AA64ISAR1_EL1 \n\t" + "lsr x12, x12, #24 \n\t" + "ands x12, x12, #255 \n\t" + "cbnz x12, .Lcheck_pac \n\t" + "mrs x12, ID_AA64ISAR2_EL1 \n\t" + "lsr x12, x12, #8 \n\t" + "ands x12, x12, #15 \n\t" + "cbnz x12, .Lcheck_pac \n\t" + "b .Lno_pauth \n\t" + ".Lcheck_pac: \n\t" + + // See also CHECK_SIGNING_SCHEME_FLAGS_INTEGRITY in Registers.hpp. + "pacga x12, x14, x16 \n\t" + "cmp x12, x13 \n\t" + "b.eq .Lpacga_success\n\t" + "brk #0xc474 \n\t" + ".Lpacga_success: \n\t" + ".Lno_pauth: \n\t" + + // See also CHECK_SIGNING_SCHEME_FLAGS_INTEGRITY_FOR_PAUTHABI + // in Registers.hpp. +#if defined(_LIBUNWIND_TARGET_AARCH64_AUTHENTICATED_UNWINDING) + "cmp x14, 5 \n\t" + "b.eq .Lpauthabi_success\n\t" + "brk #0xc474 \n\t" + ".Lpauthabi_success: \n\t" #endif - } + + // See also SIGNING_SCHEME_FLAGS_SWITCH in Registers.hpp. + "cmp x14, #0 \n\t" + "b.ne .Lswitch_1 \n\t" + "b .Lswitch_end\n\t" + + ".Lswitch_1: \n\t" + "cmp x14, #1 \n\t" + "b.ne .Lswitch_3 \n\t" + "hint 0xc \n\t" // autia1716 + "b .Lswitch_end\n\t" + + ".Lswitch_3: \n\t" + "cmp x14, #3 \n\t" + "b.ne .Lswitch_5 \n\t" + "hint 0x27 \n\t" // pacm + "hint 0xc \n\t" // autia1716 + "b .Lswitch_end\n\t" + + ".Lswitch_5: \n\t" + "cmp x14, #5 \n\t" + "b.ne .Lswitch_7 \n\t" + "hint 0xe \n\t" // autib1716 + "b .Lswitch_end\n\t" + + ".Lswitch_7: \n\t" + "cmp x14, #7 \n\t" + "b.ne .Lswitch_unexpected\n\t" + "hint 0x27 \n\t" // pacm + "hint 0xe \n\t" // autib1716 + "b .Lswitch_end\n\t" + + ".Lswitch_unexpected:\n\t" + "brk #0xc474 \n\t" + + ".Lswitch_end:\n\t" + : "+r"(x17) + : "r"(x16), "r"(x15), "r"(x14), "r"(x13) + ); + result = x17; } #endif diff --git a/libunwind/src/UnwindRegistersRestore.S b/libunwind/src/UnwindRegistersRestore.S index a98b9cb85de3c..e7a212acc7c1f 100644 --- a/libunwind/src/UnwindRegistersRestore.S +++ b/libunwind/src/UnwindRegistersRestore.S @@ -704,64 +704,97 @@ DEFINE_LIBUNWIND_FUNCTION(__libunwind_Registers_arm64_jumpto) 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)). + // The lr value stored in __pc might be signed with IA/IB key, &__pc address + // as a modifier and an optional second 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_signing_scheme.__flags & 2'). ldr x14, [x0, #0x0F8] // x14 = __sp - ldr x15, [x0, #0x110] // x15 = __ra_sign.__second_modifier + ldr x15, [x0, #0x118] // x15 = __ra_signing_scheme.__second_modifier + ldr x1, [x0, #0x108] // x1 = __ra_signing_scheme.__flags + ldr x16, [x0, #0x110] // x16 = __ra_signing_scheme.__flags_pac + + // Check if PAuth feature is available. See also + // RUN_IF_PAUTH_FEATURE_PRESENT in Registers.hpp + mrs x17, ID_AA64ISAR1_EL1 + lsr x17, x17, #24 + ands x17, x17, #255 + cbnz x17, .Lcheck_pac + mrs x17, ID_AA64ISAR2_EL1 + lsr x17, x17, #8 + ands x17, x17, #15 + cbnz x17, .Lcheck_pac + b .Lno_pauth + .Lcheck_pac: + + // See also CHECK_SIGNING_SCHEME_FLAGS_INTEGRITY in Registers.hpp. + add x17, x0, #0x108 + pacga x17, x1, x17 + cmp x17, x16 + b.eq .Lpacga_success + brk #0xc474 + .Lpacga_success: + .Lno_pauth: + + // See also CHECK_SIGNING_SCHEME_FLAGS_INTEGRITY_FOR_PAUTHABI + // in Registers.hpp. +#if __has_feature(ptrauth_returns) + cmp x1, #5 + b.eq .Lpauthabi_success + brk #0xc474 + .Lpauthabi_success: +#endif + add x16, x0, #0x100 // x16 = &__pc ldr x17, [x0, #0x100] // x17 = __pc - ldr x1, [x0, #0x108] // x1 = __ra_sign.__state - and x1, x1, #1 - cbz x1, .Lresign_end - - // lr needs resign - ldr x1, [x0, #0x108] // x1 = __ra_sign.__state - and x1, x1, #2 - cbnz x1, .Lresign_with_pc - - // lr needs resign without pc as a second modifier - ldr x1, [x0, #0x118] // x1 = __ra_sign.__use_b_key - cbnz x1, .Lresign_with_b_key - - // lr needs resign with A key and without pc as a second modifier - hint 0xc // autia1716 - mov x16, x14 // x16 = __sp - hint 0x8 // pacia1716 - b .Lresign_end - -.Lresign_with_b_key: - // lr needs resign with B key and without pc as a second modifier - hint 0xe // autib1716 - mov x16, x14 // x16 = __sp - hint 0xa // pacib1716 - b .Lresign_end - -.Lresign_with_pc: - // lr needs resign with pc as a second modifier - ldr x1, [x0, #0x118] // x1 = __ra_sign.__use_b_key - cbnz x1, .Lresign_with_pc_b_key - - // lr needs resign with A key and pc as a second modifier - hint 0xc // autia1716 - mov x16, x14 // x16 = __sp - hint 0x27 // pacm - hint 0x8 // pacia1716 - b .Lresign_end - -.Lresign_with_pc_b_key: - // lr needs resign with B key and pc as a second modifier - hint 0xe // autib1716 - mov x16, x14 // x16 = __sp - hint 0x27 // pacm - hint 0xa // pacib1716 - b .Lresign_end + // See also SIGNING_SCHEME_FLAGS_SWITCH in Registers.hpp. + cmp x1, #0 + b.ne .Lswitch_1716_1 + b .Lswitch_1716_end + + .Lswitch_1716_1: + cmp x1, #1 + b.ne .Lswitch_1716_3 + hint 0xc // autia1716 + mov x16, x14 + hint 0x8 // pacia1716 + b .Lswitch_1716_end + + .Lswitch_1716_3: + cmp x1, #3 + b.ne .Lswitch_1716_5 + hint 0x27 // pacm + hint 0xc // autia1716 + mov x16, x14 + hint 0x27 // pacm + hint 0x8 // pacia1716 + b .Lswitch_1716_end + + .Lswitch_1716_5: + cmp x1, #5 + b.ne .Lswitch_1716_7 + hint 0xe // autib1716 + mov x16, x14 + hint 0xa // pacib1716 + b .Lswitch_1716_end + + .Lswitch_1716_7: + cmp x1, #7 + b.ne .Lswitch_1716_unexpected + hint 0x27 // pacm + hint 0xe // autib1716 + mov x16, x14 + hint 0x27 // pacm + hint 0xa // pacib1716 + b .Lswitch_1716_end + + .Lswitch_1716_unexpected: + brk #0xc474 + + .Lswitch_1716_end: -.Lresign_end: mov lr, x17 // assign final lr value ldp x14,x15, [x0, #0x070] // restore x14,x15 ldr x16, [x0, #0x110] // second modifier for auti{a|b}sp with PACM @@ -770,55 +803,56 @@ DEFINE_LIBUNWIND_FUNCTION(__libunwind_Registers_arm64_jumpto) // context struct, because it is allocated on the stack, and an exception // could clobber the de-allocated portion of the stack after sp has been // restored. - ldr x17, [x0, #0x0F8] // load sp into scratch - ldr x1, [x0, #0x108] // x1 = __ra_sign.__state - and x1, x1, #1 - cbz x1, .Lauth_end + ldr x17, [x0, #0x0F8] // load sp into scratch - // lr is signed - ldr x1, [x0, #0x108] // x1 = __ra_sign.__state - and x1, x1, #2 - cbnz x1, .Lauth_with_pc + // Integrity of the signing scheme stored in x1 was checked above and no + // manipulations on x1 were performed since then, so skip this check. - // lr is signed without pc as a second modifier - ldr x1, [x0, #0x118] // x1 = __ra_sign.__use_b_key - cbnz x1, .Lauth_with_b_key + // See also SIGNING_SCHEME_FLAGS_SWITCH in Registers.hpp. + cmp x1, #0 + b.ne .Lswitch_lrsp_1 + b .Lswitch_lrsp_end - // lr is signed with A key and without pc as a second modifier + .Lswitch_lrsp_1: + cmp x1, #1 + b.ne .Lswitch_lrsp_3 ldp x0, x1, [x0, #0x000] // restore x0,x1 mov sp,x17 // restore sp hint 0x1d // autiasp ret x30 -.Lauth_with_b_key: - // lr is signed with B key and without pc as a second modifier + .Lswitch_lrsp_3: + cmp x1, #3 + b.ne .Lswitch_lrsp_5 ldp x0, x1, [x0, #0x000] // restore x0,x1 mov sp,x17 // restore sp - hint 0x1f // autibsp + hint 0x27 // pacm + hint 0x1d // autiasp ret x30 -.Lauth_with_pc: - // lr is signed with pc as a second modifier - ldr x1, [x0, #0x118] // x1 = __ra_sign.__use_b_key - cbnz x1, .Lauth_with_pc_b_key - - // lr is signed with A key and pc as a second modifier + .Lswitch_lrsp_5: + cmp x1, #5 + b.ne .Lswitch_lrsp_7 ldp x0, x1, [x0, #0x000] // restore x0,x1 mov sp,x17 // restore sp - hint 0x27 // pacm - hint 0x1d // autiasp + hint 0x1f // autibsp ret x30 -.Lauth_with_pc_b_key: - // lr is signed with B key and pc as a second modifier + .Lswitch_lrsp_7: + cmp x1, #7 + b.ne .Lswitch_lrsp_unexpected + ldp x0, x1, [x0, #0x000] // restore x0,x1 mov sp,x17 // restore sp hint 0x27 // pacm hint 0x1f // autibsp ret x30 -.Lauth_end: + .Lswitch_lrsp_unexpected: + brk #0xc474 + +.Lswitch_lrsp_end: ldp x0, x1, [x0, #0x000] // restore x0,x1 mov sp,x17 // restore sp #if defined(__ARM_FEATURE_GCS_DEFAULT) diff --git a/libunwind/src/UnwindRegistersSave.S b/libunwind/src/UnwindRegistersSave.S index 252353b7861f7..4c4942982f159 100644 --- a/libunwind/src/UnwindRegistersSave.S +++ b/libunwind/src/UnwindRegistersSave.S @@ -783,6 +783,24 @@ LnoR2Fix: #define UNW_RA_SIGNED_WITH_B_KEY ((UNW_ARM_FEATURE_PAC_DEFAULT & 2) != 0) #define UNW_RA_SIGNED_WITH_PC ((UNW_ARM_FEATURE_PAC_DEFAULT & 8) != 0) +#if UNW_RA_SIGNED +# if UNW_RA_SIGNED_WITH_PC +# if UNW_RA_SIGNED_WITH_B_KEY +# define UNW_RA_SIGNING_SCHEME 7 +# else +# define UNW_RA_SIGNING_SCHEME 3 +# endif +# else +# if UNW_RA_SIGNED_WITH_B_KEY +# define UNW_RA_SIGNING_SCHEME 5 +# else +# define UNW_RA_SIGNING_SCHEME 1 +# endif +# endif +#else +# define UNW_RA_SIGNING_SCHEME 0 +#endif + // // extern int __unw_getcontext(unw_context_t* thread_state) // @@ -824,31 +842,38 @@ DEFINE_LIBUNWIND_FUNCTION(__unw_getcontext) str x1, [x0, #0x0F8] str x30, [x0, #0x100] // store return address as pc - // Fill subfields of __ra_sign field -#if UNW_RA_SIGNED -# if UNW_RA_SIGNED_WITH_PC - adrp x16, .L__unw_getcontext_start - add x16, x16, :lo12:.L__unw_getcontext_start - str x16, [x0, #0x110] // __second_modifier = x16 -# else - str xzr, [x0, #0x110] // __second_modifier = 0 -# endif - - mov x16, #1 - str x16, [x0, #0x108] // __state = 1 - -# if UNW_RA_SIGNED_WITH_B_KEY - str x16, [x0, #0x118] // __use_b_key = 1 + // Fill subfields of __ra_signing_scheme field + + mov x16, #UNW_RA_SIGNING_SCHEME + str x16, [x0, #0x108] // __flags + + // Check if PAuth feature is available. See also + // RUN_IF_PAUTH_FEATURE_PRESENT in Registers.hpp + mrs x17, ID_AA64ISAR1_EL1 + lsr x17, x17, #24 + ands x17, x17, #255 + cbnz x17, .Lcompute_pac + mrs x17, ID_AA64ISAR2_EL1 + lsr x17, x17, #8 + ands x17, x17, #15 + cbnz x17, .Lcompute_pac + str xzr, [x0, #0x110] // __flags_pac + b .Lno_pauth + .Lcompute_pac: + add x17, x0, #0x108 + pacga x17, x16, x17 + str x17, [x0, #0x110] // __flags_pac + .Lno_pauth: + +#if UNW_RA_SIGNED_WITH_PC + adrp x17, .L__unw_getcontext_start + add x17, x17, :lo12:.L__unw_getcontext_start # else - str xzr, [x0, #0x118] // __use_b_key = 0 + mov x17, xzr # endif + str x17, [x0, #0x118] // __second_modifier = x17 - ldr x16, [x0, #0x080] // restore x16 after clobber -#else - str xzr, [x0, #0x108] // __state = 0 - str xzr, [x0, #0x110] // __second_modifier = 0 - str xzr, [x0, #0x118] // __use_b_key = 0 -#endif + ldp x16,x17, [x0, #0x080] // restore x16 and x17 after clobber // skip cpsr #if defined(__ARM_FP) && __ARM_FP != 0 diff --git a/libunwind/src/libunwind.cpp b/libunwind/src/libunwind.cpp index 9fdeca0cdbe4b..400a41fc677f5 100644 --- a/libunwind/src/libunwind.cpp +++ b/libunwind/src/libunwind.cpp @@ -162,7 +162,7 @@ _LIBUNWIND_HIDDEN int __unw_set_reg(unw_cursor_t *cursor, unw_regnum_t regNum, _LIBUNWIND_ABORT("Bad unwind through arm64e"); } } -#elif defined(_LIBUNWIND_TARGET_AARCH64) && \ +#elif defined(_LIBUNWIND_TARGET_AARCH64) && defined(_LIBUNWIND_IS_NATIVE_ONLY) && \ !(defined(_LIBUNWIND_SUPPORT_SEH_UNWIND) && defined(_WIN32)) // We expect IP register value to be signed only for a full-fledged // PAuth ABI such as Apple's arm64e or Linux's pauthtest. Otherwise, @@ -170,9 +170,8 @@ _LIBUNWIND_HIDDEN int __unw_set_reg(unw_cursor_t *cursor, unw_regnum_t regNum, // so we need to update RA sign info and mark the pointer as unsigned. // This prevents attempts of unsigned pointer authentication in case // if previously a signed RA was stored in the IP register field. - co->setReg(UNW_AARCH64_RA_SIGN_STATE, 0); - co->setReg(UNW_AARCH64_RA_SIGN_SECOND_MODIFIER, 0); - co->setReg(UNW_AARCH64_RA_SIGN_USE_B_KEY, 0); + co->setReg(UNW_AARCH64_RA_SIGNING_SCHEME_SECOND_MODIFIER, 0); + co->setReg(UNW_AARCH64_RA_SIGNING_SCHEME_FLAGS, 0); #endif // If the original call expects stack adjustment, perform this now. _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
