libunwindstack: support for Armv8.3-A Pointer Authentication
This patch adds support for handling return addresses signed with
pointer authentication. It simply strips the authentication code
without verifying its correctness, and thus works with both A and B
keys and through key-change boundaries.
Additons:
* DW_CFA_AARCH64_negate_ra_state: new CFA operation.
* RA_SIGN_STATE: new pseudo register.
* Pass the arch to DwarfCfa so that the new op is only executed
on aarch64.
The stripping uses the xpaclri instruction. This is a hint space
instruction which is compatible with pre Armv8.3-A devices. For cases
where it cannot be used, a mask can be set instead.
Test: libunwindstack_test
Without this patch all UnwindTest.* testcases should fail if
compiled with Pointer Authentication.
The tests should be executed with both -mbranch-protection=pac-ret and
pac-ret+leaf flags so that either some or all functions have pointer
authentication instructions.
Change-Id: Id7c3f1d0e2fc7fccb19bd1430826264405a9df7c
diff --git a/libunwindstack/RegsArm64.cpp b/libunwindstack/RegsArm64.cpp
index 5b7431a..b496187 100644
--- a/libunwindstack/RegsArm64.cpp
+++ b/libunwindstack/RegsArm64.cpp
@@ -30,7 +30,10 @@
namespace unwindstack {
RegsArm64::RegsArm64()
- : RegsImpl<uint64_t>(ARM64_REG_LAST, Location(LOCATION_REGISTER, ARM64_REG_LR)) {}
+ : RegsImpl<uint64_t>(ARM64_REG_LAST, Location(LOCATION_REGISTER, ARM64_REG_LR)) {
+ ResetPseudoRegisters();
+ pac_mask_ = 0;
+}
ArchEnum RegsArm64::Arch() {
return ARCH_ARM64;
@@ -45,6 +48,23 @@
}
void RegsArm64::set_pc(uint64_t pc) {
+ // If the target is aarch64 then the return address may have been
+ // signed using the Armv8.3-A Pointer Authentication extension. The
+ // original return address can be restored by stripping out the
+ // authentication code using a mask or xpaclri. xpaclri is a NOP on
+ // pre-Armv8.3-A architectures.
+ if ((0 != pc) && IsRASigned()) {
+ if (pac_mask_) {
+ pc &= ~pac_mask_;
+#if defined(__aarch64__)
+ } else {
+ register uint64_t x30 __asm("x30") = pc;
+ // This is XPACLRI.
+ asm("hint 0x7" : "+r"(x30));
+ pc = x30;
+#endif
+ }
+ }
regs_[ARM64_REG_PC] = pc;
}
@@ -144,6 +164,37 @@
return true;
}
+void RegsArm64::ResetPseudoRegisters(void) {
+ // DWARF for AArch64 says RA_SIGN_STATE should be initialized to 0.
+ this->SetPseudoRegister(Arm64Reg::ARM64_PREG_RA_SIGN_STATE, 0);
+}
+
+bool RegsArm64::SetPseudoRegister(uint16_t id, uint64_t value) {
+ if ((id >= Arm64Reg::ARM64_PREG_FIRST) && (id < Arm64Reg::ARM64_PREG_LAST)) {
+ pseudo_regs_[id - Arm64Reg::ARM64_PREG_FIRST] = value;
+ return true;
+ }
+ return false;
+}
+
+bool RegsArm64::GetPseudoRegister(uint16_t id, uint64_t* value) {
+ if ((id >= Arm64Reg::ARM64_PREG_FIRST) && (id < Arm64Reg::ARM64_PREG_LAST)) {
+ *value = pseudo_regs_[id - Arm64Reg::ARM64_PREG_FIRST];
+ return true;
+ }
+ return false;
+}
+
+bool RegsArm64::IsRASigned() {
+ uint64_t value;
+ auto result = this->GetPseudoRegister(Arm64Reg::ARM64_PREG_RA_SIGN_STATE, &value);
+ return (result && (value != 0));
+}
+
+void RegsArm64::SetPACMask(uint64_t mask) {
+ pac_mask_ = mask;
+}
+
Regs* RegsArm64::Clone() {
return new RegsArm64(*this);
}