[PATCH 0/5] Enable PAC support in elfutils

2024-06-14 Thread Steve Capper
Hello,
This series enables Pointer Authentication (PAC) support in elfutils.

The first four patches were originally posted by German Gomez. I've
rebased to the latest elfutils and added an extra patch that was
required to debug core dumps from PAC enabled applications.

These patches were tested on Debian Testing and Fedora 40 running on an
Apple M1 MacBook Pro (the CFLAG -mbranch-protection=standard needs to be
supplied to the build).

Without this series applied, the following tests failed:
 * run-backtrace-native.sh
 * run-backtrace-dwarf.sh
 * run-backtrace-native-core.sh
 * run-deleted.sh

I am happy to chop/change bits as necessary. 

A guide to pointer authentication can be found here:
https://developer.arm.com/-/media/Arm%20Developer%20Community/PDF/Learn%20the%20Architecture/Providing%20protection%20for%20complex%20software.pdf

Cheers,
-- 
Steve

German Gomez (4):
  aarch64: Create definitions for AARCH64_RA_SIGN_STATE register
  libdw, aarch64: Implement DW_CFA_AARCH64_negate_ra_state CFI
instruction
  libdwfl, aarch64: Demangle return addresses using a PAC mask
  libdwfl, eu-stack, aarch64: Add API for setting AARCH64 PAC mask.

Steve Capper (1):
  libdwfl, aarch64: Read PAC mask from core

 backends/aarch64_corenote.c | 15 ++-
 backends/aarch64_init.c |  6 +++---
 backends/aarch64_initreg.c  |  2 ++
 backends/aarch64_regs.c |  5 -
 libdw/cfi.c | 14 +-
 libdw/dwarf.h   |  5 +
 libdw/libdw.map |  1 +
 libdwfl/dwfl_frame.c|  3 +++
 libdwfl/dwfl_frame_regs.c   | 10 ++
 libdwfl/frame_unwind.c  | 14 +-
 libdwfl/libdwfl.h   |  6 ++
 libdwfl/libdwflP.h  |  7 +++
 libdwfl/linux-core-attach.c | 34 ++
 libdwfl/linux-pid-attach.c  | 34 --
 tests/run-addrcfi.sh|  1 +
 tests/run-allregs.sh|  1 +
 16 files changed, 149 insertions(+), 9 deletions(-)

-- 
2.39.2



[PATCH 3/5] libdwfl, aarch64: Demangle return addresses using a PAC mask

2024-06-14 Thread Steve Capper
From: German Gomez 

Demangle mangled return addresses on AARCH64. The value of the masks is
stored in the struct Dwfl_Thread.

Signed-off-by: German Gomez 
Signed-off-by: Steve Capper 
---
 libdwfl/dwfl_frame.c   |  3 +++
 libdwfl/frame_unwind.c | 14 +-
 libdwfl/libdwflP.h |  7 +++
 3 files changed, 23 insertions(+), 1 deletion(-)

