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
lldb-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

Reply via email to