jasonmolenda created this revision.
jasonmolenda added a reviewer: JDevlieghere.
jasonmolenda added a project: LLDB.
Herald added a subscriber: kristof.beyls.
Herald added a project: All.
jasonmolenda requested review of this revision.
Herald added a subscriber: lldb-commits.
When debugserver thread_get_state/thread_set_state's registers from an
inferior, if the inferior is arm64e, debugserver must also be built arm64e, and
debugserver passes the values through a series of macros provided by the kernel
to authorize & clear auth bits off of the values that thread_get_state
provides. When the inferior process has crashed -- jumping through an
improperly signed function pointer, or jumped to invalid memory, the pc value
will fail to authenticate in these kernel macros. On M2
<https://reviews.llvm.org/M2> era Mac hardware, this auth failure results in
debugserver crashing.
We don't need to authenticate sp/pc/fp/lr, we only need to clear the auth bits
from the address values. This patch replaces the kernel macro accesses after
thread_get_state to do that. The macros like
__darwin_arm_thread_state64_get_pc() are gated on `__has_feature(ptrauth_calls)
&& defined(__LP64__)`, and in the case where we have `ptrauth_calls`, the
register context structure in <mach/arm/_structs.h> are `void *` instead of
`uint64_t`, so I needed to add a reinterpret cast of those values before
clearing them.
It would probably be better to move my checks of `__has_feature(ptrauth_calls)
&& defined(__LP64__)` into this `clear_pac_bits()` function and call it
unconditionally, instead of testing at all of the caller sites. (these two
tests are distinguishing between arm64_32 v. arm64 v. arm64e)
In the case of thread_set_state, we will still use the kernel provided macros
-- in this case, we are passing unsigned addresses and the signing will never
fail.
With this patch, we still trigger the warning that the program has halted
because of a PAC auth failure and show the most relevant pc value to explain
it; no change in behavior there.
Repository:
rG LLVM Github Monorepo
https://reviews.llvm.org/D136620
Files:
lldb/tools/debugserver/source/DNB.cpp
lldb/tools/debugserver/source/DNB.h
lldb/tools/debugserver/source/MacOSX/arm64/DNBArchImplARM64.cpp
lldb/tools/debugserver/source/RNBRemote.cpp
Index: lldb/tools/debugserver/source/RNBRemote.cpp
===================================================================
--- lldb/tools/debugserver/source/RNBRemote.cpp
+++ lldb/tools/debugserver/source/RNBRemote.cpp
@@ -4781,24 +4781,6 @@
return g_host_cputype != 0;
}
-static bool GetAddressingBits(uint32_t &addressing_bits) {
- static uint32_t g_addressing_bits = 0;
- static bool g_tried_addressing_bits_syscall = false;
- if (g_tried_addressing_bits_syscall == false) {
- size_t len = sizeof (uint32_t);
- if (::sysctlbyname("machdep.virtual_address_size",
- &g_addressing_bits, &len, NULL, 0) != 0) {
- g_addressing_bits = 0;
- }
- }
- g_tried_addressing_bits_syscall = true;
- addressing_bits = g_addressing_bits;
- if (addressing_bits > 0)
- return true;
- else
- return false;
-}
-
rnb_err_t RNBRemote::HandlePacket_qHostInfo(const char *p) {
std::ostringstream strm;
@@ -4812,7 +4794,7 @@
}
uint32_t addressing_bits = 0;
- if (GetAddressingBits(addressing_bits)) {
+ if (DNBGetAddressingBits(addressing_bits)) {
strm << "addressing_bits:" << std::dec << addressing_bits << ';';
}
Index: lldb/tools/debugserver/source/MacOSX/arm64/DNBArchImplARM64.cpp
===================================================================
--- lldb/tools/debugserver/source/MacOSX/arm64/DNBArchImplARM64.cpp
+++ lldb/tools/debugserver/source/MacOSX/arm64/DNBArchImplARM64.cpp
@@ -94,11 +94,30 @@
uint32_t DNBArchMachARM64::GetCPUType() { return CPU_TYPE_ARM64; }
+static uint64_t clear_pac_bits(uint64_t value) {
+ uint32_t addressing_bits = 0;
+ if (!DNBGetAddressingBits(addressing_bits) || addressing_bits == 0)
+ return value;
+
+ uint64_t mask = ((1ULL << addressing_bits) - 1);
+
+ // Normally PAC bit clearing needs to check b55 and either set the
+ // non-addressing bits, or clear them. But the register values we
+ // get from thread_get_state on an arm64e process don't follow this
+ // convention?, at least when there's been a PAC auth failure in
+ // the inferior.
+ // Userland processes are always in low memory, so this
+ // hardcoding b55 == 0 PAC stripping behavior here.
+
+ return value & mask; // high bits cleared to 0
+}
+
uint64_t DNBArchMachARM64::GetPC(uint64_t failValue) {
// Get program counter
if (GetGPRState(false) == KERN_SUCCESS)
-#if defined(__LP64__)
- return arm_thread_state64_get_pc(m_state.context.gpr);
+#if __has_feature(ptrauth_calls) && defined(__LP64__)
+ return clear_pac_bits(
+ reinterpret_cast<uint64_t>(m_state.context.gpr.__opaque_pc));
#else
return m_state.context.gpr.__pc;
#endif
@@ -128,8 +147,9 @@
uint64_t DNBArchMachARM64::GetSP(uint64_t failValue) {
// Get stack pointer
if (GetGPRState(false) == KERN_SUCCESS)
-#if defined(__LP64__)
- return arm_thread_state64_get_sp(m_state.context.gpr);
+#if __has_feature(ptrauth_calls) && defined(__LP64__)
+ return clear_pac_bits(
+ reinterpret_cast<uint64_t>(m_state.context.gpr.__opaque_sp));
#else
return m_state.context.gpr.__sp;
#endif
@@ -150,16 +170,20 @@
if (DNBLogEnabledForAny(LOG_THREAD)) {
uint64_t *x = &m_state.context.gpr.__x[0];
-#if defined(__LP64__)
- uint64_t log_fp = arm_thread_state64_get_fp(m_state.context.gpr);
- uint64_t log_lr = arm_thread_state64_get_lr(m_state.context.gpr);
- uint64_t log_sp = arm_thread_state64_get_sp(m_state.context.gpr);
- uint64_t log_pc = arm_thread_state64_get_pc(m_state.context.gpr);
+#if __has_feature(ptrauth_calls) && defined(__LP64__)
+ uint64_t log_fp = clear_pac_bits(
+ reinterpret_cast<uint64_t>(m_state.context.gpr.__opaque_fp));
+ uint64_t log_lr = clear_pac_bits(
+ reinterpret_cast<uint64_t>(m_state.context.gpr.__opaque_lr));
+ uint64_t log_sp = clear_pac_bits(
+ reinterpret_cast<uint64_t>(m_state.context.gpr.__opaque_sp));
+ uint64_t log_pc = clear_pac_bits(
+ reinterpret_cast<uint64_t>(m_state.context.gpr.__opaque_pc));
#else
- uint64_t log_fp = m_state.context.gpr.__fp;
- uint64_t log_lr = m_state.context.gpr.__lr;
- uint64_t log_sp = m_state.context.gpr.__sp;
- uint64_t log_pc = m_state.context.gpr.__pc;
+ uint64_t log_fp = m_state.context.gpr.__fp;
+ uint64_t log_lr = m_state.context.gpr.__lr;
+ uint64_t log_sp = m_state.context.gpr.__sp;
+ uint64_t log_pc = m_state.context.gpr.__pc;
#endif
DNBLogThreaded(
"thread_get_state(0x%4.4x, %u, &gpr, %u) => 0x%8.8x (count = %u) regs"
@@ -617,8 +641,9 @@
return err.Status();
}
-#if defined(__LP64__)
- uint64_t pc = arm_thread_state64_get_pc (m_state.context.gpr);
+#if __has_feature(ptrauth_calls) && defined(__LP64__)
+ uint64_t pc = clear_pac_bits(
+ reinterpret_cast<uint64_t>(m_state.context.gpr.__opaque_pc));
#else
uint64_t pc = m_state.context.gpr.__pc;
#endif
@@ -1992,20 +2017,10 @@
switch (set) {
case e_regSetGPR:
if (reg <= gpr_pc) {
-#if defined(__LP64__)
- if (reg == gpr_pc)
- value->value.uint64 = arm_thread_state64_get_pc (m_state.context.gpr);
- else if (reg == gpr_lr)
- value->value.uint64 = arm_thread_state64_get_lr (m_state.context.gpr);
- else if (reg == gpr_sp)
- value->value.uint64 = arm_thread_state64_get_sp (m_state.context.gpr);
- else if (reg == gpr_fp)
- value->value.uint64 = arm_thread_state64_get_fp (m_state.context.gpr);
+ if (reg == gpr_pc || reg == gpr_lr || reg == gpr_sp || reg == gpr_fp)
+ value->value.uint64 = clear_pac_bits(m_state.context.gpr.__x[reg]);
else
- value->value.uint64 = m_state.context.gpr.__x[reg];
-#else
- value->value.uint64 = m_state.context.gpr.__x[reg];
-#endif
+ value->value.uint64 = m_state.context.gpr.__x[reg];
return true;
} else if (reg == gpr_cpsr) {
value->value.uint32 = m_state.context.gpr.__cpsr;
Index: lldb/tools/debugserver/source/DNB.h
===================================================================
--- lldb/tools/debugserver/source/DNB.h
+++ lldb/tools/debugserver/source/DNB.h
@@ -245,4 +245,6 @@
/// \return true if debugserver is running in translation
/// (is an x86_64 process on arm64)
bool DNBDebugserverIsTranslated();
+
+bool DNBGetAddressingBits(uint32_t &addressing_bits);
#endif
Index: lldb/tools/debugserver/source/DNB.cpp
===================================================================
--- lldb/tools/debugserver/source/DNB.cpp
+++ lldb/tools/debugserver/source/DNB.cpp
@@ -1824,3 +1824,21 @@
return false;
return ret == 1;
}
+
+bool DNBGetAddressingBits(uint32_t &addressing_bits) {
+ static uint32_t g_addressing_bits = 0;
+ static bool g_tried_addressing_bits_syscall = false;
+ if (g_tried_addressing_bits_syscall == false) {
+ size_t len = sizeof(uint32_t);
+ if (::sysctlbyname("machdep.virtual_address_size", &g_addressing_bits, &len,
+ NULL, 0) != 0) {
+ g_addressing_bits = 0;
+ }
+ }
+ g_tried_addressing_bits_syscall = true;
+ addressing_bits = g_addressing_bits;
+ if (addressing_bits > 0)
+ return true;
+ else
+ return false;
+}
_______________________________________________
lldb-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits