[PATCH] AArch64 pauth: Indicate unmasked addresses in backtrace

Alan Hayward
2019-07-17T08:14:47Z

Armv8.3-a Pointer Authentication causes the function return address to be
obfuscated on entry to some functions. GDB must unmask the link register in
order to produce a backtrace.

The following patch adds markers of <unmasked> to the bracktrace, to indicate
which addresses needed unmasking.

For example, consider the following backtrace:

(gdb) bt
0 0x0000000000400490 in puts@plt ()
1 0x00000000004005dc in foo ("hello") at cbreak-lib.c:6
2 0x0000000000400604<unmasked> in bar () at cbreak-lib.c:12
3 0x0000000000400620<unmasked> in barbar () at cbreak.c:17
4 0x00000000004005b4 in main () at cbreak-3.c:10

The functions in the cbreak-lib use pointer auth, obfuscating the return address
to the previous function. The caused the addresses of bar and barbar to require
unmasking in order to unwind the backtrace.

Alternatively, I considered replacing <unmasked> with a single chracter, such
as * for brevity reasons, but felt this would be non obvious for the user.

An extra bool is added alongside the prev_pc in the frame struture. At the
point at which the link register is unmasked, the AArch64 port calls into frame
to sets the bool. This seemed the most efficient way of doing it.

aarch64_frame_unmask_address is designed to work for any address, however at
the momoment it is only used for the link address. An extra default parameter
is added to ensure it only caches for the link register.

I expect this will potentially cause issues with some tests in the testsuite
when Armv8.3 pointer authentication is used. This should be fixed up in the
the future once real hardware is available for full testsuite testing.

2019-07-17 Alan Hayward <alan.hayward@arm.com>

* NEWS: Expand the Pointer Authentication entry.
* aarch64-tdep.c (aarch64_frame_unmask_address): Mark frame if
unmasking link register.
* frame.c (struct frame_info): Add masked variable.
(frame_set_previous_pc_masked) (frame_get_pc_masked): New function.
* frame.h (frame_set_previous_pc_masked) (frame_get_pc_masked): New
declaration.
* stack.c (print_frame): Check for masked pc.
---
gdb/NEWS | 3 ++-
gdb/aarch64-tdep.c | 12 +++++++++---
gdb/frame.c | 17 +++++++++++++++++
gdb/frame.h | 9 +++++++++
gdb/stack.c | 6 +++++-
5 files changed, 42 insertions(+), 5 deletions(-)

diff --git a/gdb/NEWS b/gdb/NEWS
index 4e479bf738..cea88491bd 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -16,7 +16,8 @@
architectures require kernel changes. TLS is not yet supported for
amd64 and i386 process core dumps.

-* Support for Pointer Authentication on AArch64 Linux.
+* Support for Pointer Authentication on AArch64 Linux. Return addresses that
+ required unmasking are shown in the backtrace with the postfix <masked>.

* Two new convenience functions $_cimag and $_creal that extract the
imaginary and real parts respectively from complex numbers.
diff --git a/gdb/aarch64-tdep.c b/gdb/aarch64-tdep.c
index e23cc3f956..eaac7960ec 100644
--- a/gdb/aarch64-tdep.c
+++ b/gdb/aarch64-tdep.c
@@ -251,12 +251,13 @@ class instruction_reader : public abstract_instruction_reader
} // namespace

/* If address signing is enabled, mask off the signature bits from ADDR, using
- the register values in THIS_FRAME. */
+ the register values in THIS_FRAME. IS_LR indicates if ADDR is from the link
+ register for the current frame. */

static CORE_ADDR
aarch64_frame_unmask_address (struct gdbarch_tdep *tdep,
- struct frame_info *this_frame,
- CORE_ADDR addr)
+ struct frame_info *this_frame, CORE_ADDR addr,
+ bool is_lr = true)
{
if (tdep->has_pauth ()
&& frame_unwind_register_unsigned (this_frame,
@@ -265,6 +266,11 @@ aarch64_frame_unmask_address (struct gdbarch_tdep *tdep,
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;
+
+ /* If addr was taken from the Link Register, then record this in the
+ frame. */
+ if (is_lr)
+ frame_set_previous_pc_masked (this_frame);
}

return addr;
diff --git a/gdb/frame.c b/gdb/frame.c
index 84e0397db9..b7bb44986e 100644
--- a/gdb/frame.c
+++ b/gdb/frame.c
@@ -124,6 +124,8 @@ struct frame_info
struct {
enum cached_copy_status status;
CORE_ADDR value;
+ /* Did VALUE require unmasking when being read. */
+ bool masked;
} prev_pc;

/* Cached copy of the previous frame's function address. */
@@ -161,6 +163,21 @@ struct frame_info
const char *stop_string;
};

+/* See frame.h. */
+
+void
+frame_set_previous_pc_masked (struct frame_info *frame)
+{
+ frame->prev_pc.masked = true;
+}
+
+/* See frame.h. */
+
+bool frame_get_pc_masked (struct frame_info *frame)
+{
+ return frame->next->prev_pc.masked;
+}
+
/* A frame stash used to speed up frame lookups. Create a hash table
to stash frames previously accessed from the frame cache for
quicker subsequent retrieval. The hash table is emptied whenever
diff --git a/gdb/frame.h b/gdb/frame.h
index a79eeeeab1..61c12ec0b2 100644
--- a/gdb/frame.h
+++ b/gdb/frame.h
@@ -930,4 +930,13 @@ extern const gdb::option::option_def set_backtrace_option_defs[2];
/* The values behind the global "set backtrace ..." settings. */
extern set_backtrace_options user_set_backtrace_options;

+/* Mark that the PC value is masked for the previous frame. */
+
+extern void frame_set_previous_pc_masked (struct frame_info *frame);
+
+/* Get whether the PC value is masked for the given frame. */
+
+extern bool frame_get_pc_masked (struct frame_info *frame);
+
+
#endif /* !defined (FRAME_H) */
diff --git a/gdb/stack.c b/gdb/stack.c
index 9b1d1a6856..cef1c29a82 100644
--- a/gdb/stack.c
+++ b/gdb/stack.c
@@ -1254,7 +1254,11 @@ print_frame (const frame_print_options &fp_opts,
{
annotate_frame_address ();
if (pc_p)
- uiout->field_core_addr ("addr", gdbarch, pc);
+ {
+ uiout->field_core_addr ("addr", gdbarch, pc);
+ if (frame_get_pc_masked (frame))
+ uiout->text ("<unmasked>");
+ }
else
uiout->field_string ("addr", "<unavailable>",
ui_out_style_kind::ADDRESS);
--
2.20.1 (Apple Git-117)