Skip to content

Commit

Permalink
gdb, gdbserver: Add support of Intel shadow stack pointer register.
Browse files Browse the repository at this point in the history
This patch adds the user mode register PL3_SSP which is part of the
Intel(R) Control-Flow Enforcement Technology (CET) feature for support
of shadow stack.
For now, only native and remote debugging for amd64 architecture on linux
are covered by this patch.

This patch requires fixing the test gdb.base/inline-frame-cycle-unwind
which is failing in case the shadow stack pointer is unavailable.
Such a state is possible if shadow stack is disabled for the currrent thread
but supported by HW.

This test uses the Python unwinder inline-frame-cycle-unwind.py which fakes
the cyclic stack cycle by reading the pending frame's registers and adding
them to the unwinder:

~~~
for reg in pending_frame.architecture().registers("general"):
     val = pending_frame.read_register(reg)
     unwinder.add_saved_register(reg, val)
     return unwinder
~~~

However, in case the python unwinder is used we add a register (pl3_ssp) that is
unavailable.  This leads to a NOT_AVAILABLE_ERROR caught in
gdb/frame-unwind.c:frame_unwind_try_unwinder and it is continued with standard
unwinders.  This destroys the faked cyclic behavior and the stack is
further unwinded after frame 5.

In the working scenario an error should be triggered:
~~~
bt
0  inline_func () at /tmp/gdb.base/inline-frame-cycle-unwind.c:49^M
1  normal_func () at /tmp/gdb.base/inline-frame-cycle-unwind.c:32^M
2  0x000055555555516e in inline_func () at /tmp/gdb.base/inline-frame-cycle-unwind.c:45^M
3  normal_func () at /tmp/gdb.base/inline-frame-cycle-unwind.c:32^M
4  0x000055555555516e in inline_func () at /tmp/gdb.base/inline-frame-cycle-unwind.c:45^M
5  normal_func () at /tmp/gdb.base/inline-frame-cycle-unwind.c:32^M
Backtrace stopped: previous frame identical to this frame (corrupt stack?)
(gdb) PASS: gdb.base/inline-frame-cycle-unwind.exp: cycle at level 5: backtrace when the unwind is broken at frame 5
~~~

To fix the Python unwinder, we simply skip the unavailable registers.

Co-Authored-By: Christina Schimpe <christina.schimpe@intel.com>
  • Loading branch information
2 people authored and felix-willgerodt committed Mar 1, 2024
1 parent 29cf855 commit afff428
Show file tree
Hide file tree
Showing 27 changed files with 378 additions and 17 deletions.
18 changes: 18 additions & 0 deletions gdb/amd64-linux-nat.c
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
#include "amd64-tdep.h"
#include "amd64-linux-tdep.h"
#include "i386-linux-tdep.h"
#include "x86-tdep.h"
#include "gdbsupport/x86-xstate.h"

#include "x86-linux-nat.h"
Expand Down Expand Up @@ -85,6 +86,7 @@ static int amd64_linux_gregset32_reg_offset[] =
-1, -1, -1, -1, -1, -1, -1, -1, /* k0 ... k7 (AVX512) */
-1, -1, -1, -1, -1, -1, -1, -1, /* zmm0 ... zmm7 (AVX512) */
-1, /* PKEYS register PKRU */
-1, /* CET user mode register PL3_SSP. */
-1, /* TILECFG register (AMX). */
-1, /* TILEDATA registers tmm0 ... tmm7 (AMX). */
ORIG_RAX * 8 /* "orig_eax" */
Expand Down Expand Up @@ -238,6 +240,14 @@ amd64_linux_nat_target::fetch_registers (struct regcache *regcache, int regnum)

