Skip to content

Commit 114455c

Browse files
[libunwind][AArch64] Protect PC within libunwind's context.
Libunwind manages the regiser context including the program counter which is used effectively as return address. To increase the robustness of libunwind let's protect the stored address with PAC. Since there is no unwind info for this let's use the A key and the base address of the context/registers as modifier. __libunwind_Registers_arm64_jumpto can go anywhere where the given buffer 's PC points to. After this patch it needs a signed PC therefore the context is more harder to craft outside of libunwind. The register value is internal to libunwind and the change is not visible on the the APIs.
1 parent c9280ba commit 114455c

File tree

5 files changed

+298
-43
lines changed

5 files changed

+298
-43
lines changed

libunwind/CMakeLists.txt

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ option(LIBUNWIND_USE_FRAME_HEADER_CACHE "Cache frame headers for unwinding. Requ
5555
option(LIBUNWIND_REMEMBER_HEAP_ALLOC "Use heap instead of the stack for .cfi_remember_state." OFF)
5656
option(LIBUNWIND_INSTALL_HEADERS "Install the libunwind headers." ON)
5757
option(LIBUNWIND_ENABLE_FRAME_APIS "Include libgcc-compatible frame apis." OFF)
58+
option(LIBUNWIND_ENABLE_REGISTERSET_PROTECTION "Build libunwind with protection on the internal registers." ON)
5859

5960
set(LIBUNWIND_LIBDIR_SUFFIX "${LLVM_LIBDIR_SUFFIX}" CACHE STRING
6061
"Define suffix of library directory name (32/64)")
@@ -279,6 +280,51 @@ else()
279280
endif()
280281
endif()
281282

283+
if (LIBUNWIND_ENABLE_REGISTERSET_PROTECTION AND (NOT LIBUNWIND_ENABLE_CROSS_UNWINDING))
284+
# Pointer Authentication Intrinsics are currently only supported with
285+
# the FEAT_PAuth, NOP space variant of the instructions are not
286+
# supported, enable this feature if libunwind is compiled with branch-protection
287+
# or pauth intrinsic are availabile.
288+
289+
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/ptrauth_check.cpp" "
290+
#if !( defined(__ARM_FEATURE_PAUTH) && \
291+
defined(__has_include) && __has_include (<ptrauth.h>) && \
292+
defined(__has_feature) &&__has_feature(ptrauth_intrinsics))
293+
# error
294+
#endif
295+
int main(){}
296+
")
297+
298+
try_compile(TARGET_SUPPORTS_PTRAUTH
299+
"${CMAKE_CURRENT_BINARY_DIR}"
300+
"${CMAKE_CURRENT_BINARY_DIR}/ptrauth_check.cpp"
301+
COMPILE_DEFINITIONS "${LIBUNWIND_COMPILE_FLAGS}" -fptrauth-intrinsics)
302+
303+
# Detect if the code is compiled with -mbranch-protection=pac*.
304+
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/pac_check.cpp" "
305+
#ifndef __ARM_FEATURE_PAC_DEFAULT
306+
# error
307+
#endif
308+
int main(){}
309+
")
310+
311+
try_compile(TARGET_SUPPORTS_BRANCH_PROTECTION_PAC
312+
"${CMAKE_CURRENT_BINARY_DIR}"
313+
"${CMAKE_CURRENT_BINARY_DIR}/pac_check.cpp"
314+
COMPILE_DEFINITIONS "${LIBUNWIND_COMPILE_FLAGS}")
315+
316+
if (TARGET_SUPPORTS_BRANCH_PROTECTION_PAC)
317+
add_compile_flags(-D_LIBUNWIND_AARCH64_PC_PROTECTION)
318+
endif()
319+
320+
if (TARGET_SUPPORTS_PTRAUTH)
321+
add_compile_flags(-fptrauth-intrinsics)
322+
add_compile_flags(-D_LIBUNWIND_PTRAUTH_AVAILABLE)
323+
add_compile_flags(-D_LIBUNWIND_AARCH64_PC_PROTECTION)
324+
endif()
325+
326+
endif()
327+
282328
# Cross-unwinding
283329
if (NOT LIBUNWIND_ENABLE_CROSS_UNWINDING)
284330
add_compile_flags(-D_LIBUNWIND_IS_NATIVE_ONLY)