diff --git a/libdwfl/dwfl_frame.c b/libdwfl/dwfl_frame.c
index 5ee71dd4..8af8843f 100644
--- a/libdwfl/dwfl_frame.c
+++ b/libdwfl/dwfl_frame.c
@@ -269,6 +269,8 @@ dwfl_getthreads (Dwfl *dwfl, int (*callback) (Dwfl_Thread 
*thread, void *arg),
   thread.process = process;
   thread.unwound = NULL;
   thread.callbacks_arg = NULL;
+  thread.aarch64.pauth_insn_mask = 0;
+
   for (;;)
 {
   thread.tid = process->callbacks->next_thread (dwfl,
@@ -339,6 +341,7 @@ getthread (Dwfl *dwfl, pid_t tid,
   thread.process = process;
   thread.unwound = NULL;
   thread.callbacks_arg = NULL;
+  thread.aarch64.pauth_insn_mask = 0;
 
   if (process->callbacks->get_thread (dwfl, tid, process->callbacks_arg,
  &thread.callbacks_arg))
diff --git a/libdwfl/frame_unwind.c b/libdwfl/frame_unwind.c
index 1e2f0255..3c62ca86 100644
--- a/libdwfl/frame_unwind.c
+++ b/libdwfl/frame_unwind.c
@@ -599,7 +599,19 @@ handle_cfi (Dwfl_Frame *state, Dwarf_Addr pc, Dwarf_CFI 
*cfi, Dwarf_Addr bias)
 
   /* Some architectures encode some extra info in the return address.  */
   if (regno == frame->fde->cie->return_address_register)
-   regval &= ebl_func_addr_mask (ebl);
+   {
+ regval &= ebl_func_addr_mask (ebl);
+
+ /* In aarch64, pseudo-register RA_SIGN_STATE indicates whether the 
+return address needs demangling using the PAC mask from the
+thread. */
+ if (cfi->e_machine == EM_AARCH64 &&
+ frame->nregs > DW_AARCH64_RA_SIGN_STATE &&
+ frame->regs[DW_AARCH64_RA_SIGN_STATE].value & 0x1)
+   {
+ regval &= ~(state->thread->aarch64.pauth_insn_mask);
+   }
+   }
 
   /* This is another strange PPC[64] case.  There are two
 registers numbers that can represent the same DWARF return
diff --git a/libdwfl/libdwflP.h b/libdwfl/libdwflP.h
index b3dfea1d..0c516b54 100644
--- a/libdwfl/libdwflP.h
+++ b/libdwfl/libdwflP.h
@@ -241,6 +241,12 @@ struct Dwfl_Thread
   /* Bottom (innermost) frame while we're initializing, NULL afterwards.  */
   Dwfl_Frame *unwound;
   void *callbacks_arg;
+
+  /* Data for handling AARCH64 (currently limited to demangling PAC from
+ return addresses). */
+  struct {
+Dwarf_Addr pauth_insn_mask;
+  } aarch64;
 };
 
 /* See its typedef in libdwfl.h.  */
@@ -787,6 +793,7 @@ INTDECL (dwfl_thread_tid)
 INTDECL (dwfl_frame_thread)
 INTDECL (dwfl_thread_state_registers)
 INTDECL (dwfl_thread_state_register_pc)
+INTDECL (dwfl_thread_state_aarch64_pauth)
 INTDECL (dwfl_getthread_frames)
 INTDECL (dwfl_getthreads)
 INTDECL (dwfl_thread_getframes)
-- 
2.39.2



[PATCH 4/5] libdwfl, eu-stack, aarch64: Add API for setting AARCH64 PAC mask.

2024-06-14 Thread Steve Capper
From: German Gomez 

Add user API for setting the PAC mask.
The function is intended to be called in 
Dwfl_Thread_Callbacks.set_initial_registers.

Testing notes:

 ... consider the following program.c

 | int a = 0;
 | void leaf(void) {
 |   for (;;)
 | a += a;
 | }
 | void parent(void) {
 |   leaf();
 | }
 | int main(void) {
 |   parent();
 |   return 0;
 | }

 ... compiled with "gcc-10 -O0 -g -mbranch-protection=pac-ret+leaf program.c"
 ... should yield the correct call stack, without mangled addresses:

 | $ eu-stack -p 
 |
 | PID 760267 - process
 | TID 760267:
 | #0  0xaebd0804 leaf
 | #1  0xaebd0818 parent
 | #2  0xaebd0838 main
 | #3  0xbd52ad50 __libc_start_main
 | #4  0xaebd0694 $x

Signed-off-by: German Gomez 
Signed-off-by: Steve Capper 
---
 libdw/libdw.map|  1 +
 libdwfl/dwfl_frame_regs.c  | 10 ++
 libdwfl/libdwfl.h  |  6 ++
 libdwfl/linux-pid-attach.c | 34 --
 4 files changed, 49 insertions(+), 2 deletions(-)

diff --git a/libdw/libdw.map b/libdw/libdw.map
index 3c5ce8dc..84393f4b 100644
--- a/libdw/libdw.map
+++ b/libdw/libdw.map
@@ -377,4 +377,5 @@ ELFUTILS_0.188 {
 ELFUTILS_0.191 {
   global:
 dwarf_cu_dwp_section_info;
+dwfl_thread_state_aarch64_pauth;
 } ELFUTILS_0.188;
diff --git a/libdwfl/dwfl_frame_regs.c b/libdwfl/dwfl_frame_regs.c
index a4bd3884..29e3d0b1 100644
--- a/libdwfl/dwfl_frame_regs.c
+++ b/libdwfl/dwfl_frame_regs.c
@@ -71,3 +71,13 @@ dwfl_frame_reg (Dwfl_Frame *state, unsigned regno, 
Dwarf_Word *val)
   return res;
 }
 INTDEF(dwfl_frame_reg)
+
+void
+dwfl_thread_state_aarch64_pauth(Dwfl_Thread *thread, Dwarf_Word insn_mask)
+{
+  Dwfl_Frame *state = thread->unwound;
+  assert (state && state->unwound == NULL);
+  assert (state->initial_frame);
+  thread->aarch64.pauth_insn_mask = insn_mask;
+}
+INTDEF(dwfl_thread_state_aarch64_pauth)
diff --git a/libdwfl/libdwfl.h b/libdwfl/libdwfl.h
index 49ad6664..3ab07514 100644
--- a/libdwfl/libdwfl.h
+++ b/libdwfl/libdwfl.h
@@ -763,6 +763,12 @@ bool dwfl_thread_state_registers (Dwfl_Thread *thread, int 
firstreg,
 void dwfl_thread_state_register_pc (Dwfl_Thread *thread, Dwarf_Word pc)
   __nonnull_attribute__ (1);
 
+/* Called by Dwfl_Thread_Callbacks.set_initial_registers implementation.
+   On AARCH64 platforms with Pointer Authentication, the bits from this mask
+   indicate the position of the PAC bits in return addresses. */
+void dwfl_thread_state_aarch64_pauth (Dwfl_Thread *thread, Dwarf_Word 
insn_mask)
+  __nonnull_attribute__ (1);
+
 /* Iterate through the threads for a process.  Returns zero if all threads have
been processed by the callback, returns -1 on error, or the value of the
callback when not DWARF_CB_OK.  -1 returned on error will set dwfl_errno ().
diff --git a/libdwfl/linux-pid-attach.c b/libdwfl/linux-pid-attach.c
index de867857..e68af4a9 100644
--- a/libdwfl/linux-pid-attach.c
+++ b/libdwfl/linux-pid-attach.c
@@ -320,6 +320,28 @@ pid_thread_state_registers_cb (int firstreg, unsigned 
nregs,
   return INTUSE(dwfl_thread_state_registers) (thread, firstreg, nregs, regs);
 }
 
+#if defined(__aarch64__)
+
+#include  /* struct user_pac_mask */
+
+static void
+pid_set_aarch64_pauth(Dwfl_Thread *thread, pid_t tid)
+{
+  struct user_pac_mask pac_mask;
+  struct iovec iovec;
+
+  iovec.iov_base = &pac_mask;
+  iovec.iov_len = sizeof (pac_mask);
+
+  /* If the ptrace returns an error, the system may not support pointer
+ authentication. In that case, set the masks to 0 (no PAC bits). */
+  if (ptrace(PTRACE_GETREGSET, tid, NT_ARM_PAC_MASK, &iovec))
+pac_mask.insn_mask = 0;
+
+  INTUSE(dwfl_thread_state_aarch64_pauth) (thread, pac_mask.insn_mask);
+}
+#endif /* __aarch64__ */
+
 static bool
 pid_set_initial_registers (Dwfl_Thread *thread, void *thread_arg)
 {
@@ -332,8 +354,16 @@ pid_set_initial_registers (Dwfl_Thread *thread, void 
*thread_arg)
   pid_arg->tid_attached = tid;
   Dwfl_Process *process = thread->process;
   Ebl *ebl = process->ebl;
-  return ebl_set_initial_registers_tid (ebl, tid,
-   pid_thread_state_registers_cb, thread);
+  if (!ebl_set_initial_registers_tid (ebl, tid,
+ pid_thread_state_registers_cb, thread))
+return false;
+
+#if defined(__aarch64__)
+  /* Set aarch64 pointer authentication data. */
+  pid_set_aarch64_pauth(thread, tid);
+#endif
+
+  return true;
 }
 
 static void
-- 
2.39.2



[PATCH 1/5] aarch64: Create definitions for AARCH64_RA_SIGN_STATE register

2024-06-14 Thread Steve Capper
From: German Gomez 

This register will be used to indicate whether a return address is
mangled with a PAC or not, in accordance with the DWARF AARCH64 ABI [1].

[1] 
https://github.com/ARM-software/abi-aa/blob/main/aadwarf64/aadwarf64.rst#41dwarf-register-names

Signed-off-by: German Gomez 
Signed-off-by: Steve Capper 
---
 backends/aarch64_init.c| 6 +++---
 backends/aarch64_initreg.c | 2 ++
 backends/aarch64_regs.c| 5 -
 libdw/dwarf.h  | 5 +
 tests/run-addrcfi.sh   | 1 +
 tests/run-allregs.sh   | 1 +
 6 files changed, 16 insertions(+), 4 deletions(-)

diff --git a/backends/aarch64_init.c b/backends/aarch64_init.c
index bed92954..0a3a2c79 100644
--- a/backends/aarch64_init.c
+++ b/backends/aarch64_init.c
@@ -55,10 +55,10 @@ aarch64_init (Elf *elf __attribute__ ((unused)),
   HOOK (eh, data_marker_symbol);
   HOOK (eh, abi_cfi);
 
-  /* X0-X30 (31 regs) + SP + 1 Reserved + ELR, 30 Reserved regs (34-43)
+  /* X0-X30 (31 regs) + SP + 1 Reserved + ELR + RA_SIGN_STATE, 30 Reserved 
regs (34-43)
  + V0-V31 (32 regs, least significant 64 bits only)
- + ALT_FRAME_RETURN_COLUMN (used when LR isn't used) = 97 DWARF regs. */
-  eh->frame_nregs = 97;
+ + ALT_FRAME_RETURN_COLUMN (used when LR isn't used) = 98 DWARF regs. */
+  eh->frame_nregs = 98;
   HOOK (eh, set_initial_registers_tid);
   HOOK (eh, unwind);
 
diff --git a/backends/aarch64_initreg.c b/backends/aarch64_initreg.c
index daf6f375..4661068a 100644
--- a/backends/aarch64_initreg.c
+++ b/backends/aarch64_initreg.c
@@ -73,6 +73,8 @@ aarch64_set_initial_registers_tid (pid_t tid __attribute__ 
((unused)),
 
   /* ELR cannot be found.  */
 
+  /* RA_SIGN_STATE cannot be found */
+
   /* FP registers (only 64bits are used).  */
   struct user_fpsimd_struct fregs;
   iovec.iov_base = &fregs;
diff --git a/backends/aarch64_regs.c b/backends/aarch64_regs.c
index 23014bfc..e95ece37 100644
--- a/backends/aarch64_regs.c
+++ b/backends/aarch64_regs.c
@@ -87,7 +87,10 @@ aarch64_register_info (Ebl *ebl __attribute__ ((unused)),
 case 33:
   return regtype ("integer", DW_ATE_address, "elr");
 
-case 34 ... 63:
+case 34:
+  return regtype ("integer", DW_ATE_unsigned, "ra_sign_state");
+
+case 35 ... 63:
   return 0;
 
 case 64 ... 95:
diff --git a/libdw/dwarf.h b/libdw/dwarf.h
index 4be32de5..f6d26044 100644
--- a/libdw/dwarf.h
+++ b/libdw/dwarf.h
@@ -1028,6 +1028,11 @@ enum
 DW_EH_PE_indirect = 0x80
   };
 
+/* AARCH64 DWARF registers. */
+enum
+  {
+DW_AARCH64_RA_SIGN_STATE = 34
+  };
 
 /* DWARF XXX.  */
 #define DW_ADDR_none   0
diff --git a/tests/run-addrcfi.sh b/tests/run-addrcfi.sh
index 64fa24d7..ce9e753e 100755
--- a/tests/run-addrcfi.sh
+++ b/tests/run-addrcfi.sh
@@ -3639,6 +3639,7 @@ dwarf_cfi_addrframe (.eh_frame): no matching address range
integer reg30 (x30): same_value
integer reg31 (sp): location expression: call_frame_cfa stack_value
integer reg33 (elr): undefined
+   integer reg34 (ra_sign_state): undefined
FP/SIMD reg64 (v0): undefined
FP/SIMD reg65 (v1): undefined
FP/SIMD reg66 (v2): undefined
diff --git a/tests/run-allregs.sh b/tests/run-allregs.sh
index 87b16c95..ed086651 100755
--- a/tests/run-allregs.sh
+++ b/tests/run-allregs.sh
@@ -2693,6 +2693,7 @@ integer registers:
 30: x30 (x30), signed 64 bits
 31: sp (sp), address 64 bits
 33: elr (elr), address 64 bits
+34: ra_sign_state (ra_sign_state), unsigned 64 bits
 FP/SIMD registers:
 64: v0 (v0), unsigned 128 bits
 65: v1 (v1), unsigned 128 bits
-- 
2.39.2



[PATCH 2/5] libdw, aarch64: Implement DW_CFA_AARCH64_negate_ra_state CFI instruction

2024-06-14 Thread Steve Capper
From: German Gomez 

Implement DW_CFA_AARCH64_negate_ra_state in accordance with the DWARF
AARCH64 ABI [1].

Followup commits will use the value of this register to remove the PAC
from return addresses.

[1] 
https://github.com/ARM-software/abi-aa/blob/main/aadwarf64/aadwarf64.rst#44call-frame-instructions

Signed-off-by: German Gomez 
Signed-off-by: Steve Capper 
---
 libdw/cfi.c | 14 +-
 1 file changed, 13 insertions(+), 1 deletion(-)

diff --git a/libdw/cfi.c b/libdw/cfi.c
index a7174405..743bfc07 100644
--- a/libdw/cfi.c
+++ b/libdw/cfi.c
@@ -125,6 +125,15 @@ execute_cfi (Dwarf_CFI *cache,
 fs->regs[regno].value = (r_value); \
   } while (0)
 
+  /* The AARCH64 DWARF ABI states that register 34 (ra_sign_state) must
+ be initialized to 0. So do it before executing the CFI. */
+  if (cache->e_machine == EM_AARCH64)
+{
+  if (unlikely (! enough_registers (DW_AARCH64_RA_SIGN_STATE, &fs, 
&result)))
+   goto out;
+  fs->regs[DW_AARCH64_RA_SIGN_STATE].value = 0;
+}
+
   while (program < end)
 {
   uint8_t opcode = *program++;
@@ -357,7 +366,10 @@ execute_cfi (Dwarf_CFI *cache,
{
  /* Toggles the return address state, indicating whether
 the return address is encrypted or not on
-aarch64. XXX not handled yet.  */
+aarch64. */
+ if (unlikely (! enough_registers (DW_AARCH64_RA_SIGN_STATE, &fs, 
&result)))
+   goto out;
+ fs->regs[DW_AARCH64_RA_SIGN_STATE].value ^= 0x1;
}
  else
{
-- 
2.39.2



[PATCH 5/5] libdwfl, aarch64: Read PAC mask from core

2024-06-14 Thread Steve Capper
We need to read the PAC mask from a core file when debugging offline
as the information is still needed to demangle return addresses.

This commit pulls out the NT_ARM_PAC_MASK info from the core and feeds
it through to dwfl_thread_state_aarch64_pauth for each thread.

Signed-off-by: Steve Capper 
---
 backends/aarch64_corenote.c | 15 ++-
 libdwfl/linux-core-attach.c | 34 ++
 2 files changed, 48 insertions(+), 1 deletion(-)

diff --git a/backends/aarch64_corenote.c b/backends/aarch64_corenote.c
index 905a4b8a..f612d2ce 100644
--- a/backends/aarch64_corenote.c
+++ b/backends/aarch64_corenote.c
@@ -107,6 +107,18 @@ static const Ebl_Core_Item aarch64_syscall_items [] =
 }
   };
 
+static const Ebl_Core_Item aarch64_pac_items [] =
+  {
+{
+  .name = "data_mask", .type = ELF_T_XWORD, .format = 'x',
+  .offset = 0, .group = "register"
+},
+{
+  .name = "insn_mask", .type = ELF_T_XWORD, .format = 'x',
+  .offset = 8, .group = "register"
+}
+  };
+
 #define AARCH64_HWBP_REG(KIND, N)  \
 {  \
   .name = "DBG" KIND "VR" #N "_EL1", .type = ELF_T_XWORD, .format = 'x', \
@@ -167,6 +179,7 @@ AARCH64_BP_WP_GROUP ("W", aarch64_hw_wp_items);
   EXTRA_ITEMS (NT_ARM_TLS, 8, aarch64_tls_items)   \
   EXTRA_ITEMS (NT_ARM_HW_BREAK, 264, aarch64_hw_bp_items)  \
   EXTRA_ITEMS (NT_ARM_HW_WATCH, 264, aarch64_hw_wp_items)  \
-  EXTRA_ITEMS (NT_ARM_SYSTEM_CALL, 4, aarch64_syscall_items)
+  EXTRA_ITEMS (NT_ARM_SYSTEM_CALL, 4, aarch64_syscall_items)   \
+  EXTRA_ITEMS (NT_ARM_PAC_MASK, 16, aarch64_pac_items)
 
 #include "linux-core-note.c"
diff --git a/libdwfl/linux-core-attach.c b/libdwfl/linux-core-attach.c
index d6f9e971..91a5461a 100644
--- a/libdwfl/linux-core-attach.c
+++ b/libdwfl/linux-core-attach.c
@@ -289,6 +289,40 @@ core_set_initial_registers (Dwfl_Thread *thread, void 
*thread_arg_voidp)
  reg_desc += regloc->pad;
}
 }
+
+  /* look for any Pointer Authentication code masks on AArch64 machines */
+  GElf_Ehdr ehdr_mem;
+  GElf_Ehdr *ehdr = gelf_getehdr(core, &ehdr_mem);
+  if (ehdr && ehdr->e_machine == EM_AARCH64)
+  {
+while (offset < note_data->d_size
+   && (offset = gelf_getnote (note_data, offset,
+   &nhdr, &name_offset, &desc_offset)) > 0)
+{
+  if (nhdr.n_type != NT_ARM_PAC_MASK)
+continue;
+
+  name = (nhdr.n_namesz == 0 ? "" : note_data->d_buf + name_offset);
+  desc = note_data->d_buf + desc_offset;
+  core_note_err = ebl_core_note (core_arg->ebl, &nhdr, name, desc,
+  ®s_offset, &nregloc, ®locs,
+  &nitems, &items);
+  if (!core_note_err)
+break;
+
+  for (item = items; item < items + nitems; item++)
+if (strcmp(item->name, "insn_mask") == 0)
+  break;
+
+  if (item == items + nitems)
+continue;
+
+  uint64_t insn_mask = read_8ubyte_unaligned_noncvt(desc + item->offset);
+  dwfl_thread_state_aarch64_pauth(thread, insn_mask);
+  break;
+}
+  }
+
   return true;
 }
 
-- 
2.39.2



Re: [PATCH 5/5] libdwfl, aarch64: Read PAC mask from core

2024-08-19 Thread Steve Capper




On 17/08/2024 01:13, Mark Wielaard wrote:

Hi Steve,



Hey Mark,


On Fri, Jun 14, 2024 at 03:47:19PM +0100, Steve Capper wrote:

We need to read the PAC mask from a core file when debugging offline
as the information is still needed to demangle return addresses.

This commit pulls out the NT_ARM_PAC_MASK info from the core and feeds
it through to dwfl_thread_state_aarch64_pauth for each thread.


Sorry, I was on vacation and started reviewing patches posted while I
was away. Should have started at the other end of the queue.



Not a problem, a big thanks for looking into this!


This patch partially overlaps with:
https://patchwork.sourceware.org/project/elfutils/patch/20240814085134.109500-3-kuan-ying@canonical.com/

Luckily the patches agree on the definition of the the pac_items
(modulo the name data_mask/insn_mask vs pauth_dmask/pauth_cmask).

This patch doesn't introduce a regset for ARM_PAC_ENABLED_KEYS that
the other one does. Is this not necessary?



My patch pulled out what was needed to perform the unwinding by the 
debug tools. From the debug tool's point of view, one doesn't need to 
know the keys in order to unwind the call stack, it is sufficient to 
mask off the pointer authentication code from the pointer. (The size of 
address space varies on AArch64 so we need to read the mask rather than 
a boolean "is-enabled" style flag).


Indeed, even with the keys, one also needs the modifier register 
contents (typically SP for AUTIASP) at the point of authentication in 
order to successfully authenticate the pointer. So for a debug tool, it 
is often easier to just mask off the PAC from the pointer.


(Additionally the debug tool may be running on a system without PAC 
support anyway, so a pointer mask out would be preferred).


It is useful to have the keys though when debugging a PAC issue, so I 
believe both Kuan-Ying's and my patches to be complementary.


Would you like me to tweak my patch-set to apply on top of Kuan-Ying's?
(Also happy to test a branch if you do a rebase your end)

Cheers,
--
Steve


Re: [PATCH 0/5] Enable PAC support in elfutils

2024-08-22 Thread Steve Capper




On 21/08/2024 00:34, Mark Wielaard wrote:

Hi Steve,



Hey Mark,


On Fri, Jun 14, 2024 at 03:47:14PM +0100, Steve Capper wrote:

This series enables Pointer Authentication (PAC) support in elfutils.

The first four patches were originally posted by German Gomez. I've
rebased to the latest elfutils and added an extra patch that was
required to debug core dumps from PAC enabled applications.

These patches were tested on Debian Testing and Fedora 40 running on an
Apple M1 MacBook Pro (the CFLAG -mbranch-protection=standard needs to be
supplied to the build).


Thanks, I found a setup to test this and it works. Nice.


Without this series applied, the following tests failed:
  * run-backtrace-native.sh
  * run-backtrace-dwarf.sh
  * run-backtrace-native-core.sh
  * run-deleted.sh

I am happy to chop/change bits as necessary.


I had some small comments on the first two patches. They look good,
just tiny nitpicks.



Thanks!


The last three introduce/depend on a new public function
dwfl_thread_state_aarch64_pauth. I rather not have such a public
architecture specific function.

Could we instead try to reuse dwfl_thread_state_registers?  We could
say that negative regnums are special architecture specific settings?
There is already some precedent for that in the the
thread_state_registers_cb function given to
ebl_set_initial_registers_tid, which call with -1 to set the PC
value. We could use -2 to indicate it is an arch specific setting.

That is also slightly ugly. But given we can hide most of it in
architecture specific/private code better than special case public
architecture functions.



Yeah, I know what you mean. There's some #ifdef AARCH64 stuff too.

I have removed dwfl_thread_state_aarch64_pauth and have rolled the 
functionality into dwfl_thread_state_registers. It looks a lot tidier 
now thanks :-).


Just giving the revised series a few tests and will send out a V2 shortly.

Cheers,
--
Steve


[PATCH v2 0/5] Enable PAC support in elfutils

2024-08-26 Thread Steve Capper
Hello,
This series enables Pointer Authentication (PAC) support in elfutils.

The first three patches were originally posted by German Gomez. I've
rewritten a new patch to extend the dwfl_thread_state_registers function
to handle the PAC mask and added an extra patch that was required to
debug core dumps from PAC enabled applications.

These patches were tested on Debian Testing and Fedora 40 running on an
Apple M1 MacBook Pro (the CFLAG -mbranch-protection=standard needs to be
supplied to the build).

Without this series applied, the following tests failed:
 * run-backtrace-native.sh
 * run-backtrace-dwarf.sh
 * run-backtrace-native-core.sh
 * run-deleted.sh

I am happy to chop/change bits as necessary. 

A guide to pointer authentication can be found here:
https://developer.arm.com/-/media/Arm%20Developer%20Community/PDF/Learn%20the%20Architecture/Providing%20protection%20for%20complex%20software.pdf

Changed in V2:
 * moved DW_AARCH64_RA_SIGN_STATE to cfi.h,
 * dwfl_thread_state_aarch64_pauth API dropped,
   (using dwfl_thread_state_registers instead)
 * AArch64 #ifdefs removed, ptrace logic for PAC consolidated into
   aarch64_initreg.c
 
Cheers,
-- 
Steve

German Gomez (3):
  aarch64: Create definitions for AARCH64_RA_SIGN_STATE register
  libdw, aarch64: Implement DW_CFA_AARCH64_negate_ra_state CFI
instruction
  libdwfl, aarch64: Demangle return addresses using a PAC mask

Steve Capper (2):
  libdwfl, aarch64: extend dwfl_thread_state_registers to handle PAC
  libdwfl, aarch64: Read PAC mask from core

 backends/aarch64_corenote.c | 17 +++--
 backends/aarch64_initreg.c  | 12 
 backends/aarch64_regs.c |  5 -
 libdw/cfi.c | 14 +-
 libdw/cfi.h |  5 +
 libdwfl/dwfl_frame.c|  3 +++
 libdwfl/dwfl_frame_regs.c   |  6 ++
 libdwfl/frame_unwind.c  | 14 +-
 libdwfl/libdwflP.h  |  6 ++
 libdwfl/linux-core-attach.c | 34 ++
 libdwfl/linux-pid-attach.c  |  9 +++--
 tests/run-addrcfi.sh|  1 +
 tests/run-allregs.sh|  1 +
 13 files changed, 120 insertions(+), 7 deletions(-)

-- 
2.43.0



[PATCH v2 1/5] aarch64: Create definitions for AARCH64_RA_SIGN_STATE register

2024-08-26 Thread Steve Capper
From: German Gomez 

This register will be used to indicate whether a return address is
mangled with a PAC or not, in accordance with the DWARF AARCH64 ABI [1].

[1] 
https://github.com/ARM-software/abi-aa/blob/main/aadwarf64/aadwarf64.rst#41dwarf-register-names

Signed-off-by: German Gomez 
[SteveC: move DW_AARCH64_RA_SIGN_STATE to cfi.h, fix comments]
Signed-off-by: Steve Capper 
---
 backends/aarch64_initreg.c | 2 ++
 backends/aarch64_regs.c| 5 -
 libdw/cfi.h| 5 +
 tests/run-addrcfi.sh   | 1 +
 tests/run-allregs.sh   | 1 +
 5 files changed, 13 insertions(+), 1 deletion(-)

diff --git a/backends/aarch64_initreg.c b/backends/aarch64_initreg.c
index daf6f375..4661068a 100644
--- a/backends/aarch64_initreg.c
+++ b/backends/aarch64_initreg.c
@@ -73,6 +73,8 @@ aarch64_set_initial_registers_tid (pid_t tid __attribute__ 
((unused)),
 
   /* ELR cannot be found.  */
 
+  /* RA_SIGN_STATE cannot be found */
+
   /* FP registers (only 64bits are used).  */
   struct user_fpsimd_struct fregs;
   iovec.iov_base = &fregs;
diff --git a/backends/aarch64_regs.c b/backends/aarch64_regs.c
index 23014bfc..e95ece37 100644
--- a/backends/aarch64_regs.c
+++ b/backends/aarch64_regs.c
@@ -87,7 +87,10 @@ aarch64_register_info (Ebl *ebl __attribute__ ((unused)),
 case 33:
   return regtype ("integer", DW_ATE_address, "elr");
 
-case 34 ... 63:
+case 34:
+  return regtype ("integer", DW_ATE_unsigned, "ra_sign_state");
+
+case 35 ... 63:
   return 0;
 
 case 64 ... 95:
diff --git a/libdw/cfi.h b/libdw/cfi.h
index bb9dc0df..d0134243 100644
--- a/libdw/cfi.h
+++ b/libdw/cfi.h
@@ -228,6 +228,11 @@ extern int __libdw_frame_at_address (Dwarf_CFI *cache, 
struct dwarf_fde *fde,
 { ((BYTE_ORDER == LITTLE_ENDIAN && e_ident[EI_DATA] == ELFDATA2MSB)   \
|| (BYTE_ORDER == BIG_ENDIAN && e_ident[EI_DATA] == ELFDATA2LSB)) }
 
+/* AARCH64 DWARF registers. */
+enum
+  {
+DW_AARCH64_RA_SIGN_STATE = 34
+  };
 
 INTDECL (dwarf_next_cfi)
 INTDECL (dwarf_getcfi)
diff --git a/tests/run-addrcfi.sh b/tests/run-addrcfi.sh
index 64fa24d7..ce9e753e 100755
--- a/tests/run-addrcfi.sh
+++ b/tests/run-addrcfi.sh
@@ -3639,6 +3639,7 @@ dwarf_cfi_addrframe (.eh_frame): no matching address range
integer reg30 (x30): same_value
integer reg31 (sp): location expression: call_frame_cfa stack_value
integer reg33 (elr): undefined
+   integer reg34 (ra_sign_state): undefined
FP/SIMD reg64 (v0): undefined
FP/SIMD reg65 (v1): undefined
FP/SIMD reg66 (v2): undefined
diff --git a/tests/run-allregs.sh b/tests/run-allregs.sh
index 87b16c95..ed086651 100755
--- a/tests/run-allregs.sh
+++ b/tests/run-allregs.sh
@@ -2693,6 +2693,7 @@ integer registers:
 30: x30 (x30), signed 64 bits
 31: sp (sp), address 64 bits
 33: elr (elr), address 64 bits
+34: ra_sign_state (ra_sign_state), unsigned 64 bits
 FP/SIMD registers:
 64: v0 (v0), unsigned 128 bits
 65: v1 (v1), unsigned 128 bits
-- 
2.43.0



[PATCH v2 2/5] libdw, aarch64: Implement DW_CFA_AARCH64_negate_ra_state CFI instruction

2024-08-26 Thread Steve Capper
From: German Gomez 

Implement DW_CFA_AARCH64_negate_ra_state in accordance with the DWARF
AARCH64 ABI [1].

Followup commits will use the value of this register to remove the PAC
from return addresses.

[1] 
https://github.com/ARM-software/abi-aa/blob/main/aadwarf64/aadwarf64.rst#44call-frame-instructions

Signed-off-by: German Gomez 
Signed-off-by: Steve Capper 
---
 libdw/cfi.c | 14 +-
 1 file changed, 13 insertions(+), 1 deletion(-)

diff --git a/libdw/cfi.c b/libdw/cfi.c
index a7174405..743bfc07 100644
--- a/libdw/cfi.c
+++ b/libdw/cfi.c
@@ -125,6 +125,15 @@ execute_cfi (Dwarf_CFI *cache,
 fs->regs[regno].value = (r_value); \
   } while (0)
 
+  /* The AARCH64 DWARF ABI states that register 34 (ra_sign_state) must
+ be initialized to 0. So do it before executing the CFI. */
+  if (cache->e_machine == EM_AARCH64)
+{
+  if (unlikely (! enough_registers (DW_AARCH64_RA_SIGN_STATE, &fs, 
&result)))
+   goto out;
+  fs->regs[DW_AARCH64_RA_SIGN_STATE].value = 0;
+}
+
   while (program < end)
 {
   uint8_t opcode = *program++;
@@ -357,7 +366,10 @@ execute_cfi (Dwarf_CFI *cache,
{
  /* Toggles the return address state, indicating whether
 the return address is encrypted or not on
-aarch64. XXX not handled yet.  */
+aarch64. */
+ if (unlikely (! enough_registers (DW_AARCH64_RA_SIGN_STATE, &fs, 
&result)))
+   goto out;
+ fs->regs[DW_AARCH64_RA_SIGN_STATE].value ^= 0x1;
}
  else
{
-- 
2.43.0



[PATCH v2 3/5] libdwfl, aarch64: Demangle return addresses using a PAC mask

2024-08-26 Thread Steve Capper
From: German Gomez 

Demangle mangled return addresses on AARCH64. The value of the masks is
stored in the struct Dwfl_Thread.

Signed-off-by: German Gomez 
[SteveC: remove dwfl_thread_state_aarch64_pauth]
Signed-off-by: Steve Capper 
---
 libdwfl/dwfl_frame.c   |  3 +++
 libdwfl/frame_unwind.c | 14 +-
 libdwfl/libdwflP.h |  6 ++
 3 files changed, 22 insertions(+), 1 deletion(-)

diff --git a/libdwfl/dwfl_frame.c b/libdwfl/dwfl_frame.c
index 5ee71dd4..8af8843f 100644
--- a/libdwfl/dwfl_frame.c
+++ b/libdwfl/dwfl_frame.c
@@ -269,6 +269,8 @@ dwfl_getthreads (Dwfl *dwfl, int (*callback) (Dwfl_Thread 
*thread, void *arg),
   thread.process = process;
   thread.unwound = NULL;
   thread.callbacks_arg = NULL;
+  thread.aarch64.pauth_insn_mask = 0;
+
   for (;;)
 {
   thread.tid = process->callbacks->next_thread (dwfl,
@@ -339,6 +341,7 @@ getthread (Dwfl *dwfl, pid_t tid,
   thread.process = process;
   thread.unwound = NULL;
   thread.callbacks_arg = NULL;
+  thread.aarch64.pauth_insn_mask = 0;
 
   if (process->callbacks->get_thread (dwfl, tid, process->callbacks_arg,
  &thread.callbacks_arg))
diff --git a/libdwfl/frame_unwind.c b/libdwfl/frame_unwind.c
index 1e2f0255..ab444d25 100644
--- a/libdwfl/frame_unwind.c
+++ b/libdwfl/frame_unwind.c
@@ -599,7 +599,19 @@ handle_cfi (Dwfl_Frame *state, Dwarf_Addr pc, Dwarf_CFI 
*cfi, Dwarf_Addr bias)
 
   /* Some architectures encode some extra info in the return address.  */
   if (regno == frame->fde->cie->return_address_register)
-   regval &= ebl_func_addr_mask (ebl);
+   {
+ regval &= ebl_func_addr_mask (ebl);
+
+ /* In aarch64, pseudo-register RA_SIGN_STATE indicates whether the
+return address needs demangling using the PAC mask from the
+thread. */
+ if (cfi->e_machine == EM_AARCH64 &&
+ frame->nregs > DW_AARCH64_RA_SIGN_STATE &&
+ frame->regs[DW_AARCH64_RA_SIGN_STATE].value & 0x1)
+   {
+ regval &= ~(state->thread->aarch64.pauth_insn_mask);
+   }
+   }
 
   /* This is another strange PPC[64] case.  There are two
 registers numbers that can represent the same DWARF return
diff --git a/libdwfl/libdwflP.h b/libdwfl/libdwflP.h
index e0055d65..d0a5f056 100644
--- a/libdwfl/libdwflP.h
+++ b/libdwfl/libdwflP.h
@@ -244,6 +244,12 @@ struct Dwfl_Thread
   /* Bottom (innermost) frame while we're initializing, NULL afterwards.  */
   Dwfl_Frame *unwound;
   void *callbacks_arg;
+
+  /* Data for handling AARCH64 (currently limited to demangling PAC from
+ return addresses). */
+  struct {
+Dwarf_Addr pauth_insn_mask;
+  } aarch64;
 };
 
 /* See its typedef in libdwfl.h.  */
-- 
2.43.0



[PATCH v2 5/5] libdwfl, aarch64: Read PAC mask from core

2024-08-26 Thread Steve Capper
We need to read the PAC mask from a core file when debugging offline
as the information is still needed to demangle return addresses.

This commit pulls out the NT_ARM_PAC_MASK info from the core and feeds
it through to dwfl_thread_state_registers for each thread.

Signed-off-by: Steve Capper 
---
 backends/aarch64_corenote.c | 17 +++--
 libdwfl/linux-core-attach.c | 34 ++
 2 files changed, 49 insertions(+), 2 deletions(-)

diff --git a/backends/aarch64_corenote.c b/backends/aarch64_corenote.c
index bd0a4a72..38b21de7 100644
--- a/backends/aarch64_corenote.c
+++ b/backends/aarch64_corenote.c
@@ -115,6 +115,18 @@ static const Ebl_Core_Item aarch64_mte_items [] =
 }
   };
 
+static const Ebl_Core_Item aarch64_pac_items [] =
+  {
+{
+  .name = "data_mask", .type = ELF_T_XWORD, .format = 'x',
+  .offset = 0, .group = "register"
+},
+{
+  .name = "insn_mask", .type = ELF_T_XWORD, .format = 'x',
+  .offset = 8, .group = "register"
+}
+  };
+
 #define AARCH64_HWBP_REG(KIND, N)  \
 {  \
   .name = "DBG" KIND "VR" #N "_EL1", .type = ELF_T_XWORD, .format = 'x', \
@@ -175,7 +187,8 @@ AARCH64_BP_WP_GROUP ("W", aarch64_hw_wp_items);
   EXTRA_ITEMS (NT_ARM_TLS, 8, aarch64_tls_items)   \
   EXTRA_ITEMS (NT_ARM_HW_BREAK, 264, aarch64_hw_bp_items)  \
   EXTRA_ITEMS (NT_ARM_HW_WATCH, 264, aarch64_hw_wp_items)  \
-  EXTRA_ITEMS (NT_ARM_SYSTEM_CALL, 4, aarch64_syscall_items) \
-  EXTRA_ITEMS (NT_ARM_TAGGED_ADDR_CTRL, 8, aarch64_mte_items)
+  EXTRA_ITEMS (NT_ARM_SYSTEM_CALL, 4, aarch64_syscall_items)   \
+  EXTRA_ITEMS (NT_ARM_TAGGED_ADDR_CTRL, 8, aarch64_mte_items)  \
+  EXTRA_ITEMS (NT_ARM_PAC_MASK, 16, aarch64_pac_items)
 
 #include "linux-core-note.c"
diff --git a/libdwfl/linux-core-attach.c b/libdwfl/linux-core-attach.c
index d6f9e971..75e3c219 100644
--- a/libdwfl/linux-core-attach.c
+++ b/libdwfl/linux-core-attach.c
@@ -289,6 +289,40 @@ core_set_initial_registers (Dwfl_Thread *thread, void 
*thread_arg_voidp)
  reg_desc += regloc->pad;
}
 }
+
+  /* look for any Pointer Authentication code masks on AArch64 machines */
+  GElf_Ehdr ehdr_mem;
+  GElf_Ehdr *ehdr = gelf_getehdr(core, &ehdr_mem);
+  if (ehdr && ehdr->e_machine == EM_AARCH64)
+  {
+while (offset < note_data->d_size
+   && (offset = gelf_getnote (note_data, offset,
+   &nhdr, &name_offset, &desc_offset)) > 0)
+{
+  if (nhdr.n_type != NT_ARM_PAC_MASK)
+continue;
+
+  name = (nhdr.n_namesz == 0 ? "" : note_data->d_buf + name_offset);
+  desc = note_data->d_buf + desc_offset;
+  core_note_err = ebl_core_note (core_arg->ebl, &nhdr, name, desc,
+  ®s_offset, &nregloc, ®locs,
+  &nitems, &items);
+  if (!core_note_err)
+break;
+
+  for (item = items; item < items + nitems; item++)
+if (strcmp(item->name, "insn_mask") == 0)
+  break;
+
+  if (item == items + nitems)
+continue;
+
+  uint64_t insn_mask = read_8ubyte_unaligned_noncvt(desc + item->offset);
+  INTUSE(dwfl_thread_state_registers)(thread, -2, 1, &insn_mask);
+  break;
+}
+  }
+
   return true;
 }
 
-- 
2.43.0



[PATCH v2 4/5] libdwfl, aarch64: extend dwfl_thread_state_registers to handle PAC

2024-08-26 Thread Steve Capper
On AArch64 systems with pointer authentication enabled, one needs to
know the PAC mask in order to unwind functions that employ PAC.

This patch extends dwfl_thread_state_registers to handle the PAC mask
information by introducing a special register -2. (-1 is used in a
similar manner already for handling the program counter).

The AArch64 linux process attach logic is also extended to query ptrace
for the PAC mask.

A subsequent patch will add support for retrieving the PAC mask from an
AArch64 linux core file.

Signed-off-by: Steve Capper 
---
 backends/aarch64_initreg.c | 10 ++
 libdwfl/dwfl_frame_regs.c  |  6 ++
 libdwfl/linux-pid-attach.c |  9 +++--
 3 files changed, 23 insertions(+), 2 deletions(-)

diff --git a/backends/aarch64_initreg.c b/backends/aarch64_initreg.c
index 4661068a..5ec45ea6 100644
--- a/backends/aarch64_initreg.c
+++ b/backends/aarch64_initreg.c
@@ -36,6 +36,7 @@
 # include 
 # include 
 # include 
+# include 
 /* Deal with old glibc defining user_pt_regs instead of user_regs_struct.  */
 # ifndef HAVE_SYS_USER_REGS
 #  define user_regs_struct user_pt_regs
@@ -57,12 +58,18 @@ aarch64_set_initial_registers_tid (pid_t tid __attribute__ 
((unused)),
 
   /* General registers.  */
   struct user_regs_struct gregs;
+  struct user_pac_mask pac_mask;
   struct iovec iovec;
   iovec.iov_base = &gregs;
   iovec.iov_len = sizeof (gregs);
   if (ptrace (PTRACE_GETREGSET, tid, NT_PRSTATUS, &iovec) != 0)
 return false;
 
+  iovec.iov_base = &pac_mask;
+  iovec.iov_len = sizeof (pac_mask);
+  if (ptrace (PTRACE_GETREGSET, tid, NT_ARM_PAC_MASK, &iovec) != 0)
+pac_mask.insn_mask = 0;
+
   /* X0..X30 plus SP.  */
   if (! setfunc (0, 32, (Dwarf_Word *) &gregs.regs[0], arg))
 return false;
@@ -71,6 +78,9 @@ aarch64_set_initial_registers_tid (pid_t tid __attribute__ 
((unused)),
   if (! setfunc (-1, 1, (Dwarf_Word *) &gregs.pc, arg))
 return false;
 
+  if (! setfunc (-2, 1, (Dwarf_Word *) &pac_mask.insn_mask, arg))
+return false;
+
   /* ELR cannot be found.  */
 
   /* RA_SIGN_STATE cannot be found */
diff --git a/libdwfl/dwfl_frame_regs.c b/libdwfl/dwfl_frame_regs.c
index a4bd3884..572ac676 100644
--- a/libdwfl/dwfl_frame_regs.c
+++ b/libdwfl/dwfl_frame_regs.c
@@ -39,6 +39,12 @@ dwfl_thread_state_registers (Dwfl_Thread *thread, int 
firstreg,
   Dwfl_Frame *state = thread->unwound;
   assert (state && state->unwound == NULL);
   assert (state->initial_frame);
+
+  if (firstreg == -2 && nregs == 1) {
+thread->aarch64.pauth_insn_mask = regs[0];
+return true;
+  }
+
   for (unsigned regno = firstreg; regno < firstreg + nregs; regno++)
 if (! __libdwfl_frame_reg_set (state, regno, regs[regno - firstreg]))
   {
diff --git a/libdwfl/linux-pid-attach.c b/libdwfl/linux-pid-attach.c
index de867857..0eec1e88 100644
--- a/libdwfl/linux-pid-attach.c
+++ b/libdwfl/linux-pid-attach.c
@@ -309,13 +309,18 @@ pid_thread_state_registers_cb (int firstreg, unsigned 
nregs,
   const Dwarf_Word *regs, void *arg)
 {
   Dwfl_Thread *thread = (Dwfl_Thread *) arg;
-  if (firstreg < 0)
+  if (firstreg == -1)
 {
-  assert (firstreg == -1);
   assert (nregs == 1);
   INTUSE(dwfl_thread_state_register_pc) (thread, *regs);
   return true;
 }
+  else if (firstreg == -2)
+{
+  assert (nregs == 1);
+  INTUSE(dwfl_thread_state_registers) (thread, firstreg, nregs, regs);
+  return true;
+ }
   assert (nregs > 0);
   return INTUSE(dwfl_thread_state_registers) (thread, firstreg, nregs, regs);
 }
-- 
2.43.0