if (have_ptrace_getregset == TRIBOOL_TRUE)
{
if ((regnum == -1 && tdep->ssp_regnum > 0)
|| (regnum != -1 && regnum == tdep->ssp_regnum))
{
x86_linux_fetch_ssp (regcache, tid);
if (regnum != -1)
return;
}

char xstateregs[tdep->xsave_layout.sizeof_xsave];
struct iovec iov;

Expand Down Expand Up @@ -303,6 +313,14 @@ amd64_linux_nat_target::store_registers (struct regcache *regcache, int regnum)

if (have_ptrace_getregset == TRIBOOL_TRUE)
{
if ((regnum == -1 && tdep->ssp_regnum > 0)
|| (regnum != -1 && regnum == tdep->ssp_regnum))
{
x86_linux_store_ssp (regcache, tid);
if (regnum != -1)
return;
}

char xstateregs[tdep->xsave_layout.sizeof_xsave];
struct iovec iov;

Expand Down
12 changes: 8 additions & 4 deletions gdb/amd64-linux-tdep.c
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ int amd64_linux_gregset_reg_offset[] =
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, /* PKEYS register pkru */
-1, /* CET user mode register PL3_SSP. */
-1, /* TILECFG register (AMX). */
-1, /* TILEDATA registers tmm0 ... tmm7 (AMX). */

Expand Down Expand Up @@ -1611,10 +1612,12 @@ amd64_linux_record_signal (struct gdbarch *gdbarch,
}

const target_desc *
amd64_linux_read_description (uint64_t xcr0_features_bit, bool is_x32)
amd64_linux_read_description (uint64_t xcr0_features_bit, bool is_x32,
bool ssp_enabled)
{
static target_desc *amd64_linux_tdescs \
[2/*AVX*/][2/*MPX*/][2/*AVX512*/][2/*PKRU*/][2/*AMX*/] = {};
[2/*AVX*/][2/*MPX*/][2/*AVX512*/][2/*PKRU*/][2/*PL3_SSP*/]\
[2/*AMX*/] = {};
static target_desc *x32_linux_tdescs \
[2/*AVX*/][2/*AVX512*/][2/*PKRU*/][2/*AMX*/] = {};

Expand All @@ -1633,12 +1636,13 @@ amd64_linux_read_description (uint64_t xcr0_features_bit, bool is_x32)
[(xcr0_features_bit & X86_XSTATE_MPX) ? 1 : 0]
[(xcr0_features_bit & X86_XSTATE_AVX512) ? 1 : 0]
[(xcr0_features_bit & X86_XSTATE_PKRU) ? 1 : 0]
[ssp_enabled ? 1 : 0]
[(xcr0_features_bit & X86_XSTATE_AMX) ? 1 : 0];
}

if (*tdesc == NULL)
*tdesc = amd64_create_target_description (xcr0_features_bit, is_x32,
true, true);
*tdesc = amd64_create_target_description (xcr0_features_bit, is_x32, true,
true, ssp_enabled);

return *tdesc;
}
Expand Down
3 changes: 2 additions & 1 deletion gdb/amd64-linux-tdep.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ extern struct target_desc *tdesc_x32_avx_avx512_linux;
XCR0_FEATURES_BIT and IS_X32. */

const target_desc *amd64_linux_read_description (uint64_t xcr0_features_bit,
bool is_x32);
bool is_x32,
bool ssp_enabled = false);

