Skip to content

Commit 3dd2f4d

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 cract outside of libunwind. The register value is internal to libunwind and the change is not visible on the the APIs. w
1 parent 7033408 commit 3dd2f4d

File tree

3 files changed

+99
-8
lines changed

3 files changed

+99
-8
lines changed

libunwind/src/Registers.hpp

Lines changed: 85 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1823,9 +1823,61 @@ extern "C" void *__libunwind_cet_get_jump_target() {
18231823
#endif
18241824

18251825
class _LIBUNWIND_HIDDEN Registers_arm64 {
1826+
protected:
1827+
/// The program counter is used effectively as a return address
1828+
/// when the context is restored therefore protect it with PAC.
1829+
/// The base address of the context is used with the A key for
1830+
/// authentication and signing. Return address authentication is
1831+
/// still managed according to the unwind info.
1832+
inline uint64_t getAuthSalt() const {
1833+
return reinterpret_cast<uint64_t>(this);
1834+
}
1835+
#if defined(_LIBUNWIND_IS_NATIVE_ONLY)
1836+
// Authenticate the given pointer and return with the raw value
1837+
// if the authentication is succeeded.
1838+
inline uint64_t auth(uint64_t ptr, uint64_t salt) const {
1839+
register uint64_t x17 __asm("x17") = ptr;
1840+
register uint64_t x16 __asm("x16") = salt;
1841+
asm("hint 0xc" // autia1716
1842+
: "+r"(x17)
1843+
: "r"(x16)
1844+
:);
1845+
1846+
uint64_t checkValue = ptr;
1847+
// Support for machines without FPAC.
1848+
// Strip the upper bits with `XPACLRI` and compare with the
1849+
// authenticated value.
1850+
asm("mov x30, %[checkValue] \r\n"
1851+
"hint 0x7 \r\n"
1852+
"mov %[checkValue], x30 \r\n"
1853+
: [checkValue] "+r"(checkValue)
1854+
:
1855+
: "x30");
1856+
if (x17 != checkValue)
1857+
_LIBUNWIND_ABORT("IP PAC authentication failure");
1858+
return x17;
1859+
}
1860+
1861+
// Sign the PC with the A-KEY and the current salt.
1862+
inline void updatePC(uint64_t value) {
1863+
register uint64_t x17 __asm("x17") = value;
1864+
register uint64_t x16 __asm("x16") = getAuthSalt();
1865+
asm("hint 0x8" : "+r"(x17) : "r"(x16)); // pacia1716
1866+
_registers.__pc = x17;
1867+
}
1868+
#else //! defined(_LIBUNWIND_IS_NATIVE_ONLY)
1869+
// Remote unwinding is not supported by this protection.
1870+
inline uint64_t auth(uint64_t ptr, uint64_t salt) const { return ptr; }
1871+
inline void updatePC(uint64_t value) { _registers.__pc = value; }
1872+
#endif
1873+
18261874
public:
18271875
Registers_arm64();
18281876
Registers_arm64(const void *registers);
1877+
Registers_arm64(const Registers_arm64 &other);
1878+
Registers_arm64(const Registers_arm64 &&other) = delete;
1879+
Registers_arm64 &operator=(const Registers_arm64 &other);
1880+
Registers_arm64 &operator=(Registers_arm64 &&other) = delete;
18291881

18301882
bool validRegister(int num) const;
18311883
uint64_t getRegister(int num) const;
@@ -1845,8 +1897,14 @@ class _LIBUNWIND_HIDDEN Registers_arm64 {
18451897

18461898
uint64_t getSP() const { return _registers.__sp; }
18471899
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; }
1900+
uint64_t getIP() const { return auth(_registers.__pc, getAuthSalt()); }
1901+
void setIP(uint64_t value) {
1902+
// First authenticate the current value of the IP to ensure the context
1903+
// is still valid. This also ensure the setIP can't be used for signing
1904+
// arbitrary values.
1905+
auth(_registers.__pc, getAuthSalt());
1906+
updatePC(value);
1907+
}
18501908
uint64_t getFP() const { return _registers.__fp; }
18511909
void setFP(uint64_t value) { _registers.__fp = value; }
18521910

@@ -1862,8 +1920,8 @@ class _LIBUNWIND_HIDDEN Registers_arm64 {
18621920

18631921
GPRs _registers;
18641922
double _vectorHalfRegisters[32];
1865-
// Currently only the lower double in 128-bit vectore registers
1866-
// is perserved during unwinding. We could define new register
1923+
// Currently only the lower double in 128-bit vector registers
1924+
// is preserved during unwinding. We could define new register
18671925
// numbers (> 96) which mean whole vector registers, then this
18681926
// struct would need to change to contain whole vector registers.
18691927
};
@@ -1874,6 +1932,8 @@ inline Registers_arm64::Registers_arm64(const void *registers) {
18741932
memcpy(&_registers, registers, sizeof(_registers));
18751933
static_assert(sizeof(GPRs) == 0x110,
18761934
"expected VFP registers to be at offset 272");
1935+
// getcontext signs the PC with the base address of the context.
1936+
updatePC(auth(_registers.__pc, reinterpret_cast<uint64_t>(registers)));
18771937
memcpy(_vectorHalfRegisters,
18781938
static_cast<const uint8_t *>(registers) + sizeof(GPRs),
18791939
sizeof(_vectorHalfRegisters));
@@ -1882,6 +1942,25 @@ inline Registers_arm64::Registers_arm64(const void *registers) {
18821942
inline Registers_arm64::Registers_arm64() {
18831943
memset(&_registers, 0, sizeof(_registers));
18841944
memset(&_vectorHalfRegisters, 0, sizeof(_vectorHalfRegisters));
1945+
// We don't know the PC but let's sign to indicate we have a valid
1946+
// register set.
1947+
updatePC(0);
1948+
}
1949+
1950+
inline Registers_arm64::Registers_arm64(const Registers_arm64 &other) {
1951+
memcpy(&_registers, &other._registers, sizeof(_registers));
1952+
memcpy(&_vectorHalfRegisters, &other._vectorHalfRegisters,
1953+
sizeof(_vectorHalfRegisters));
1954+
updatePC(other.getIP());
1955+
}
1956+
1957+
inline Registers_arm64 &
1958+
Registers_arm64::operator=(const Registers_arm64 &other) {
1959+
memcpy(&_registers, &other._registers, sizeof(_registers));
1960+
memcpy(&_vectorHalfRegisters, &other._vectorHalfRegisters,
1961+
sizeof(_vectorHalfRegisters));
1962+
updatePC(other.getIP());
1963+
return *this;
18851964
}
18861965

18871966
inline bool Registers_arm64::validRegister(int regNum) const {
@@ -1902,7 +1981,7 @@ inline bool Registers_arm64::validRegister(int regNum) const {
19021981

19031982
inline uint64_t Registers_arm64::getRegister(int regNum) const {
19041983
if (regNum == UNW_REG_IP || regNum == UNW_AARCH64_PC)
1905-
return _registers.__pc;
1984+
return getIP();
19061985
if (regNum == UNW_REG_SP || regNum == UNW_AARCH64_SP)
19071986
return _registers.__sp;
19081987
if (regNum == UNW_AARCH64_RA_SIGN_STATE)
@@ -1918,7 +1997,7 @@ inline uint64_t Registers_arm64::getRegister(int regNum) const {
19181997

19191998
inline void Registers_arm64::setRegister(int regNum, uint64_t value) {
19201999
if (regNum == UNW_REG_IP || regNum == UNW_AARCH64_PC)
1921-
_registers.__pc = value;
2000+
setIP(value);
19222001
else if (regNum == UNW_REG_SP || regNum == UNW_AARCH64_SP)
19232002
_registers.__sp = value;
19242003
else if (regNum == UNW_AARCH64_RA_SIGN_STATE)

libunwind/src/UnwindRegistersRestore.S

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -676,7 +676,13 @@ DEFINE_LIBUNWIND_FUNCTION(__libunwind_Registers_arm64_jumpto)
676676
ldp d28,d29, [x0, #0x1F0]
677677
ldr d30, [x0, #0x200]
678678
ldr d31, [x0, #0x208]
679-
679+
// Authenticate return address with the address of the context.
680+
mov x16, x0
681+
mov x17, x30
682+
hint 0xc // autia1716
683+
mov x30, x17
684+
mov x16, xzr
685+
mov x17, xzr
680686
// Finally, restore sp. This must be done after the last read from the
681687
// context struct, because it is allocated on the stack, and an exception
682688
// could clobber the de-allocated portion of the stack after sp has been

libunwind/src/UnwindRegistersSave.S

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -744,7 +744,13 @@ DEFINE_LIBUNWIND_FUNCTION(__unw_getcontext)
744744
str x30, [x0, #0x0F0]
745745
mov x1,sp
746746
str x1, [x0, #0x0F8]
747-
str x30, [x0, #0x100] // store return address as pc
747+
// Sign the return address as pc with the address of the context
748+
mov x17, x30
749+
mov x16, x0
750+
hint 0x8 // pacia1716
751+
str x17, [x0, #0x100] // store return address as pc
752+
mov x17, xzr
753+
mov x16, xzr
748754
// skip cpsr
749755
stp d0, d1, [x0, #0x110]
750756
stp d2, d3, [x0, #0x120]

0 commit comments

Comments
 (0)