libunwind/include/__libunwind_config.h

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,11 +73,19 @@
7373
# define _LIBUNWIND_HIGHEST_DWARF_REGISTER _LIBUNWIND_HIGHEST_DWARF_REGISTER_PPC
7474
# elif defined(__aarch64__)
7575
# define _LIBUNWIND_TARGET_AARCH64 1
76-
# define _LIBUNWIND_CONTEXT_SIZE 66
76+
# if defined(_LIBUNWIND_AARCH64_PC_PROTECTION)
77+
# define _LIBUNWIND_CONTEXT_SIZE 67
78+
# else
79+
# define _LIBUNWIND_CONTEXT_SIZE 66
80+
# endif
7781
# if defined(__SEH__)
7882
# define _LIBUNWIND_CURSOR_SIZE 164
7983
# else
80-
# define _LIBUNWIND_CURSOR_SIZE 78
84+
# if defined(_LIBUNWIND_AARCH64_PC_PROTECTION)
85+
# define _LIBUNWIND_CURSOR_SIZE 79
86+
# else
87+
# define _LIBUNWIND_CURSOR_SIZE 78
88+
# endif
8189
# endif
8290
# define _LIBUNWIND_HIGHEST_DWARF_REGISTER _LIBUNWIND_HIGHEST_DWARF_REGISTER_ARM64
8391
# elif defined(__arm__)

libunwind/src/Registers.hpp

Lines changed: 162 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@
1919
#include "libunwind.h"
2020
#include "shadow_stack_unwind.h"
2121