/* Enum that defines the syscall identifiers for amd64 linux.
Used for process record/replay, these will be translated into
Expand Down
3 changes: 3 additions & 0 deletions gdb/amd64-tdep.c
Original file line number Diff line number Diff line change
Expand Up @@ -3388,6 +3388,9 @@ amd64_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch,
tdep->num_pkeys_regs = 1;
}

if (tdesc_find_feature (tdesc, "org.gnu.gdb.i386.pl3_ssp") != nullptr)
tdep->ssp_regnum = AMD64_PL3_SSP_REGNUM;

if (tdesc_find_feature (tdesc, "org.gnu.gdb.i386.amx") != nullptr)
{
tdep->tilecfg_raw_register_names = amd64_tilecfg_raw_names;
Expand Down
1 change: 1 addition & 0 deletions gdb/amd64-tdep.h
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ enum amd64_regnum
AMD64_ZMM0H_REGNUM,
AMD64_ZMM31H_REGNUM = AMD64_ZMM0H_REGNUM + 31,
AMD64_PKRU_REGNUM,
AMD64_PL3_SSP_REGNUM,
AMD64_AMX_TILECFG_RAW_REGNUM,
AMD64_AMX_TILEDATA_REGNUM,
AMD64_FSBASE_REGNUM,
Expand Down
10 changes: 8 additions & 2 deletions gdb/arch/amd64.c
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,20 @@
#include "../features/i386/64bit-segments.c"
#include "../features/i386/64bit-sse.c"
#include "../features/i386/pkeys.c"
#include "../features/i386/64bit-ssp.c"

#include "../features/i386/x32-core.c"

/* Create amd64 target descriptions according to XCR0. If IS_X32 is
true, create the x32 ones. If IS_LINUX is true, create target
descriptions for Linux. If SEGMENTS is true, then include
the "org.gnu.gdb.i386.segments" feature registers. */
the "org.gnu.gdb.i386.segments" feature registers. If SSP_ENABLED is
true include the "org.gnu.gdb.i386.pl3_ssp" register.
*/

