mirror of
https://gcc.gnu.org/git/gcc.git
synced 2024-11-27 05:44:15 +08:00
aarch64: store signing key and signing method in DWARF _Unwind_FrameState
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.
This commit is contained in:
parent
cdb9aa0f62
commit
f531673917
@ -25,52 +25,145 @@ 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 struct dwarf_fde *fde = _Unwind_Find_FDE (context->bases.func,
|
||||
&context->bases);
|
||||
if (fde != NULL)
|
||||
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 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)
|
||||
{
|
||||
/* /!\ Mixing DW_CFA_val_expression with DW_CFA_AARCH64_negate_ra_state will
|
||||
result in undefined behavior (likely an unwinding failure), as the
|
||||
chronology of the DWARF directives will be broken. */
|
||||
gcc_assert (fs->regs.how[AARCH64_DWARF_REGNUM_RA_STATE] == REG_ARCHEXT);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/* 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)
|
||||
{
|
||||
/* By default, DW_CFA_AARCH64_negate_ra_state assumes key A is being used
|
||||
for signing. This can be overridden by adding 'B' to the augmentation
|
||||
string. */
|
||||
fs->regs.signing_key = AARCH64_PAUTH_KEY_A;
|
||||
|
||||
/* All registers are initially in state REG_UNSAVED, which indicates that
|
||||
they inherit register values from the previous frame. However, the
|
||||
return address starts every frame in the "unsigned" state. It also
|
||||
starts every frame in a state that supports the original toggle-based
|
||||
DW_CFA_AARCH64_negate_ra_state method of controlling RA signing. */
|
||||
fs->regs.how[AARCH64_DWARF_REGNUM_RA_STATE] = 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. */
|
||||
on it using CFA of current frame.
|
||||
|
||||
Note: when DW_CFA_val_expression is used, FS only records the location of the
|
||||
associated CFI program, rather than the value of the expression itself.
|
||||
The CFI program is executed by uw_update_context when updating the context,
|
||||
so the value of the expression must be taken from CONTEXT rather than FS. */
|
||||
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;
|
||||
const int reg = AARCH64_DWARF_REGNUM_RA_STATE;
|
||||
|
||||
if (fs->regs.how[reg] == REG_UNSAVED)
|
||||
return addr;
|
||||
/* In libgcc, REG_ARCHEXT means that the RA state register was set by an
|
||||
AArch64 DWARF instruction and contains a valid value, or is used to
|
||||
describe the initial state set in aarch64_arch_extension_frame_init.
|
||||
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);
|
||||
|
||||
/* 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);
|
||||
}
|
||||
|
@ -271,23 +271,25 @@
|
||||
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. */
|
||||
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 +297,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);
|
||||
|
@ -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:
|
||||
/* 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. */
|
||||
case REG_ARCHEXT:
|
||||
break;
|
||||
|
||||
case REG_SAVED_OFFSET:
|
||||
|
@ -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: this information has 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. */
|
||||
|
Loading…
Reference in New Issue
Block a user