22+
#if defined(_LIBUNWIND_PTRAUTH_AVAILABLE)
23+
#include <ptrauth.h>
24+
#endif
2225
namespace libunwind {
2326

2427
// For emulating 128-bit registers
@@ -1823,9 +1826,127 @@ extern "C" void *__libunwind_shstk_get_jump_target() {
18231826
#endif
18241827

18251828
class _LIBUNWIND_HIDDEN Registers_arm64 {
1829+
struct GPRs;
1830+
1831+
private:
1832+
/// The program counter is used effectively as a return address
1833+
/// when the context is restored therefore protect it with PAC.
1834+
/// The base address of the context is used with the A key for
1835+
/// authentication and signing. Return address authentication is
1836+
/// still managed according to the unwind info. In some cases
1837+
/// the LR contains significant bits in the space for the PAC bits the
1838+
/// value of the PC is stored in 2 halfs and each signed.
1839+
inline uint64_t getDiscriminator() const {
1840+
return reinterpret_cast<uint64_t>(this);
1841+
}
1842+
#if defined(_LIBUNWIND_AARCH64_PC_PROTECTION)
1843+
#if defined(_LIBUNWIND_PTRAUTH_AVAILABLE)
1844+
/// Use Pointer Authentication Intrinsics when available.
1845+
#define __libunwind_ptrauth_auth_data(__value, __key, __discriminator) \
1846+
ptrauth_auth_data(__value, __key, __discriminator)
1847+
#define __libunwind_ptrauth_auth_and_resign(pointer, oldKey, oldDiscriminator, \
1848+
newKey, newDiscriminator) \
1849+
ptrauth_auth_and_resign(pointer, oldKey, oldDiscriminator, newKey, \
1850+
newDiscriminator)
1851+
#define __libunwind_ptrauth_sign_unauthenticated(__value, __key, __data) \
1852+
ptrauth_sign_unauthenticated(__value, __key, __data)
1853+
#else // !_LIBUNWIND_PTRAUTH_AVAILABLE
1854+
typedef enum {
1855+
ptrauth_key_asia = 0,
1856+
} ptrauth_key;
1857+
/// Using only the NOP space compatible instructions. FPAC might not be
1858+
/// available on the target so a manual check is added.
1859+
inline void *__libunwind_ptrauth_strip(void *__value,
1860+
ptrauth_key __key) const {
1861+
assert(__key == ptrauth_key_asia && "Only A key is supported");
1862+
void *__return = 0;
1863+
asm("mov x30, %[__value] \r\n"
1864+
"hint 0x7 \r\n" // xpaclri
1865+
"mov %[__return], x30 \r\n"
1866+
: [__return] "+r"(__return)
1867+
: [__value] "r"(__value)
1868+
: "x30");
1869+
return __return;
1870+
}
1871+
1872+
inline void *__libunwind_ptrauth_auth_data(void *__value, ptrauth_key __key,
1873+
uint64_t __discriminator) const {
1874+
assert(__key == ptrauth_key_asia && "Only A key is supported");
1875+
register void *x17 __asm("x17") = __value;
1876+
register uintptr_t x16 __asm("x16") = __discriminator;
1877+
asm("hint 0xc" // autia1716
1878+
: "+r"(x17)
1879+
: "r"(x16)
1880+
:);
1881+
if (x17 != __libunwind_ptrauth_strip(__value, __key))
1882+
_LIBUNWIND_ABORT("ptrauth authentication failure");
1883+
return x17;
1884+
}
1885+
1886+
inline void *
1887+
__libunwind_ptrauth_sign_unauthenticated(void *__value, ptrauth_key __key,
1888+
uint64_t __discriminator) const {
1889+
assert(__key == ptrauth_key_asia && "Only A key is supported");
1890+
register void *x17 __asm("x17") = __value;
1891+
register uint64_t x16 __asm("x16") = __discriminator;
1892+
asm("hint 0x8" : "+r"(x17) : "r"(x16));
1893+
return x17;
1894+
}
1895+
1896+
inline void *__libunwind_ptrauth_auth_and_resign(
1897+
void *pointer, ptrauth_key oldKey, uint64_t oldDiscriminator,
1898+
ptrauth_key newKey, uint64_t newDiscriminator) const {
1899+
return __libunwind_ptrauth_sign_unauthenticated(
1900+
__libunwind_ptrauth_auth_data(pointer, oldKey, oldDiscriminator),
1901+
newKey, newDiscriminator);
1902+
}
1903+
#endif
1904+
// Authenticate the currently stored PC and return it's raw value.
1905+
inline uint64_t authPC(const struct GPRs *gprs,
1906+
uint64_t discriminator) const {
1907+
uint64_t lower = (uint64_t)__libunwind_ptrauth_auth_data(
1908+
(void *)gprs->__pc, ptrauth_key_asia, discriminator);
1909+
uint64_t upper = (uint64_t)__libunwind_ptrauth_auth_data(
1910+
(void *)gprs->__pc2, ptrauth_key_asia, discriminator);
1911+
return (upper << 32) | lower;
1912+
}
1913+
1914+
// Sign and store the new PC.
1915+
inline void updatePC(uint64_t value) {
1916+
_registers.__pc = (uint64_t)__libunwind_ptrauth_sign_unauthenticated(
1917+
(void *)(value & (((uint64_t)~0) >> 32)), ptrauth_key_asia,
1918+
getDiscriminator());
1919+
_registers.__pc2 = (uint64_t)__libunwind_ptrauth_sign_unauthenticated(
1920+
(void *)(value >> 32), ptrauth_key_asia, getDiscriminator());
1921+
}
1922+
1923+
// Update the signature on the current PC.
1924+
inline void resignPC(uint64_t oldDiscriminator) {
1925+
_registers.__pc = (uint64_t)__libunwind_ptrauth_auth_and_resign(
1926+
(void *)_registers.__pc, ptrauth_key_asia, oldDiscriminator,
1927+
ptrauth_key_asia, getDiscriminator());
1928+
_registers.__pc2 = (uint64_t)__libunwind_ptrauth_auth_and_resign(
1929+
(void *)_registers.__pc2, ptrauth_key_asia, oldDiscriminator,
1930+
ptrauth_key_asia, getDiscriminator());
1931+
}
1932+
#else //! defined(_LIBUNWIND_AARCH64_PC_PROTECTION))
1933+
// Remote unwinding is not supported by this protection.
1934+
inline uint64_t authPC(const struct GPRs *gprs,
1935+
const uint64_t discriminator) const {
1936+
(void)discriminator;
1937+
return gprs->__pc;
1938+
}
1939+
inline void updatePC(const uint64_t value) { _registers.__pc = value; }
1940+
inline void resignPC(uint64_t oldDiscriminator) { (void)oldDiscriminator; }
1941+
#endif
1942+
18261943
public:
18271944
Registers_arm64();
18281945
Registers_arm64(const void *registers);
1946+
Registers_arm64(const Registers_arm64 &other);
1947+
Registers_arm64(const Registers_arm64 &&other) = delete;
1948+
Registers_arm64 &operator=(const Registers_arm64 &other);
1949+
Registers_arm64 &operator=(Registers_arm64 &&other) = delete;
18291950

18301951
bool validRegister(int num) const;
18311952
uint64_t getRegister(int num) const;
@@ -1845,8 +1966,14 @@ class _LIBUNWIND_HIDDEN Registers_arm64 {
18451966

18461967
uint64_t getSP() const { return _registers.__sp; }
18471968
void setSP(uint64_t value) { _registers.__sp = value; }
1848-
uint64_t getIP() const { return _registers.__pc; }
1849-
void setIP(uint64_t value) { _registers.__pc = value; }
1969+
uint64_t getIP() const { return authPC(&_registers, getDiscriminator()); }
1970+
void setIP(uint64_t value) {
1971+
// First authenticate the current value of the IP to ensure the context
1972+
// is still valid. This also ensure the setIP can't be used for signing
1973+
// arbitrary values.
1974+
authPC(&_registers, getDiscriminator());
1975+
updatePC(value);
1976+
}
18501977
uint64_t getFP() const { return _registers.__fp; }
18511978
void setFP(uint64_t value) { _registers.__fp = value; }
18521979

@@ -1858,12 +1985,15 @@ class _LIBUNWIND_HIDDEN Registers_arm64 {
18581985
uint64_t __sp; // Stack pointer x31
18591986
uint64_t __pc; // Program counter
18601987
uint64_t __ra_sign_state; // RA sign state register
1988+
#if defined(_LIBUNWIND_AARCH64_PC_PROTECTION)
1989+
uint64_t __pc2; // PC's signed upper part
1990+
#endif
18611991
};
18621992

18631993
GPRs _registers;
18641994
double _vectorHalfRegisters[32];
1865-
// Currently only the lower double in 128-bit vectore registers
1866-
// is perserved during unwinding. We could define new register
1995+
// Currently only the lower double in 128-bit vector registers
1996+
// is preserved during unwinding. We could define new register
18671997
// numbers (> 96) which mean whole vector registers, then this
18681998
// struct would need to change to contain whole vector registers.
18691999
};
@@ -1872,8 +2002,15 @@ inline Registers_arm64::Registers_arm64(const void *registers) {
18722002
static_assert((check_fit<Registers_arm64, unw_context_t>::does_fit),
18732003
"arm64 registers do not fit into unw_context_t");
18742004
memcpy(&_registers, registers, sizeof(_registers));
2005+
#if defined(_LIBUNWIND_AARCH64_PC_PROTECTION)
2006+
static_assert(sizeof(GPRs) == 0x118,
2007+
"expected VFP registers to be at offset 280");
2008+
#else
18752009
static_assert(sizeof(GPRs) == 0x110,
18762010
"expected VFP registers to be at offset 272");
2011+
#endif
2012+
// getcontext signs the PC with the base address of the context.
2013+
resignPC(reinterpret_cast<uint64_t>(registers));
18772014
memcpy(_vectorHalfRegisters,
18782015
static_cast<const uint8_t *>(registers) + sizeof(GPRs),
18792016
sizeof(_vectorHalfRegisters));
@@ -1882,6 +2019,25 @@ inline Registers_arm64::Registers_arm64(const void *registers) {
18822019
inline Registers_arm64::Registers_arm64() {
18832020
memset(&_registers, 0, sizeof(_registers));
18842021
memset(&_vectorHalfRegisters, 0, sizeof(_vectorHalfRegisters));
2022+
// We don't know the value of the PC but let's sign it to indicate we have a
2023+
// valid register set.
2024+
updatePC(0);
2025+
}
2026+
2027+
inline Registers_arm64::Registers_arm64(const Registers_arm64 &other) {
2028+
memcpy(&_registers, &other._registers, sizeof(_registers));
2029+
memcpy(&_vectorHalfRegisters, &other._vectorHalfRegisters,
2030+
sizeof(_vectorHalfRegisters));
2031+
resignPC(other.getDiscriminator());
2032+
}
2033+
2034+
inline Registers_arm64 &
2035+
Registers_arm64::operator=(const Registers_arm64 &other) {
2036+
memcpy(&_registers, &other._registers, sizeof(_registers));
2037+
memcpy(&_vectorHalfRegisters, &other._vectorHalfRegisters,
2038+
sizeof(_vectorHalfRegisters));
2039+
resignPC(other.getDiscriminator());
2040+
return *this;
18852041
}
18862042

18872043
inline bool Registers_arm64::validRegister(int regNum) const {
@@ -1902,7 +2058,7 @@ inline bool Registers_arm64::validRegister(int regNum) const {
19022058

19032059
inline uint64_t Registers_arm64::getRegister(int regNum) const {
19042060
if (regNum == UNW_REG_IP || regNum == UNW_AARCH64_PC)
1905-
return _registers.__pc;
2061+
return getIP();
19062062
if (regNum == UNW_REG_SP || regNum == UNW_AARCH64_SP)
19072063
return _registers.__sp;
19082064
if (regNum == UNW_AARCH64_RA_SIGN_STATE)
@@ -1918,7 +2074,7 @@ inline uint64_t Registers_arm64::getRegister(int regNum) const {
19182074

19192075
inline void Registers_arm64::setRegister(int regNum, uint64_t value) {
19202076
if (regNum == UNW_REG_IP || regNum == UNW_AARCH64_PC)
1921-
_registers.__pc = value;
2077+
setIP(value);
19222078
else if (regNum == UNW_REG_SP || regNum == UNW_AARCH64_SP)
19232079
_registers.__sp = value;
19242080
else if (regNum == UNW_AARCH64_RA_SIGN_STATE)

libunwind/src/UnwindRegistersRestore.S

Lines changed: 46 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -657,25 +657,49 @@ DEFINE_LIBUNWIND_FUNCTION(__libunwind_Registers_arm64_jumpto)
657657
ldp x24,x25, [x0, #0x0C0]
658658
ldp x26,x27, [x0, #0x0D0]
659659
ldp x28,x29, [x0, #0x0E0]
660+
#if defined(_LIBUNWIND_AARCH64_PC_PROTECTION)
661+
#define __VOFFSET 0x118
662+
// Authenticate return address with the address of the context.
663+
ldr x30, [x0, #0x100] // __pc
664+
mov x17, x30
665+
mov x16, x0
666+
hint 0xc // autia1716
667+
xpaclri
668+
cmp x17, x30
669+
b.ne LauthError
670+
mov x1, x17
671+
ldr x30, [x0, #0x110] // __pc2
672+
mov x17, x30
673+
hint 0xc // autia1716
674+
xpaclri
675+
cmp x17, x30
676+
b.ne LauthError
677+
lsl x17, x17, 32
678+
orr x30, x17, x1
679+
mov x16, xzr
680+
mov x17, xzr
681+
#else
682+
#define __VOFFSET 0x110
660683
ldr x30, [x0, #0x100] // restore pc into lr
684+
#endif
661685
#if defined(__ARM_FP) && __ARM_FP != 0
662-
ldp d0, d1, [x0, #0x110]
663-
ldp d2, d3, [x0, #0x120]
664-
ldp d4, d5, [x0, #0x130]
665-
ldp d6, d7, [x0, #0x140]
666-
ldp d8, d9, [x0, #0x150]
667-
ldp d10,d11, [x0, #0x160]
668-
ldp d12,d13, [x0, #0x170]
669-
ldp d14,d15, [x0, #0x180]
670-
ldp d16,d17, [x0, #0x190]
671-
ldp d18,d19, [x0, #0x1A0]
672-
ldp d20,d21, [x0, #0x1B0]
673-
ldp d22,d23, [x0, #0x1C0]
674-
ldp d24,d25, [x0, #0x1D0]
675-
ldp d26,d27, [x0, #0x1E0]
676-
ldp d28,d29, [x0, #0x1F0]
677-
ldr d30, [x0, #0x200]
678-
ldr d31, [x0, #0x208]
686+
ldp d0, d1, [x0, #(__VOFFSET + 0x0)]
687+
ldp d2, d3, [x0, #(__VOFFSET + 0x10)]
688+
ldp d4, d5, [x0, #(__VOFFSET + 0x20)]
689+
ldp d6, d7, [x0, #(__VOFFSET + 0x30)]
690+
ldp d8, d9, [x0, #(__VOFFSET + 0x40)]
691+
ldp d10,d11, [x0, #(__VOFFSET + 0x50)]
692+
ldp d12,d13, [x0, #(__VOFFSET + 0x60)]
693+
ldp d14,d15, [x0, #(__VOFFSET + 0x70)]
694+
ldp d16,d17, [x0, #(__VOFFSET + 0x80)]
695+
ldp d18,d19, [x0, #(__VOFFSET + 0x90)]
696+
ldp d20,d21, [x0, #(__VOFFSET + 0xA0)]
697+
ldp d22,d23, [x0, #(__VOFFSET + 0xB0)]
698+
ldp d24,d25, [x0, #(__VOFFSET + 0xC0)]
699+
ldp d26,d27, [x0, #(__VOFFSET + 0xD0)]
700+
ldp d28,d29, [x0, #(__VOFFSET + 0xE0)]
701+
ldr d30, [x0, #(__VOFFSET + 0xF0)]
702+
ldr d31, [x0, #(__VOFFSET + 0xF8)]
679703
#endif
680704
// Finally, restore sp. This must be done after the last read from the
681705
// context struct, because it is allocated on the stack, and an exception
@@ -695,7 +719,11 @@ DEFINE_LIBUNWIND_FUNCTION(__libunwind_Registers_arm64_jumpto)
695719
Lnogcs:
696720
#endif
697721
ret x30 // jump to pc
698-
722+
#if defined(_LIBUNWIND_AARCH64_PC_PROTECTION)
723+
LauthError:
724+
mov x30, xzr
725+
ret x30
726+
#endif
699727
#elif defined(__arm__) && !defined(__APPLE__)
700728

701729
#if !defined(__ARM_ARCH_ISA_ARM)

0 commit comments

Comments
 (0)