target_desc *
amd64_create_target_description (uint64_t xcr0, bool is_x32, bool is_linux,
bool segments)
bool segments, bool ssp_enabled)
{
target_desc_up tdesc = allocate_target_description ();

Expand Down Expand Up @@ -76,6 +79,9 @@ amd64_create_target_description (uint64_t xcr0, bool is_x32, bool is_linux,
if (xcr0 & X86_XSTATE_PKRU)
regnum = create_feature_i386_pkeys (tdesc.get (), regnum);

if (ssp_enabled && !is_x32)
regnum = create_feature_i386_64bit_ssp (tdesc.get (), regnum);

if (xcr0 & X86_XSTATE_AMX)
regnum = create_feature_i386_64bit_amx (tdesc.get (), regnum);

Expand Down
3 changes: 2 additions & 1 deletion gdb/arch/amd64.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include <stdint.h>

target_desc *amd64_create_target_description (uint64_t xcr0, bool is_x32,
bool is_linux, bool segments);
bool is_linux, bool segments,
bool ssp_enabled = false);

#endif /* ARCH_AMD64_H */
4 changes: 4 additions & 0 deletions gdb/doc/gdb.texinfo
Original file line number Diff line number Diff line change
Expand Up @@ -50007,6 +50007,10 @@ describe one config user mode register @samp{tilecfg_raw} that has
64 bytes and one @samp{tiledata} register that has 8192 bytes.
All AMX registers are valid only for amd64.

The @samp{org.gnu.gdb.i386.pl3_ssp} feature is optional. It should describe the
user mode register @samp{pl3_ssp} which has 64 bits on amd64. Following the
restriction of the Linux kernel, only amd64 is supported for now.

@node LoongArch Features
@subsection LoongArch Features
@cindex target descriptions, LoongArch Features
Expand Down
1 change: 1 addition & 0 deletions gdb/features/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,7 @@ FEATURE_XMLFILES = aarch64-core.xml \
i386/64bit-linux.xml \
i386/64bit-sse.xml \
i386/pkeys.xml \
i386/64bit-ssp.xml \
i386/x32-core.xml \
loongarch/base32.xml \
loongarch/base64.xml \
Expand Down
14 changes: 14 additions & 0 deletions gdb/features/i386/64bit-ssp.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/* THIS FILE IS GENERATED. -*- buffer-read-only: t -*- vi:set ro:
Original: 64bit-ssp.xml */

#include "gdbsupport/tdesc.h"

static int
create_feature_i386_64bit_ssp (struct target_desc *result, long regnum)
{
struct tdesc_feature *feature;

feature = tdesc_create_feature (result, "org.gnu.gdb.i386.pl3_ssp");
tdesc_create_reg (feature, "pl3_ssp", regnum++, 1, NULL, 64, "data_ptr");
return regnum;
}
11 changes: 11 additions & 0 deletions gdb/features/i386/64bit-ssp.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?xml version="1.0"?>
<!-- Copyright (C) 2022-2024 Free Software Foundation, Inc.
Copying and distribution of this file, with or without modification,
are permitted in any medium without royalty provided the copyright
notice and this notice are preserved. -->

<!DOCTYPE feature SYSTEM "gdb-target.dtd">
<feature name="org.gnu.gdb.i386.pl3_ssp">
<reg name="pl3_ssp" bitsize="64" type="data_ptr"/>
</feature>
1 change: 1 addition & 0 deletions gdb/i386-linux-tdep.c
Original file line number Diff line number Diff line change
Expand Up @@ -611,6 +611,7 @@ int i386_linux_gregset_reg_offset[] =
-1, -1, -1, -1, -1, -1, -1, -1, /* k0 ... k7 (AVX512) */
-1, -1, -1, -1, -1, -1, -1, -1, /* zmm0 ... zmm7 (AVX512) */
-1, /* PKRU register */
-1, /* CET user mode register PL3_SSP. */
-1, /* AMX register TILECFG. */
-1, /* AMX TILEDATA registers: tmm0 ... tmm7. */
11 * 4, /* "orig_eax" */
Expand Down
17 changes: 16 additions & 1 deletion gdb/i386-tdep.c
Original file line number Diff line number Diff line change
Expand Up @@ -8661,7 +8661,7 @@ i386_validate_tdesc_p (i386_gdbarch_tdep *tdep,

const struct tdesc_feature *feature_sse, *feature_avx, *feature_mpx,
*feature_avx512, *feature_pkeys, *feature_segments,
*feature_amx;
*feature_pl3_ssp, *feature_amx;
int i, num_regs, valid_p;

if (! tdesc_has_registers (tdesc))
Expand Down Expand Up @@ -8690,6 +8690,9 @@ i386_validate_tdesc_p (i386_gdbarch_tdep *tdep,
/* Try PKEYS */
feature_pkeys = tdesc_find_feature (tdesc, "org.gnu.gdb.i386.pkeys");

/* Try Shadow Stack. */
feature_pl3_ssp = tdesc_find_feature (tdesc, "org.gnu.gdb.i386.pl3_ssp");

/* Try AMX. */
feature_amx = tdesc_find_feature (tdesc, "org.gnu.gdb.i386.amx");

Expand Down Expand Up @@ -8825,6 +8828,15 @@ i386_validate_tdesc_p (i386_gdbarch_tdep *tdep,
tdep->pkeys_register_names[i]);
}

if (feature_pl3_ssp != nullptr)
{
if (tdep->ssp_regnum < 0)
tdep->ssp_regnum = I386_PL3_SSP_REGNUM;

valid_p &= tdesc_numbered_register (feature_pl3_ssp, tdesc_data,
tdep->ssp_regnum, "pl3_ssp");
}

if (feature_amx != nullptr)
{
tdep->xcr0 |= X86_XSTATE_TILECFG;
Expand Down Expand Up @@ -9151,6 +9163,9 @@ i386_gdbarch_init (struct gdbarch_info info, struct gdbarch_list *arches)
/* No segment base registers. */
tdep->fsbase_regnum = -1;

/* No shadow stack pointer register. */
tdep->ssp_regnum = -1;

/* No AMX registers. */
tdep->tilecfg_regnum = -1;
tdep->num_tilecfg_regs = 0;
Expand Down
4 changes: 4 additions & 0 deletions gdb/i386-tdep.h
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,9 @@ struct i386_gdbarch_tdep : gdbarch_tdep_base
/* PKEYS register names. */
const char * const *pkeys_register_names = nullptr;

/* Shadow stack pointer register. */
int ssp_regnum = 0;

/* Register number for AMX tilecfg register, including pseudo register. */
int tilecfg_regnum = 0;
int tilecfg_raw_regnum = 0;
Expand Down Expand Up @@ -338,6 +341,7 @@ enum i386_regnum
I386_ZMM0H_REGNUM, /* %zmm0h */
I386_ZMM7H_REGNUM = I386_ZMM0H_REGNUM + 7,
I386_PKRU_REGNUM,
I386_PL3_SSP_REGNUM,
I386_AMX_TILECFG_RAW_REGNUM,
I386_AMX_TILEDATA_REGNUM,
I386_FSBASE_REGNUM,
Expand Down
34 changes: 34 additions & 0 deletions gdb/nat/x86-linux.c
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,12 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */

#include "elf/common.h"
#include "gdbsupport/common-defs.h"
#include "nat/gdb_ptrace.h"
#include "nat/linux-ptrace.h"
#include "nat/x86-cpuid.h"
#include <sys/uio.h>
#include "x86-linux.h"
#include "x86-linux-dregs.h"

Expand Down Expand Up @@ -80,3 +85,32 @@ x86_linux_prepare_to_resume (struct lwp_info *lwp)
{
x86_linux_update_debug_registers (lwp);
}

/* See nat/x86-linux.h. */

bool
x86_check_ssp_support (const int tid)
{
unsigned int eax, ebx, ecx, edx;

__get_cpuid_count (7, 0, &eax, &ebx, &ecx, &edx);

if ((ecx & bit_SHSTK) == 0)
return false;

/* Further check for NT_X86_SHSTK kernel support. */
uint64_t ssp;
iovec iov;
iov.iov_base = &ssp;
iov.iov_len = sizeof (ssp);

int res = ptrace (PTRACE_GETREGSET, tid, NT_X86_SHSTK, &iov);
if (res < 0 && errno == EINVAL)
{
/* The errno EINVAL for a PTRACE_GETREGSET call indicates that
kernel support is not available. */
return false;
}

return true;
}
4 changes: 4 additions & 0 deletions gdb/nat/x86-linux.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,8 @@ extern void x86_linux_delete_thread (struct arch_lwp_info *arch_lwp);

extern void x86_linux_prepare_to_resume (struct lwp_info *lwp);

/* Check shadow stack hardware and kernel support. */

extern bool x86_check_ssp_support (const int tid);

#endif /* NAT_X86_LINUX_H */
22 changes: 22 additions & 0 deletions gdb/testsuite/gdb.arch/amd64-shadow-stack.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/* This testcase is part of GDB, the GNU debugger.
Copyright 2018-2024 Free Software Foundation, Inc.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */

int
main ()
{
return 0;
}
35 changes: 35 additions & 0 deletions gdb/testsuite/gdb.arch/amd64-ssp.exp
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Copyright 2018-2024 Free Software Foundation, Inc.

# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

# Test accessing the shadow stack pointer register.

require allow_ssp_tests

standard_testfile amd64-shadow-stack.c
if { [prepare_for_testing "failed to prepare" ${testfile} ${srcfile} \
additional_flags="-fcf-protection=return"] } {
return -1
}

if {![runto_main]} {
return -1
}

# Read PL3_SSP register.
gdb_test "print /x \$pl3_ssp" "= $hex" "read pl3_ssp value"

# Restore/write PL3_SSP register.
gdb_test "print /x \$pl3_ssp = 0x12345678" "= 0x12345678" "set pl3_ssp value"
gdb_test "print /x \$pl3_ssp" "= 0x12345678" "read pl3_ssp value after setting"
4 changes: 4 additions & 0 deletions gdb/testsuite/gdb.base/inline-frame-cycle-unwind.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ def __call__(self, pending_frame):

for reg in pending_frame.architecture().registers("general"):
val = pending_frame.read_register(reg)
# Having unavailable registers leads to a fall back to the standard
# unwinders. Don't add unavailable registers to avoid this.
if (str (val) == "<unavailable>"):
continue
unwinder.add_saved_register(reg, val)
return unwinder

Expand Down
Loading

0 comments on commit afff428

Please sign in to comment.