-
Notifications
You must be signed in to change notification settings - Fork 15.2k
[libunwind][AArch64] Protect PC within libunwind's context. #113368
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||
---|---|---|---|---|---|---|---|---|
|
@@ -19,6 +19,9 @@ | |||||||
#include "libunwind.h" | ||||||||
#include "shadow_stack_unwind.h" | ||||||||
|
||||||||
#if defined(_LIBUNWIND_PTRAUTH_AVAILABLE) | ||||||||
#include <ptrauth.h> | ||||||||
#endif | ||||||||
namespace libunwind { | ||||||||
|
||||||||
// For emulating 128-bit registers | ||||||||
|
@@ -1823,9 +1826,127 @@ extern "C" void *__libunwind_shstk_get_jump_target() { | |||||||
#endif | ||||||||
|
||||||||
class _LIBUNWIND_HIDDEN Registers_arm64 { | ||||||||
struct GPRs; | ||||||||
|
||||||||
private: | ||||||||
/// The program counter is used effectively as a return address | ||||||||
/// when the context is restored therefore protect it with PAC. | ||||||||
/// The base address of the context is used with the A key for | ||||||||
/// authentication and signing. Return address authentication is | ||||||||
/// still managed according to the unwind info. In some cases | ||||||||
/// the LR contains significant bits in the space for the PAC bits the | ||||||||
/// value of the PC is stored in 2 halfs and each signed. | ||||||||
inline uint64_t getDiscriminator() const { | ||||||||
return reinterpret_cast<uint64_t>(this); | ||||||||
} | ||||||||
#if defined(_LIBUNWIND_AARCH64_PC_PROTECTION) | ||||||||
#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) | ||||||||
#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); | ||||||||
} | ||||||||
#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 lower = (uint64_t)__libunwind_ptrauth_auth_data( | ||||||||
(void *)gprs->__pc, ptrauth_key_asia, discriminator); | ||||||||
uint64_t upper = (uint64_t)__libunwind_ptrauth_auth_data( | ||||||||
(void *)gprs->__pc2, ptrauth_key_asia, discriminator); | ||||||||
return (upper << 32) | lower; | ||||||||
} | ||||||||
|
||||||||
// Sign and store the new PC. | ||||||||
inline void updatePC(uint64_t value) { | ||||||||
|
inline void updatePC(uint64_t value) { | |
__attribute__((always_inline)) | |
inline void updatePC(uint64_t value) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will you please expand how this would work? As far as I can see, the value
above comes from outside (e.g. from setRegister
call). Is this just saved unsigned PC that is located somewhere on the stack? So it could be substituted and then organized in ROP-like chain during unwinding process?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
unwinding starts from a context which is created by the __unw_getcontext
.
Here the authentication ensure the context/registers are still pointing to a legitimate address.
without this check any buffer could be used and setIP would just set the arbitrary address and where the restore context would happily jump.
If the value
is altered outside of libunwind then nothing to do here. Here I assume rest of the application has no idea about PAC. With PAuthAbi this would be different.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"used effectively" should be "effectively used", or just "used".