[PATCH 0/5] Enable PAC support in elfutils
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
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.
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
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
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
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
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
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
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
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
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
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
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
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