[aarch64] Enable pointer authentication support for aarch64 bare metal/kernel mode addresses
At the moment GDB only handles pointer authentication (pauth) for userspace addresses and if we're debugging a Linux-hosted program. The Linux Kernel can be configured to use pauth instructions for some additional security hardening, but GDB doesn't handle this well. To overcome this limitation, GDB needs a couple things: 1 - The target needs to advertise pauth support. 2 - The hook to remove non-address bits from a pointer needs to be registered in aarch64-tdep.c as opposed to aarch64-linux-tdep.c. There is a patch for QEMU that addresses the first point, and it makes QEMU's gdbstub expose a couple more pauth mask registers, so overall we will have up to 4 pauth masks (2 masks or 4 masks): pauth_dmask pauth_cmask pauth_dmask_high pauth_cmask_high pauth_dmask and pauth_cmask are the masks used to remove pauth signatures from userspace addresses. pauth_dmask_high and pauth_cmask_high masks are used to remove pauth signatures from kernel addresses. The second point is easily addressed by moving code around. When debugging a Linux Kernel built with pauth with an unpatched GDB, we get the following backtrace: #0 __fput (file=0xffff0000c17a6400) at /repos/linux/fs/file_table.c:296 #1 0xffff8000082bd1f0 in ____fput (work=<optimized out>) at /repos/linux/fs/file_table.c:348 #2 0x30008000080ade30 [PAC] in ?? () #3 0x30d48000080ade30 in ?? () Backtrace stopped: previous frame identical to this frame (corrupt stack?) With a patched GDB, we get something a lot more meaningful: #0 __fput (file=0xffff0000c1bcfa00) at /repos/linux/fs/file_table.c:296 #1 0xffff8000082bd1f0 in ____fput (work=<optimized out>) at /repos/linux/fs/file_table.c:348 #2 0xffff8000080ade30 [PAC] in task_work_run () at /repos/linux/kernel/task_work.c:179 #3 0xffff80000801db90 [PAC] in resume_user_mode_work (regs=0xffff80000a96beb0) at /repos/linux/include/linux/resume_user_mode.h:49 #4 do_notify_resume (regs=regs@entry=0xffff80000a96beb0, thread_flags=4) at /repos/linux/arch/arm64/kernel/signal.c:1127 #5 0xffff800008fb9974 [PAC] in prepare_exit_to_user_mode (regs=0xffff80000a96beb0) at /repos/linux/arch/arm64/kernel/entry-common.c:137 #6 exit_to_user_mode (regs=0xffff80000a96beb0) at /repos/linux/arch/arm64/kernel/entry-common.c:142 #7 el0_svc (regs=0xffff80000a96beb0) at /repos/linux/arch/arm64/kernel/entry-common.c:638 #8 0xffff800008fb9d34 [PAC] in el0t_64_sync_handler (regs=<optimized out>) at /repos/linux/arch/arm64/kernel/entry-common.c:655 #9 0xffff800008011548 [PAC] in el0t_64_sync () at /repos/linux/arch/arm64/kernel/entry.S:586 Backtrace stopped: Cannot access memory at address 0xffff80000a96c0c8
This commit is contained in:
parent
fd7f7424f5
commit
6d0020873d
4 changed files with 100 additions and 51 deletions
|
@ -1980,40 +1980,6 @@ aarch64_linux_decode_memtag_section (struct gdbarch *gdbarch,
|
|||
return tags;
|
||||
}
|
||||
|
||||
/* AArch64 implementation of the remove_non_address_bits gdbarch hook. Remove
|
||||
non address bits from a pointer value. */
|
||||
|
||||
static CORE_ADDR
|
||||
aarch64_remove_non_address_bits (struct gdbarch *gdbarch, CORE_ADDR pointer)
|
||||
{
|
||||
aarch64_gdbarch_tdep *tdep = gdbarch_tdep<aarch64_gdbarch_tdep> (gdbarch);
|
||||
|
||||
/* By default, we assume TBI and discard the top 8 bits plus the VA range
|
||||
select bit (55). */
|
||||
CORE_ADDR mask = AARCH64_TOP_BITS_MASK;
|
||||
|
||||
if (tdep->has_pauth ())
|
||||
{
|
||||
/* Fetch the PAC masks. These masks are per-process, so we can just
|
||||
fetch data from whatever thread we have at the moment.
|
||||
|
||||
Also, we have both a code mask and a data mask. For now they are the
|
||||
same, but this may change in the future. */
|
||||
struct regcache *regs = get_current_regcache ();
|
||||
CORE_ADDR cmask, dmask;
|
||||
|
||||
if (regs->cooked_read (tdep->pauth_reg_base, &dmask) != REG_VALID)
|
||||
dmask = mask;
|
||||
|
||||
if (regs->cooked_read (tdep->pauth_reg_base + 1, &cmask) != REG_VALID)
|
||||
cmask = mask;
|
||||
|
||||
mask |= aarch64_mask_from_pac_registers (cmask, dmask);
|
||||
}
|
||||
|
||||
return aarch64_remove_top_bits (pointer, mask);
|
||||
}
|
||||
|
||||
static void
|
||||
aarch64_linux_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch)
|
||||
{
|
||||
|
@ -2066,12 +2032,6 @@ aarch64_linux_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch)
|
|||
/* Syscall record. */
|
||||
tdep->aarch64_syscall_record = aarch64_linux_syscall_record;
|
||||
|
||||
/* The top byte of a user space address known as the "tag",
|
||||
is ignored by the kernel and can be regarded as additional
|
||||
data associated with the address. */
|
||||
set_gdbarch_remove_non_address_bits (gdbarch,
|
||||
aarch64_remove_non_address_bits);
|
||||
|
||||
/* MTE-specific settings and hooks. */
|
||||
if (tdep->has_mte ())
|
||||
{
|
||||
|
|
|
@ -137,10 +137,14 @@ static const char *const aarch64_sve_register_names[] =
|
|||
|
||||
static const char *const aarch64_pauth_register_names[] =
|
||||
{
|
||||
/* Authentication mask for data pointer. */
|
||||
/* Authentication mask for data pointer, low half/user pointers. */
|
||||
"pauth_dmask",
|
||||
/* Authentication mask for code pointer. */
|
||||
"pauth_cmask"
|
||||
/* Authentication mask for code pointer, low half/user pointers. */
|
||||
"pauth_cmask",
|
||||
/* Authentication mask for data pointer, high half / kernel pointers. */
|
||||
"pauth_dmask_high",
|
||||
/* Authentication mask for code pointer, high half / kernel pointers. */
|
||||
"pauth_cmask_high"
|
||||
};
|
||||
|
||||
static const char *const aarch64_mte_register_names[] =
|
||||
|
@ -228,9 +232,19 @@ aarch64_frame_unmask_lr (aarch64_gdbarch_tdep *tdep,
|
|||
&& frame_unwind_register_unsigned (this_frame,
|
||||
tdep->ra_sign_state_regnum))
|
||||
{
|
||||
int cmask_num = AARCH64_PAUTH_CMASK_REGNUM (tdep->pauth_reg_base);
|
||||
CORE_ADDR cmask = frame_unwind_register_unsigned (this_frame, cmask_num);
|
||||
addr = addr & ~cmask;
|
||||
/* VA range select (bit 55) tells us whether to use the low half masks
|
||||
or the high half masks. */
|
||||
int cmask_num;
|
||||
if (tdep->pauth_reg_count > 2 && addr & VA_RANGE_SELECT_BIT_MASK)
|
||||
cmask_num = AARCH64_PAUTH_CMASK_HIGH_REGNUM (tdep->pauth_reg_base);
|
||||
else
|
||||
cmask_num = AARCH64_PAUTH_CMASK_REGNUM (tdep->pauth_reg_base);
|
||||
|
||||
/* By default, we assume TBI and discard the top 8 bits plus the VA range
|
||||
select bit (55). */
|
||||
CORE_ADDR mask = AARCH64_TOP_BITS_MASK;
|
||||
mask |= frame_unwind_register_unsigned (this_frame, cmask_num);
|
||||
addr = aarch64_remove_top_bits (addr, mask);
|
||||
|
||||
/* Record in the frame that the link register required unmasking. */
|
||||
set_frame_previous_pc_masked (this_frame);
|
||||
|
@ -1326,8 +1340,8 @@ aarch64_dwarf2_frame_init_reg (struct gdbarch *gdbarch, int regnum,
|
|||
reg->loc.exp.len = 1;
|
||||
return;
|
||||
}
|
||||
else if (regnum == AARCH64_PAUTH_DMASK_REGNUM (tdep->pauth_reg_base)
|
||||
|| regnum == AARCH64_PAUTH_CMASK_REGNUM (tdep->pauth_reg_base))
|
||||
else if (regnum >= tdep->pauth_reg_base
|
||||
&& regnum < tdep->pauth_reg_base + tdep->pauth_reg_count)
|
||||
{
|
||||
reg->how = DWARF2_FRAME_REG_SAME_VALUE;
|
||||
return;
|
||||
|
@ -3507,8 +3521,8 @@ aarch64_cannot_store_register (struct gdbarch *gdbarch, int regnum)
|
|||
return 0;
|
||||
|
||||
/* Pointer authentication registers are read-only. */
|
||||
return (regnum == AARCH64_PAUTH_DMASK_REGNUM (tdep->pauth_reg_base)
|
||||
|| regnum == AARCH64_PAUTH_CMASK_REGNUM (tdep->pauth_reg_base));
|
||||
return (regnum >= tdep->pauth_reg_base
|
||||
&& regnum < tdep->pauth_reg_base + tdep->pauth_reg_count);
|
||||
}
|
||||
|
||||
/* Implement the stack_frame_destroyed_p gdbarch method. */
|
||||
|
@ -3536,6 +3550,51 @@ aarch64_stack_frame_destroyed_p (struct gdbarch *gdbarch, CORE_ADDR pc)
|
|||
return streq (inst.opcode->name, "ret");
|
||||
}
|
||||
|
||||
/* AArch64 implementation of the remove_non_address_bits gdbarch hook. Remove
|
||||
non address bits from a pointer value. */
|
||||
|
||||
static CORE_ADDR
|
||||
aarch64_remove_non_address_bits (struct gdbarch *gdbarch, CORE_ADDR pointer)
|
||||
{
|
||||
aarch64_gdbarch_tdep *tdep = gdbarch_tdep<aarch64_gdbarch_tdep> (gdbarch);
|
||||
|
||||
/* By default, we assume TBI and discard the top 8 bits plus the VA range
|
||||
select bit (55). */
|
||||
CORE_ADDR mask = AARCH64_TOP_BITS_MASK;
|
||||
|
||||
if (tdep->has_pauth ())
|
||||
{
|
||||
/* Fetch the PAC masks. These masks are per-process, so we can just
|
||||
fetch data from whatever thread we have at the moment.
|
||||
|
||||
Also, we have both a code mask and a data mask. For now they are the
|
||||
same, but this may change in the future. */
|
||||
struct regcache *regs = get_current_regcache ();
|
||||
CORE_ADDR cmask, dmask;
|
||||
int dmask_regnum = AARCH64_PAUTH_DMASK_REGNUM (tdep->pauth_reg_base);
|
||||
int cmask_regnum = AARCH64_PAUTH_CMASK_REGNUM (tdep->pauth_reg_base);
|
||||
|
||||
/* If we have a kernel address and we have kernel-mode address mask
|
||||
registers, use those instead. */
|
||||
if (tdep->pauth_reg_count > 2
|
||||
&& pointer & VA_RANGE_SELECT_BIT_MASK)
|
||||
{
|
||||
dmask_regnum = AARCH64_PAUTH_DMASK_HIGH_REGNUM (tdep->pauth_reg_base);
|
||||
cmask_regnum = AARCH64_PAUTH_CMASK_HIGH_REGNUM (tdep->pauth_reg_base);
|
||||
}
|
||||
|
||||
if (regs->cooked_read (dmask_regnum, &dmask) != REG_VALID)
|
||||
dmask = mask;
|
||||
|
||||
if (regs->cooked_read (cmask_regnum, &cmask) != REG_VALID)
|
||||
cmask = mask;
|
||||
|
||||
mask |= aarch64_mask_from_pac_registers (cmask, dmask);
|
||||
}
|
||||
|
||||
return aarch64_remove_top_bits (pointer, mask);
|
||||
}
|
||||
|
||||
/* Initialize the current architecture based on INFO. If possible,
|
||||
re-use an architecture from ARCHES, which is a list of
|
||||
architectures already created during this debugging session.
|
||||
|
@ -3674,19 +3733,35 @@ aarch64_gdbarch_init (struct gdbarch_info info, struct gdbarch_list *arches)
|
|||
}
|
||||
|
||||
/* Add the pauth registers. */
|
||||
int pauth_masks = 0;
|
||||
if (feature_pauth != NULL)
|
||||
{
|
||||
first_pauth_regnum = num_regs;
|
||||
ra_sign_state_offset = num_pseudo_regs;
|
||||
|
||||
/* Size of the expected register set with all 4 masks. */
|
||||
int set_size = ARRAY_SIZE (aarch64_pauth_register_names);
|
||||
|
||||
/* QEMU exposes a couple additional masks for the high half of the
|
||||
address. We should either have 2 registers or 4 registers. */
|
||||
if (tdesc_unnumbered_register (feature_pauth,
|
||||
"pauth_dmask_high") == 0)
|
||||
{
|
||||
/* We did not find pauth_dmask_high, assume we only have
|
||||
2 masks. We are not dealing with QEMU/Emulators then. */
|
||||
set_size -= 2;
|
||||
}
|
||||
|
||||
/* Validate the descriptor provides the mandatory PAUTH registers and
|
||||
allocate their numbers. */
|
||||
for (i = 0; i < ARRAY_SIZE (aarch64_pauth_register_names); i++)
|
||||
for (i = 0; i < set_size; i++)
|
||||
valid_p &= tdesc_numbered_register (feature_pauth, tdesc_data.get (),
|
||||
first_pauth_regnum + i,
|
||||
aarch64_pauth_register_names[i]);
|
||||
|
||||
num_regs += i;
|
||||
num_pseudo_regs += 1; /* Count RA_STATE pseudo register. */
|
||||
pauth_masks = set_size;
|
||||
}
|
||||
|
||||
/* Add the MTE registers. */
|
||||
|
@ -3722,6 +3797,7 @@ aarch64_gdbarch_init (struct gdbarch_info info, struct gdbarch_list *arches)
|
|||
tdep->jb_elt_size = 8;
|
||||
tdep->vq = vq;
|
||||
tdep->pauth_reg_base = first_pauth_regnum;
|
||||
tdep->pauth_reg_count = pauth_masks;
|
||||
tdep->ra_sign_state_regnum = -1;
|
||||
tdep->mte_reg_base = first_mte_regnum;
|
||||
tdep->tls_regnum_base = first_tls_regnum;
|
||||
|
@ -3838,6 +3914,11 @@ aarch64_gdbarch_init (struct gdbarch_info info, struct gdbarch_list *arches)
|
|||
if (tdep->has_pauth ())
|
||||
tdep->ra_sign_state_regnum = ra_sign_state_offset + num_regs;
|
||||
|
||||
/* Architecture hook to remove bits of a pointer that are not part of the
|
||||
address, like memory tags (MTE) and pointer authentication signatures. */
|
||||
set_gdbarch_remove_non_address_bits (gdbarch,
|
||||
aarch64_remove_non_address_bits);
|
||||
|
||||
/* Add standard register aliases. */
|
||||
for (i = 0; i < ARRAY_SIZE (aarch64_register_aliases); i++)
|
||||
user_reg_add (gdbarch, aarch64_register_aliases[i].name,
|
||||
|
|
|
@ -94,6 +94,8 @@ struct aarch64_gdbarch_tdep : gdbarch_tdep_base
|
|||
}
|
||||
|
||||
int pauth_reg_base = 0;
|
||||
/* Number of pauth masks. */
|
||||
int pauth_reg_count = 0;
|
||||
int ra_sign_state_regnum = 0;
|
||||
|
||||
/* Returns true if the target supports pauth. */
|
||||
|
|
|
@ -132,6 +132,12 @@ enum aarch64_regnum
|
|||
|
||||
#define AARCH64_PAUTH_DMASK_REGNUM(pauth_reg_base) (pauth_reg_base)
|
||||
#define AARCH64_PAUTH_CMASK_REGNUM(pauth_reg_base) (pauth_reg_base + 1)
|
||||
/* The high versions of these masks are used for bare metal/kernel-mode pointer
|
||||
authentication support. */
|
||||
#define AARCH64_PAUTH_DMASK_HIGH_REGNUM(pauth_reg_base) (pauth_reg_base + 2)
|
||||
#define AARCH64_PAUTH_CMASK_HIGH_REGNUM(pauth_reg_base) (pauth_reg_base + 3)
|
||||
|
||||
/* This size is only meant for Linux, not bare metal. QEMU exposes 4 masks. */
|
||||
#define AARCH64_PAUTH_REGS_SIZE (16)
|
||||
|
||||
#define AARCH64_X_REGS_NUM 31
|
||||
|
|
Loading…
Add table
Reference in a new issue