This patch is only a refactoring of the existing implementation of PAuth and returned-address signing. The existing behavior is preserved.
_Unwind_FrameState already contains several CIE and FDE information (see the attributes below the comment "The information we care about from the CIE/FDE" in libgcc/unwind-dw2.h). The patch aims at moving the information from DWARF CIE (signing key stored in the augmentation string) and FDE (the used signing method) into _Unwind_FrameState along the already-stored CIE and FDE information. Note: those information have to be saved in frame_state_reg_info instead of _Unwind_FrameState as they need to be savable by DW_CFA_remember_state and restorable by DW_CFA_restore_state, that both rely on the attribute "prev". Those new information in _Unwind_FrameState simplifies the look-up of the signing key when the return address is demangled. It also allows future signing methods to be easily added. _Unwind_FrameState is not a part of the public API of libunwind, so the change is backward compatible. A new architecture-specific handler MD_ARCH_EXTENSION_FRAME_INIT allows to reset values (if needed) in the frame state and unwind context before changing the frame state to the caller context. A new architecture-specific handler MD_ARCH_EXTENSION_CIE_AUG_HANDLER isolates the architecture-specific augmentation strings in AArch64 backend, and allows others architectures to reuse augmentation strings that would have clashed with AArch64 DWARF extensions. aarch64_demangle_return_addr, DW_CFA_AARCH64_negate_ra_state and DW_CFA_val_expression cases in libgcc/unwind-dw2-execute_cfa.h were documented to clarify where the value of the RA state register is stored (FS and CONTEXT respectively). libgcc/ChangeLog: * config/aarch64/aarch64-unwind.h (AARCH64_DWARF_RA_STATE_MASK): The mask for RA state register. (aarch64_RA_signing_method_t): The diversifiers used to sign a function's return address. (aarch64_pointer_auth_key): The key used to sign a function's return address. (aarch64_cie_signed_with_b_key): Deleted as the signing key is available now in _Unwind_FrameState. (MD_ARCH_EXTENSION_CIE_AUG_HANDLER): New CIE augmentation string handler for architecture extensions. (MD_ARCH_EXTENSION_FRAME_INIT): New architecture-extension initialization routine for DWARF frame state and context before execution of DWARF instructions. (aarch64_context_RA_state_get): Read RA state register from CONTEXT. (aarch64_RA_state_get): Read RA state register from FS. (aarch64_RA_state_set): Write RA state register into FS. (aarch64_RA_state_toggle): Toggle RA state register in FS. (aarch64_cie_aug_handler): Handler AArch64 augmentation strings. (aarch64_arch_extension_frame_init): Initialize defaults for the signing key (PAUTH_KEY_A), and RA state register (RA_no_signing). (aarch64_demangle_return_addr): Rely on the frame registers and the signing_key attribute in _Unwind_FrameState. * unwind-dw2-execute_cfa.h: Use the right alias DW_CFA_AARCH64_negate_ra_state for __aarch64__ instead of DW_CFA_GNU_window_save. (DW_CFA_AARCH64_negate_ra_state): Save the signing method in RA state register. Toggle RA state register without resetting 'how' to REG_UNSAVED. * unwind-dw2.c: (extract_cie_info): Save the signing key in the current _Unwind_FrameState while parsing the augmentation data. (uw_frame_state_for): Reset some attributes related to architecture extensions in _Unwind_FrameState. (uw_update_context): Move authentication code to AArch64 unwinding. * unwind-dw2.h (enum register_rule): Give a name to the existing enum for the register rules, and replace 'unsigned char' by 'enum register_rule' to facilitate debugging in GDB. (_Unwind_FrameState): Add a new architecture-extension attribute to store the signing key. --- libgcc/config/aarch64/aarch64-unwind.h | 154 ++++++++++++++++++++----- libgcc/unwind-dw2-execute_cfa.h | 34 ++++-- libgcc/unwind-dw2.c | 19 ++- libgcc/unwind-dw2.h | 17 ++- 4 files changed, 175 insertions(+), 49 deletions(-) diff --git a/libgcc/config/aarch64/aarch64-unwind.h b/libgcc/config/aarch64/aarch64-unwind.h index daf96624b5e..cc225a7e207 100644 --- a/libgcc/config/aarch64/aarch64-unwind.h +++ b/libgcc/config/aarch64/aarch64-unwind.h @@ -25,55 +25,155 @@ see the files COPYING3 and COPYING.RUNTIME respectively. If not, see #if !defined (AARCH64_UNWIND_H) && !defined (__ILP32__) #define AARCH64_UNWIND_H -#define DWARF_REGNUM_AARCH64_RA_STATE 34 +#include "ansidecl.h" +#include <stdbool.h> + +#define AARCH64_DWARF_REGNUM_RA_STATE 34 +#define AARCH64_DWARF_RA_STATE_MASK 0x1 + +/* The diversifiers used to sign a function's return address. */ +typedef enum +{ + AARCH64_RA_no_signing = 0x0, + AARCH64_RA_signing_SP = 0x1, +} __attribute__((packed)) aarch64_RA_signing_method_t; + +/* The key used to sign a function's return address. */ +typedef enum { + AARCH64_PAUTH_KEY_A, + AARCH64_PAUTH_KEY_B, +} __attribute__((packed)) aarch64_pointer_auth_key; + +#define MD_ARCH_EXTENSION_CIE_AUG_HANDLER(fs, aug) \ + aarch64_cie_aug_handler(fs, aug) + +#define MD_ARCH_EXTENSION_FRAME_INIT(context, fs) \ + aarch64_arch_extension_frame_init(context, fs) #define MD_DEMANGLE_RETURN_ADDR(context, fs, addr) \ aarch64_demangle_return_addr (context, fs, addr) -static inline int -aarch64_cie_signed_with_b_key (struct _Unwind_Context *context) +static inline aarch64_RA_signing_method_t +aarch64_context_RA_state_get(struct _Unwind_Context *context) +{ + const int index = AARCH64_DWARF_REGNUM_RA_STATE; + return _Unwind_GetGR (context, index) & AARCH64_DWARF_RA_STATE_MASK; +} + +static inline aarch64_RA_signing_method_t +aarch64_fs_RA_state_get(_Unwind_FrameState const* fs) { - const struct dwarf_fde *fde = _Unwind_Find_FDE (context->bases.func, - &context->bases); - if (fde != NULL) + const int index = AARCH64_DWARF_REGNUM_RA_STATE; + return fs->regs.reg[index].loc.offset & AARCH64_DWARF_RA_STATE_MASK; +} + +static inline void +aarch64_fs_RA_state_set(_Unwind_FrameState *fs, + aarch64_RA_signing_method_t signing_method) +{ + fs->regs.reg[AARCH64_DWARF_REGNUM_RA_STATE].loc.offset = signing_method; +} + +static inline void +aarch64_fs_RA_state_toggle(_Unwind_FrameState *fs) +{ + aarch64_RA_signing_method_t signing_method = aarch64_fs_RA_state_get(fs); + gcc_assert (signing_method == AARCH64_RA_no_signing || + signing_method == AARCH64_RA_signing_SP); + aarch64_fs_RA_state_set(fs, (signing_method == AARCH64_RA_no_signing) + ? AARCH64_RA_signing_SP + : AARCH64_RA_no_signing); +} + +/* CIE handler for custom augmentation string. */ +static inline bool +aarch64_cie_aug_handler(_Unwind_FrameState *fs, unsigned char aug) +{ + // AArch64 B-key pointer authentication. + if (aug == 'B') { - const struct dwarf_cie *cie = get_cie (fde); - if (cie != NULL) - { - const unsigned char *aug_str = cie->augmentation; - return __builtin_strchr ((const char *) aug_str, - 'B') == NULL ? 0 : 1; - } + fs->regs.signing_key = AARCH64_PAUTH_KEY_B; + return true; } - return 0; + return false; } -/* Do AArch64 private extraction on ADDR_WORD based on context info CONTEXT and - unwind frame info FS. If ADDR_WORD is signed, we do address authentication - on it using CFA of current frame. */ +/* At the entrance of a new frame, some cached information from the CIE/FDE, + * and registers values related to architectural extensions require a default + * initialization. + * If any of those values related to architecture extensions had to be saved + * for the next frame, it should be done via the architecture extensions handler + * MD_FROB_UPDATE_CONTEXT in uw_update_context_1 (libgcc/unwind-dw2.c). */ +static inline void +aarch64_arch_extension_frame_init (struct _Unwind_Context *context ATTRIBUTE_UNUSED, + _Unwind_FrameState *fs) +{ + // Reset previously cached CIE/FDE information. + fs->regs.signing_key = AARCH64_PAUTH_KEY_A; + + // Reset some registers. + // Note: the associated fs->how table is automatically reset to REG_UNSAVED in + // uw_frame_state_for (libgcc/unwind-dw2.c). REG_UNSAVED means that whatever + // was in the previous context is the current register value. In other words, + // the next context inherits by default the previous value for a register. + // To keep this logic coherent with some architecture extensions, we need to + // reinitialize fs->how for some registers to REG_ARCHEXT. + const int reg = AARCH64_DWARF_REGNUM_RA_STATE; + fs->regs.how[reg] = REG_ARCHEXT; + aarch64_fs_RA_state_set(fs, AARCH64_RA_no_signing); +} +/* Do AArch64 private extraction on ADDR_WORD based on context info CONTEXT and + unwind frame info FS. If ADDR_WORD is signed, we do address authentication + on it using CFA of current frame. + Note: this function is called after uw_update_context_1 in uw_update_context. + uw_update_context_1 is the function in which val expression are computed. Thus + if DW_CFA_val_expression is used, the value of the RA state register is stored + in CONTEXT, not FS. (more details about when DW_CFA_val_expression is used in + the next comment below) */ static inline void * aarch64_demangle_return_addr (struct _Unwind_Context *context, _Unwind_FrameState *fs, _Unwind_Word addr_word) { void *addr = (void *)addr_word; - const int reg = DWARF_REGNUM_AARCH64_RA_STATE; - - if (fs->regs.how[reg] == REG_UNSAVED) - return addr; + const int reg = AARCH64_DWARF_REGNUM_RA_STATE; + + // In libgcc, REG_ARCHEXT means that the RA state register was set by an AArch64 + // DWARF instruction and contains a valid value. + // Return-address signing state is normally toggled by DW_CFA_AARCH64_negate_ra_state + // (also knwon by its alias as DW_CFA_GNU_window_save). + // However, RA state register can be set directly via DW_CFA_val_expression + // too. GCC does not generate such CFI but some other compilers reportedly do. + // (see PR104689 for more details). + // Any other value than REG_ARCHEXT should be interpreted as if the RA state register + // is set by another DWARF instruction, and the value is fetchable via _Unwind_GetGR. + aarch64_RA_signing_method_t signing_method = AARCH64_RA_no_signing; + if (fs->regs.how[reg] == REG_ARCHEXT) + { + signing_method = aarch64_fs_RA_state_get(fs); + } + else if (fs->regs.how[reg] != REG_UNSAVED) + { + signing_method = aarch64_context_RA_state_get(context); + + // If the value was set in context, CONTEXT->by_value was set to 1. + // uw_install_context_1 contains an assert on by_value, to check that all + // registers values have been resolved before installing the context. + // This will make the unwinding crash if we don't reset CONTEXT->by_value, + // and CONTEXT->reg[reg]. + context->reg[reg] = _Unwind_Get_Unwind_Context_Reg_Val(0x0); + context->by_value[reg] = 0; + } - /* Return-address signing state is toggled by DW_CFA_GNU_window_save (where - REG_UNSAVED/REG_UNSAVED_ARCHEXT means RA signing is disabled/enabled), - or set by a DW_CFA_expression. */ - if (fs->regs.how[reg] == REG_UNSAVED_ARCHEXT - || (_Unwind_GetGR (context, reg) & 0x1) != 0) + if (signing_method == AARCH64_RA_signing_SP) { _Unwind_Word salt = (_Unwind_Word) context->cfa; - if (aarch64_cie_signed_with_b_key (context) != 0) + if (fs->regs.signing_key == AARCH64_PAUTH_KEY_B) return __builtin_aarch64_autib1716 (addr, salt); return __builtin_aarch64_autia1716 (addr, salt); } + // else {} // signing_method == AARCH64_RA_no_signing, nothing to do. return addr; } diff --git a/libgcc/unwind-dw2-execute_cfa.h b/libgcc/unwind-dw2-execute_cfa.h index a6b249f9ad6..2ea78c4ef8d 100644 --- a/libgcc/unwind-dw2-execute_cfa.h +++ b/libgcc/unwind-dw2-execute_cfa.h @@ -271,23 +271,33 @@ fs->regs.how[reg] = REG_SAVED_VAL_EXP; fs->regs.reg[reg].loc.exp = insn_ptr; } + // Don't execute the expression, but jump over it by adding + // DW_FORM_block's size to insn_ptr. insn_ptr = read_uleb128 (insn_ptr, &utmp); insn_ptr += utmp; break; - case DW_CFA_GNU_window_save: #if defined (__aarch64__) && !defined (__ILP32__) - /* This CFA is multiplexed with Sparc. On AArch64 it's used to toggle - return address signing status. REG_UNSAVED/REG_UNSAVED_ARCHEXT - mean RA signing is disabled/enabled. */ - reg = DWARF_REGNUM_AARCH64_RA_STATE; - gcc_assert (fs->regs.how[reg] == REG_UNSAVED - || fs->regs.how[reg] == REG_UNSAVED_ARCHEXT); - if (fs->regs.how[reg] == REG_UNSAVED) - fs->regs.how[reg] = REG_UNSAVED_ARCHEXT; - else - fs->regs.how[reg] = REG_UNSAVED; + case DW_CFA_AARCH64_negate_ra_state: + // This CFA is multiplexed with Sparc. + // On AArch64 it's used to toggle the status of return address signing + // with SP as a diversifier. + // - REG_ARCHEXT means that the RA state register in FS contains a + // valid value, and that no other DWARF directive has changed the value + // of this register. + // - any other value is not compatible with negating the RA state. + reg = AARCH64_DWARF_REGNUM_RA_STATE; + + // /!\ Mixing DW_CFA_val_expression with DW_CFA_AARCH64_negate_ra_state + // will result in an undefined behavior (likely an unwinding failure), + // as the chronology of the DWARF directives will be broken. + gcc_assert (fs->regs.how[reg] == REG_ARCHEXT); + + // Toggle current state + aarch64_fs_RA_state_toggle(fs); + break; #else + case DW_CFA_GNU_window_save: /* ??? Hardcoded for SPARC register window configuration. */ if (__LIBGCC_DWARF_FRAME_REGISTERS__ >= 32) for (reg = 16; reg < 32; ++reg) @@ -295,8 +305,8 @@ fs->regs.how[reg] = REG_SAVED_OFFSET; fs->regs.reg[reg].loc.offset = (reg - 16) * sizeof (void *); } -#endif break; +#endif case DW_CFA_GNU_args_size: insn_ptr = read_uleb128 (insn_ptr, &utmp); diff --git a/libgcc/unwind-dw2.c b/libgcc/unwind-dw2.c index 0849e89cd34..fb60853825d 100644 --- a/libgcc/unwind-dw2.c +++ b/libgcc/unwind-dw2.c @@ -501,11 +501,11 @@ extract_cie_info (const struct dwarf_cie *cie, struct _Unwind_Context *context, fs->signal_frame = 1; aug += 1; } - /* aarch64 B-key pointer authentication. */ - else if (aug[0] == 'B') - { - aug += 1; - } + +#if defined(MD_ARCH_EXTENSION_CIE_AUG_HANDLER) + else if (MD_ARCH_EXTENSION_CIE_AUG_HANDLER(fs, aug[0])) + aug += 1; +#endif /* Otherwise we have an unknown augmentation string. Bail unless we saw a 'z' prefix. */ @@ -996,6 +996,9 @@ uw_frame_state_for (struct _Unwind_Context *context, _Unwind_FrameState *fs) memset (&fs->regs.how[0], 0, sizeof (*fs) - offsetof (_Unwind_FrameState, regs.how[0])); +#if defined(MD_ARCH_EXTENSION_FRAME_INIT) + MD_ARCH_EXTENSION_FRAME_INIT(context, fs); +#endif context->args_size = 0; context->lsda = 0; @@ -1197,7 +1200,11 @@ uw_update_context_1 (struct _Unwind_Context *context, _Unwind_FrameState *fs) { case REG_UNSAVED: case REG_UNDEFINED: - case REG_UNSAVED_ARCHEXT: + case REG_ARCHEXT: // If the value depends on an augmenter, then there is + // no processing to do here, and the value computation + // should be delayed until the architecture handler + // computes the value correctly based on the augmenter + // information. break; case REG_SAVED_OFFSET: diff --git a/libgcc/unwind-dw2.h b/libgcc/unwind-dw2.h index 0dd8611f697..b75f4c65f98 100644 --- a/libgcc/unwind-dw2.h +++ b/libgcc/unwind-dw2.h @@ -22,16 +22,17 @@ see the files COPYING3 and COPYING.RUNTIME respectively. If not, see <http://www.gnu.org/licenses/>. */ -enum { +enum register_rule +{ REG_UNSAVED, REG_SAVED_OFFSET, REG_SAVED_REG, REG_SAVED_EXP, REG_SAVED_VAL_OFFSET, REG_SAVED_VAL_EXP, - REG_UNSAVED_ARCHEXT, /* Target specific extension. */ + REG_ARCHEXT, /* Target specific extension. */ REG_UNDEFINED -}; +} __attribute__((packed)); /* The result of interpreting the frame unwind info for a frame. This is all symbolic at this point, as none of the values can @@ -49,7 +50,7 @@ typedef struct const unsigned char *exp; } loc; } reg[__LIBGCC_DWARF_FRAME_REGISTERS__+1]; - unsigned char how[__LIBGCC_DWARF_FRAME_REGISTERS__+1]; + enum register_rule how[__LIBGCC_DWARF_FRAME_REGISTERS__+1]; enum { CFA_UNSET, @@ -65,6 +66,14 @@ typedef struct _Unwind_Sword cfa_offset; _Unwind_Word cfa_reg; const unsigned char *cfa_exp; + + /* Architecture extensions information from CIE/FDE. + * Note: those information have to be saved in struct frame_state_reg_info + * instead of _Unwind_FrameState as DW_CFA_restore_state has to be able to + * restore them. */ +#if defined(__aarch64__) && !defined (__ILP32__) + unsigned char signing_key; +#endif } regs; /* The PC described by the current frame state. */ -- 2.45.2