diff --git a/libunwind/CMakeLists.txt b/libunwind/CMakeLists.txt index 3c8499fd33464..390b43b85f400 100644 --- a/libunwind/CMakeLists.txt +++ b/libunwind/CMakeLists.txt @@ -55,6 +55,7 @@ option(LIBUNWIND_USE_FRAME_HEADER_CACHE "Cache frame headers for unwinding. Requ option(LIBUNWIND_REMEMBER_HEAP_ALLOC "Use heap instead of the stack for .cfi_remember_state." OFF) option(LIBUNWIND_INSTALL_HEADERS "Install the libunwind headers." ON) option(LIBUNWIND_ENABLE_FRAME_APIS "Include libgcc-compatible frame apis." OFF) +option(LIBUNWIND_ENABLE_REGISTERSET_PROTECTION "Build libunwind with protection on the internal registers." ON) set(LIBUNWIND_LIBDIR_SUFFIX "${LLVM_LIBDIR_SUFFIX}" CACHE STRING "Define suffix of library directory name (32/64)") @@ -279,6 +280,51 @@ else() endif() endif() +if (LIBUNWIND_ENABLE_REGISTERSET_PROTECTION AND (NOT LIBUNWIND_ENABLE_CROSS_UNWINDING)) + # Pointer Authentication Intrinsics are currently only supported with + # the FEAT_PAuth, NOP space variant of the instructions are not + # supported, enable this feature if libunwind is compiled with branch-protection + # or pauth intrinsic are availabile. + + file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/ptrauth_check.cpp" " + #if !( defined(__ARM_FEATURE_PAUTH) && \ + defined(__has_include) && __has_include () && \ + defined(__has_feature) &&__has_feature(ptrauth_intrinsics)) + # error + #endif + int main(){} + ") + + try_compile(TARGET_SUPPORTS_PTRAUTH + "${CMAKE_CURRENT_BINARY_DIR}" + "${CMAKE_CURRENT_BINARY_DIR}/ptrauth_check.cpp" + COMPILE_DEFINITIONS "${LIBUNWIND_COMPILE_FLAGS}" -fptrauth-intrinsics) + + # Detect if the code is compiled with -mbranch-protection=pac*. + file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/pac_check.cpp" " + #ifndef __ARM_FEATURE_PAC_DEFAULT + # error + #endif + int main(){} + ") + + try_compile(TARGET_SUPPORTS_BRANCH_PROTECTION_PAC + "${CMAKE_CURRENT_BINARY_DIR}" + "${CMAKE_CURRENT_BINARY_DIR}/pac_check.cpp" + COMPILE_DEFINITIONS "${LIBUNWIND_COMPILE_FLAGS}") + + if (TARGET_SUPPORTS_BRANCH_PROTECTION_PAC) + add_compile_flags(-D_LIBUNWIND_AARCH64_PC_PROTECTION) + endif() + + if (TARGET_SUPPORTS_PTRAUTH) + add_compile_flags(-fptrauth-intrinsics) + add_compile_flags(-D_LIBUNWIND_PTRAUTH_AVAILABLE) + add_compile_flags(-D_LIBUNWIND_AARCH64_PC_PROTECTION) + endif() + +endif() + # Cross-unwinding if (NOT LIBUNWIND_ENABLE_CROSS_UNWINDING) add_compile_flags(-D_LIBUNWIND_IS_NATIVE_ONLY) diff --git a/libunwind/include/__libunwind_config.h b/libunwind/include/__libunwind_config.h index bb7fe4c83a3c1..0c4f8ec1b8a27 100644 --- a/libunwind/include/__libunwind_config.h +++ b/libunwind/include/__libunwind_config.h @@ -73,11 +73,19 @@ # define _LIBUNWIND_HIGHEST_DWARF_REGISTER _LIBUNWIND_HIGHEST_DWARF_REGISTER_PPC # elif defined(__aarch64__) # define _LIBUNWIND_TARGET_AARCH64 1 -# define _LIBUNWIND_CONTEXT_SIZE 66 +# if defined(_LIBUNWIND_AARCH64_PC_PROTECTION) +# define _LIBUNWIND_CONTEXT_SIZE 67 +# else +# define _LIBUNWIND_CONTEXT_SIZE 66 +# endif # if defined(__SEH__) # define _LIBUNWIND_CURSOR_SIZE 164 # else -# define _LIBUNWIND_CURSOR_SIZE 78 +# if defined(_LIBUNWIND_AARCH64_PC_PROTECTION) +# define _LIBUNWIND_CURSOR_SIZE 79 +# else +# define _LIBUNWIND_CURSOR_SIZE 78 +# endif # endif # define _LIBUNWIND_HIGHEST_DWARF_REGISTER _LIBUNWIND_HIGHEST_DWARF_REGISTER_ARM64 # elif defined(__arm__) diff --git a/libunwind/src/Registers.hpp b/libunwind/src/Registers.hpp index 2c3bfb7e8428a..a7ddf6a61e35e 100644 --- a/libunwind/src/Registers.hpp +++ b/libunwind/src/Registers.hpp @@ -19,6 +19,9 @@ #include "libunwind.h" #include "shadow_stack_unwind.h" +#if defined(_LIBUNWIND_PTRAUTH_AVAILABLE) +#include +#endif namespace libunwind { // For emulating 128-bit registers @@ -1823,9 +1826,147 @@ extern "C" void *__libunwind_shstk_get_jump_target() { #endif class _LIBUNWIND_HIDDEN Registers_arm64 { + struct GPRs; + +private: +#define PAC_DISCRIMINATOR ((uint16_t)0xFACEU) + +#if defined(_LIBUNWIND_AARCH64_PC_PROTECTION) + /// The program counter is effectively used as a return address when the + /// context is restored; therefore, it should be protected with PAC to prevent + /// arbitrary returns. The base address of the context is blended with a + /// discriminator using the A key for authentication and signing. Return + /// address authentication is still managed according to the unwind + /// information. In some cases, the LR contains significant bits in the space + /// reserved for PAC bits, so the value of the PC is stored in two halves. + /// The second half is signed using the first half as a discriminator to bind + /// the two halves together. +#if defined(_LIBUNWIND_PTRAUTH_AVAILABLE) + /// Use Pointer Authentication Intrinsics when available. +#define __libunwind_ptrauth_auth_data(__value, __key, __discriminator) \ + ptrauth_auth_data(__value, __key, __discriminator) +#define __libunwind_ptrauth_auth_and_resign(pointer, oldKey, oldDiscriminator, \ + newKey, newDiscriminator) \ + ptrauth_auth_and_resign(pointer, oldKey, oldDiscriminator, newKey, \ + newDiscriminator) +#define __libunwind_ptrauth_sign_unauthenticated(__value, __key, __data) \ + ptrauth_sign_unauthenticated(__value, __key, __data) +#define __libunwind_ptrauth_blend_discriminator(__ptr, __data) \ + ptrauth_blend_discriminator(__ptr, __data) +#else // !_LIBUNWIND_PTRAUTH_AVAILABLE + typedef enum { + ptrauth_key_asia = 0, + } ptrauth_key; + /// Using only the NOP space compatible instructions. FPAC might not be + /// available on the target so a manual check is added. + inline void *__libunwind_ptrauth_strip(void *__value, + ptrauth_key __key) const { + assert(__key == ptrauth_key_asia && "Only A key is supported"); + void *__return = 0; + asm("mov x30, %[__value] \r\n" + "hint 0x7 \r\n" // xpaclri + "mov %[__return], x30 \r\n" + : [__return] "+r"(__return) + : [__value] "r"(__value) + : "x30"); + return __return; + } + + inline void *__libunwind_ptrauth_auth_data(void *__value, ptrauth_key __key, + uint64_t __discriminator) const { + assert(__key == ptrauth_key_asia && "Only A key is supported"); + register void *x17 __asm("x17") = __value; + register uintptr_t x16 __asm("x16") = __discriminator; + asm("hint 0xc" // autia1716 + : "+r"(x17) + : "r"(x16) + :); + if (x17 != __libunwind_ptrauth_strip(__value, __key)) + _LIBUNWIND_ABORT("ptrauth authentication failure"); + return x17; + } + + inline void * + __libunwind_ptrauth_sign_unauthenticated(void *__value, ptrauth_key __key, + uint64_t __discriminator) const { + assert(__key == ptrauth_key_asia && "Only A key is supported"); + register void *x17 __asm("x17") = __value; + register uint64_t x16 __asm("x16") = __discriminator; + asm("hint 0x8" : "+r"(x17) : "r"(x16)); + return x17; + } + + inline void *__libunwind_ptrauth_auth_and_resign( + void *pointer, ptrauth_key oldKey, uint64_t oldDiscriminator, + ptrauth_key newKey, uint64_t newDiscriminator) const { + return __libunwind_ptrauth_sign_unauthenticated( + __libunwind_ptrauth_auth_data(pointer, oldKey, oldDiscriminator), + newKey, newDiscriminator); + } + inline uint64_t + __libunwind_ptrauth_blend_discriminator(const void *__ptr, + uint16_t __data) const { + return (reinterpret_cast(__ptr) & (~0 >> 16)) | + ((uint64_t)__data << 48); + } +#endif + // Authenticate the currently stored PC and return it's raw value. + inline uint64_t authPC(const struct GPRs *gprs, + uint64_t discriminator) const { + uint64_t upper = (uint64_t)__libunwind_ptrauth_auth_data( + (void *)gprs->__pc2, ptrauth_key_asia, gprs->__pc); + uint64_t lower = (uint64_t)__libunwind_ptrauth_auth_data( + (void *)gprs->__pc, ptrauth_key_asia, discriminator); + return (upper << 32) | lower; + } + + // Sign and store the new PC. + inline void updatePC(uint64_t value) __attribute__((always_inline)) { + _registers.__pc = (uint64_t)__libunwind_ptrauth_sign_unauthenticated( + (void *)(value & (((uint64_t)~0) >> 32)), ptrauth_key_asia, + getDiscriminator()); + _registers.__pc2 = (uint64_t)__libunwind_ptrauth_sign_unauthenticated( + (void *)(value >> 32), ptrauth_key_asia, _registers.__pc); + } + + // Update the signature on the current PC. + inline void resignPC(uint64_t oldDiscriminator) { + uint64_t old_signed_pc = _registers.__pc; + _registers.__pc = (uint64_t)__libunwind_ptrauth_auth_and_resign( + (void *)_registers.__pc, ptrauth_key_asia, oldDiscriminator, + ptrauth_key_asia, getDiscriminator()); + _registers.__pc2 = (uint64_t)__libunwind_ptrauth_auth_and_resign( + (void *)_registers.__pc2, ptrauth_key_asia, old_signed_pc, + ptrauth_key_asia, _registers.__pc); + } +#else //! defined(_LIBUNWIND_AARCH64_PC_PROTECTION)) + inline uint64_t + __libunwind_ptrauth_blend_discriminator(const void *__ptr, + uint16_t __data) const { + (void)__data; + return __ptr; + } + // Remote unwinding is not supported by this protection. + inline uint64_t authPC(const struct GPRs *gprs, + const uint64_t discriminator) const { + (void)discriminator; + return gprs->__pc; + } + inline void updatePC(const uint64_t value) { _registers.__pc = value; } + inline void resignPC(uint64_t oldDiscriminator) { (void)oldDiscriminator; } +#endif + + inline uint64_t getDiscriminator() const { + return __libunwind_ptrauth_blend_discriminator(this, PAC_DISCRIMINATOR); + } + public: Registers_arm64(); Registers_arm64(const void *registers); + Registers_arm64(const Registers_arm64 &other); + Registers_arm64(const Registers_arm64 &&other) = delete; + Registers_arm64 &operator=(const Registers_arm64 &other); + Registers_arm64 &operator=(Registers_arm64 &&other) = delete; bool validRegister(int num) const; uint64_t getRegister(int num) const; @@ -1845,8 +1986,14 @@ class _LIBUNWIND_HIDDEN Registers_arm64 { uint64_t getSP() const { return _registers.__sp; } void setSP(uint64_t value) { _registers.__sp = value; } - uint64_t getIP() const { return _registers.__pc; } - void setIP(uint64_t value) { _registers.__pc = value; } + uint64_t getIP() const { return authPC(&_registers, getDiscriminator()); } + void setIP(uint64_t value) { + // First authenticate the current value of the IP to ensure the context + // is still valid. This also ensure the setIP can't be used for signing + // arbitrary values. + authPC(&_registers, getDiscriminator()); + updatePC(value); + } uint64_t getFP() const { return _registers.__fp; } void setFP(uint64_t value) { _registers.__fp = value; } @@ -1858,12 +2005,15 @@ class _LIBUNWIND_HIDDEN Registers_arm64 { uint64_t __sp; // Stack pointer x31 uint64_t __pc; // Program counter uint64_t __ra_sign_state; // RA sign state register +#if defined(_LIBUNWIND_AARCH64_PC_PROTECTION) + uint64_t __pc2; // PC's signed upper part +#endif }; GPRs _registers; double _vectorHalfRegisters[32]; - // Currently only the lower double in 128-bit vectore registers - // is perserved during unwinding. We could define new register + // Currently only the lower double in 128-bit vector registers + // is preserved during unwinding. We could define new register // numbers (> 96) which mean whole vector registers, then this // struct would need to change to contain whole vector registers. }; @@ -1872,8 +2022,16 @@ inline Registers_arm64::Registers_arm64(const void *registers) { static_assert((check_fit::does_fit), "arm64 registers do not fit into unw_context_t"); memcpy(&_registers, registers, sizeof(_registers)); +#if defined(_LIBUNWIND_AARCH64_PC_PROTECTION) + static_assert(sizeof(GPRs) == 0x118, + "expected VFP registers to be at offset 280"); +#else static_assert(sizeof(GPRs) == 0x110, "expected VFP registers to be at offset 272"); +#endif + // getcontext signs the PC with the base address of the context. + resignPC( + __libunwind_ptrauth_blend_discriminator(registers, PAC_DISCRIMINATOR)); memcpy(_vectorHalfRegisters, static_cast(registers) + sizeof(GPRs), sizeof(_vectorHalfRegisters)); @@ -1882,6 +2040,25 @@ inline Registers_arm64::Registers_arm64(const void *registers) { inline Registers_arm64::Registers_arm64() { memset(&_registers, 0, sizeof(_registers)); memset(&_vectorHalfRegisters, 0, sizeof(_vectorHalfRegisters)); + // We don't know the value of the PC but let's sign it to indicate we have a + // valid register set. + updatePC(0); +} + +inline Registers_arm64::Registers_arm64(const Registers_arm64 &other) { + memcpy(&_registers, &other._registers, sizeof(_registers)); + memcpy(&_vectorHalfRegisters, &other._vectorHalfRegisters, + sizeof(_vectorHalfRegisters)); + resignPC(other.getDiscriminator()); +} + +inline Registers_arm64 & +Registers_arm64::operator=(const Registers_arm64 &other) { + memcpy(&_registers, &other._registers, sizeof(_registers)); + memcpy(&_vectorHalfRegisters, &other._vectorHalfRegisters, + sizeof(_vectorHalfRegisters)); + resignPC(other.getDiscriminator()); + return *this; } inline bool Registers_arm64::validRegister(int regNum) const { @@ -1902,7 +2079,7 @@ inline bool Registers_arm64::validRegister(int regNum) const { inline uint64_t Registers_arm64::getRegister(int regNum) const { if (regNum == UNW_REG_IP || regNum == UNW_AARCH64_PC) - return _registers.__pc; + return getIP(); if (regNum == UNW_REG_SP || regNum == UNW_AARCH64_SP) return _registers.__sp; if (regNum == UNW_AARCH64_RA_SIGN_STATE) @@ -1918,7 +2095,7 @@ inline uint64_t Registers_arm64::getRegister(int regNum) const { inline void Registers_arm64::setRegister(int regNum, uint64_t value) { if (regNum == UNW_REG_IP || regNum == UNW_AARCH64_PC) - _registers.__pc = value; + setIP(value); else if (regNum == UNW_REG_SP || regNum == UNW_AARCH64_SP) _registers.__sp = value; else if (regNum == UNW_AARCH64_RA_SIGN_STATE) diff --git a/libunwind/src/UnwindRegistersRestore.S b/libunwind/src/UnwindRegistersRestore.S index 1702d016c368b..fb6e6fdd7893c 100644 --- a/libunwind/src/UnwindRegistersRestore.S +++ b/libunwind/src/UnwindRegistersRestore.S @@ -657,25 +657,51 @@ DEFINE_LIBUNWIND_FUNCTION(__libunwind_Registers_arm64_jumpto) ldp x24,x25, [x0, #0x0C0] ldp x26,x27, [x0, #0x0D0] ldp x28,x29, [x0, #0x0E0] +#if defined(_LIBUNWIND_AARCH64_PC_PROTECTION) +#define __VOFFSET 0x118 + // See the description in Registers_arm64 + ldr x30, [x0, #0x100] // __pc + mov x17, x30 + mov x16, x0 // auth with the buffer's address + movk x16, #0xface, lsl #48 // blinded with a constant + hint 0xc // autia1716 + hint 0x7 // xpaclri + cmp x17, x30 // check if the auth failed + b.ne LauthError + mov x1, x17 + ldr x30, [x0, #0x110] // __pc2 + mov x17, x30 + ldr x16, [x0, #0x100] // Auth the second part with the signed first part + hint 0xc // autia1716 + hint 0x7 // xpaclri + cmp x17, x30 // check if the auth failed + b.ne LauthError + lsl x17, x17, 32 + orr x30, x17, x1 + mov x16, xzr // clearup registers + mov x17, xzr +#else +#define __VOFFSET 0x110 ldr x30, [x0, #0x100] // restore pc into lr +#endif #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, #(__VOFFSET + 0x0)] + ldp d2, d3, [x0, #(__VOFFSET + 0x10)] + ldp d4, d5, [x0, #(__VOFFSET + 0x20)] + ldp d6, d7, [x0, #(__VOFFSET + 0x30)] + ldp d8, d9, [x0, #(__VOFFSET + 0x40)] + ldp d10,d11, [x0, #(__VOFFSET + 0x50)] + ldp d12,d13, [x0, #(__VOFFSET + 0x60)] + ldp d14,d15, [x0, #(__VOFFSET + 0x70)] + ldp d16,d17, [x0, #(__VOFFSET + 0x80)] + ldp d18,d19, [x0, #(__VOFFSET + 0x90)] + ldp d20,d21, [x0, #(__VOFFSET + 0xA0)] + ldp d22,d23, [x0, #(__VOFFSET + 0xB0)] + ldp d24,d25, [x0, #(__VOFFSET + 0xC0)] + ldp d26,d27, [x0, #(__VOFFSET + 0xD0)] + ldp d28,d29, [x0, #(__VOFFSET + 0xE0)] + ldr d30, [x0, #(__VOFFSET + 0xF0)] + ldr d31, [x0, #(__VOFFSET + 0xF8)] #endif // 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 @@ -695,7 +721,11 @@ DEFINE_LIBUNWIND_FUNCTION(__libunwind_Registers_arm64_jumpto) Lnogcs: #endif ret x30 // jump to pc - +#if defined(_LIBUNWIND_AARCH64_PC_PROTECTION) +LauthError: + mov x30, xzr + ret x30 +#endif #elif defined(__arm__) && !defined(__APPLE__) #if !defined(__ARM_ARCH_ISA_ARM) diff --git a/libunwind/src/UnwindRegistersSave.S b/libunwind/src/UnwindRegistersSave.S index a489a8ba6df15..221dc145dbc03 100644 --- a/libunwind/src/UnwindRegistersSave.S +++ b/libunwind/src/UnwindRegistersSave.S @@ -744,26 +744,44 @@ DEFINE_LIBUNWIND_FUNCTION(__unw_getcontext) str x30, [x0, #0x0F0] mov x1,sp str x1, [x0, #0x0F8] +#if defined(_LIBUNWIND_AARCH64_PC_PROTECTION) +#define __VOFFSET 0x118 + // See the description in Registers_arm64 + mov w17, w30 // lower part of the PC + mov x16, x0 // sign with the address of the context + movk x16, #0xface, lsl #48 // blinded with a constant + hint 0x8 // pacia1716 + mov x16, x17 // sign the second part with the first part + str x17, [x0, #0x100] // store to __pc + mov x17, x30 // upper part of the PC + lsr x17, x17, 32 + hint 0x8 // pacia1716 + str x17, [x0, #0x110] // store to __pc2 + mov x17, xzr // let's not leak PC or the discriminator + mov x16, xzr +#else +#define __VOFFSET 0x110 str x30, [x0, #0x100] // store return address as pc +#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, #(__VOFFSET + 0x0)] + stp d2, d3, [x0, #(__VOFFSET + 0x10)] + stp d4, d5, [x0, #(__VOFFSET + 0x20)] + stp d6, d7, [x0, #(__VOFFSET + 0x30)] + stp d8, d9, [x0, #(__VOFFSET + 0x40)] + stp d10,d11, [x0, #(__VOFFSET + 0x50)] + stp d12,d13, [x0, #(__VOFFSET + 0x60)] + stp d14,d15, [x0, #(__VOFFSET + 0x70)] + stp d16,d17, [x0, #(__VOFFSET + 0x80)] + stp d18,d19, [x0, #(__VOFFSET + 0x90)] + stp d20,d21, [x0, #(__VOFFSET + 0xA0)] + stp d22,d23, [x0, #(__VOFFSET + 0xB0)] + stp d24,d25, [x0, #(__VOFFSET + 0xC0)] + stp d26,d27, [x0, #(__VOFFSET + 0xD0)] + stp d28,d29, [x0, #(__VOFFSET + 0xE0)] + str d30, [x0, #(__VOFFSET + 0xF0)] + str d31, [x0, #(__VOFFSET + 0xF8)] #endif mov x0, #0 // return UNW_ESUCCESS ret