From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from simark.ca by simark.ca with LMTP id iKk4MYVcUWhd8hEAWB0awg (envelope-from ) for ; Tue, 17 Jun 2025 08:16:05 -0400 Authentication-Results: simark.ca; dkim=pass (2048-bit key; unprotected) header.d=intel.com header.i=@intel.com header.a=rsa-sha256 header.s=Intel header.b=W9yIpwvr; dkim-atps=neutral Received: by simark.ca (Postfix, from userid 112) id C58E81E11C; Tue, 17 Jun 2025 08:16:05 -0400 (EDT) X-Spam-Checker-Version: SpamAssassin 4.0.1 (2024-03-25) on simark.ca X-Spam-Level: X-Spam-Status: No, score=-10.1 required=5.0 tests=ARC_SIGNED,ARC_VALID, BAYES_00,DKIMWL_WL_HIGH,DKIM_SIGNED,DKIM_VALID,DKIM_VALID_AU, MAILING_LIST_MULTI,RCVD_IN_DNSWL_MED,RCVD_IN_VALIDITY_CERTIFIED, RCVD_IN_VALIDITY_RPBL,RCVD_IN_VALIDITY_SAFE autolearn=ham autolearn_force=no version=4.0.1 Received: from server2.sourceware.org (server2.sourceware.org [8.43.85.97]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (prime256v1) server-digest SHA256) (No client certificate requested) by simark.ca (Postfix) with ESMTPS id C5BFD1E089 for ; Tue, 17 Jun 2025 08:16:04 -0400 (EDT) Received: from server2.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 5DF9A38E9BE0 for ; Tue, 17 Jun 2025 12:16:04 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 5DF9A38E9BE0 Authentication-Results: sourceware.org; dkim=pass (2048-bit key, unprotected) header.d=intel.com header.i=@intel.com header.a=rsa-sha256 header.s=Intel header.b=W9yIpwvr Received: from mgamail.intel.com (mgamail.intel.com [198.175.65.19]) by sourceware.org (Postfix) with ESMTPS id 144863875DA5 for ; Tue, 17 Jun 2025 12:13:14 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 144863875DA5 Authentication-Results: sourceware.org; dmarc=pass (p=none dis=none) header.from=intel.com Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=intel.com ARC-Filter: OpenARC Filter v1.0.0 sourceware.org 144863875DA5 Authentication-Results: server2.sourceware.org; arc=none smtp.remote-ip=198.175.65.19 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1750162394; cv=none; b=BofPj6dUJdd1qMOwEmcNAuigeuaAhqW2JX3m45yytGKRmZwuoKo1f3Qoc/fzZRT3zKJ1lCcHRfqgjkabfNBAOp9U+hFoRL79xoDwu69/ZqSuXnIvuQkQhzGoLgTUf62w/q3d/SeArc77QBRLsnLknnLJUZFLQKZYLO5nozcqtNA= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1750162394; c=relaxed/simple; bh=XfeHuXRiPb7IIuiF9stmt880fVoXF3beXZ+Www7ja0U=; h=DKIM-Signature:From:To:Subject:Date:Message-Id:MIME-Version; b=gNimCiw9wW+FUwHFYJty2oczqXQG8k4h9i+ddJTFEy6sF33URxkuh8h6EUxZZ5TBi5NDOaFgL79asuWMmxseBDzY05OWzJMB3yJshVZXMqguB6uYrby+oJw/uVRBHHjqdGsi3Kfofe/VmvxiA9rCetzq8YQ45pn2ORe7MT3Q4uw= ARC-Authentication-Results: i=1; server2.sourceware.org DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 144863875DA5 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=intel.com; i=@intel.com; q=dns/txt; s=Intel; t=1750162394; x=1781698394; h=from:to:cc:subject:date:message-id:in-reply-to: references:mime-version:content-transfer-encoding; bh=XfeHuXRiPb7IIuiF9stmt880fVoXF3beXZ+Www7ja0U=; b=W9yIpwvrYJHXXmCdDm+aMriERqxi1WNQf1/s6N63Eq1pXA5CJgqlTWiG Gv9loQ11BonXjK0ihl5wUybj/4T9IGkjcyMlmx6Odc8yeoyAk9e73z1zF dUj4Z9vzY/gxqGceNSx70hG4ZL05lgqp2y/znqimKo/Enw/4zHC98EyQQ UdcOJ/H1SqbFGJfoN76Nhm8YU04c55eITntBo37FSGv3io3OPZaRTLF+1 htIveSn5C4jL6qFSOE2dKxIJNhnfwurAUtbdRCC+10pUxEi2z/cYkqZF4 v5rIcgR0mfG1MsOd5K649/4BF4NrRcxWnjeLzU2RWjXqZYtlZFUKImPOw Q==; X-CSE-ConnectionGUID: bJfPWQcGT2m+5t0tvfm3Aw== X-CSE-MsgGUID: IdzHk/WvSFyx+OHTkUwkag== X-IronPort-AV: E=McAfee;i="6800,10657,11467"; a="52206029" X-IronPort-AV: E=Sophos;i="6.16,243,1744095600"; d="scan'208";a="52206029" Received: from orviesa003.jf.intel.com ([10.64.159.143]) by orvoesa111.jf.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 17 Jun 2025 05:13:14 -0700 X-CSE-ConnectionGUID: i07yW6XuTWCgDvb73UpuSg== X-CSE-MsgGUID: PvLTnf12Rhi5ET4XaPFjLw== X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="6.16,243,1744095600"; d="scan'208";a="153527496" Received: from gkldtt-dev-004.igk.intel.com (HELO localhost) ([10.123.221.202]) by ORVIESA003-auth.jf.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 17 Jun 2025 05:13:12 -0700 From: Christina Schimpe To: gdb-patches@sourceware.org Cc: thiago.bauermann@linaro.org, eliz@gnu.org Subject: [PATCH v4 07/11] gdb: Handle shadow stack pointer register unwinding for amd64 linux. Date: Tue, 17 Jun 2025 12:11:43 +0000 Message-Id: <20250617121147.1956686-8-christina.schimpe@intel.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20250617121147.1956686-1-christina.schimpe@intel.com> References: <20250617121147.1956686-1-christina.schimpe@intel.com> MIME-Version: 1.0 Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit X-BeenThere: gdb-patches@sourceware.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: Gdb-patches mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: gdb-patches-bounces~public-inbox=simark.ca@sourceware.org Unwind the $pl3_ssp register. We now have an updated value for the shadow stack pointer when moving up or down the frame level. Note that $pl3_ssp can become unavailable when moving to a frame before the shadow stack enablement. In the example below, shadow stack is enabled in the function 'call1'. Thus, when moving to a frame level above the function, $pl3_ssp will become unavaiable. Following the restriction of the linux kernel, implement the unwinding for amd64 linux only. Before this patch: ~~~ Breakpoint 1, call2 (j=3) at sample.c:44 44 return 42; (gdb) p $pl3_ssp $1 = (void *) 0x7ffff79ffff8 (gdb) up 55 call2 (3); (gdb) p $pl3_ssp $2 = (void *) 0x7ffff79ffff8 (gdb) up 68 call1 (43); (gdb) p $pl3_ssp $3 = (void *) 0x7ffff79ffff8 ~~~ After this patch: ~~~ Breakpoint 1, call2 (j=3) at sample.c:44 44 return 42; (gdb) p $pl3_ssp $1 = (void *) 0x7ffff79ffff8 (gdb) up 55 call2 (3); (gdb) p $pl3_ssp $2 = (void *) 0x7ffff7a00000 (gdb) up 68 call1 (43i); (gdb) p $pl3_ssp $3 = ~~~ As we now have an updated value for each selected frame, the return command is now enabled for shadow stack enabled programs, too. We therefore add a test for the return command and shadow stack support, and for an updated shadow stack pointer after a frame level change. Reviewed-by: Thiago Jung Bauermann --- gdb/amd64-linux-tdep.c | 84 ++++++++++++++++++ gdb/linux-tdep.c | 47 ++++++++++ gdb/linux-tdep.h | 7 ++ .../gdb.arch/amd64-shadow-stack-cmds.exp | 88 +++++++++++++++++++ gdb/testsuite/gdb.arch/amd64-shadow-stack.c | 13 +++ 5 files changed, 239 insertions(+) create mode 100644 gdb/testsuite/gdb.arch/amd64-shadow-stack-cmds.exp diff --git a/gdb/amd64-linux-tdep.c b/gdb/amd64-linux-tdep.c index d806d3cb1f7..9436f0b190c 100644 --- a/gdb/amd64-linux-tdep.c +++ b/gdb/amd64-linux-tdep.c @@ -47,6 +47,8 @@ #include "arch/amd64-linux-tdesc.h" #include "inferior.h" #include "x86-tdep.h" +#include "dwarf2/frame.h" +#include "frame-unwind.h" /* The syscall's XML filename for i386. */ #define XML_SYSCALL_FILENAME_AMD64 "syscalls/amd64-linux.xml" @@ -1917,6 +1919,87 @@ amd64_linux_get_tls_dtv_addr (struct gdbarch *gdbarch, ptid_t ptid, return dtv_addr; } +/* Return the number of bytes required to update the shadow stack pointer + by one element. For x32 the shadow stack elements are still 64-bit + aligned. Thus, gdbarch_addr_bit cannot be used to compute the new + stack pointer. */ + +static inline int +amd64_linux_shadow_stack_element_size_aligned (gdbarch *gdbarch) +{ + const bfd_arch_info *binfo = gdbarch_bfd_arch_info (gdbarch); + return (binfo->bits_per_word / binfo->bits_per_byte); +} + + +/* Implement shadow stack pointer unwinding. For each new shadow stack + pointer check if its address is still in the shadow stack memory range. + If it's outside the range set the returned value to unavailable, + otherwise return a value containing the new shadow stack pointer. */ + +static value * +amd64_linux_dwarf2_prev_ssp (const frame_info_ptr &this_frame, + void **this_cache, int regnum) +{ + value *v = frame_unwind_got_register (this_frame, regnum, regnum); + gdb_assert (v != nullptr); + + gdbarch *gdbarch = get_frame_arch (this_frame); + + if (v->entirely_available () && !v->optimized_out ()) + { + int size = register_size (gdbarch, regnum); + bfd_endian byte_order = gdbarch_byte_order (gdbarch); + CORE_ADDR ssp = extract_unsigned_integer (v->contents_all ().data (), + size, byte_order); + + /* Starting with v6.6., the Linux kernel supports CET shadow stack. + Using /proc/PID/smaps we can only check if the current shadow + stack pointer SSP points to shadow stack memory. Only if this is + the case a valid previous shadow stack pointer can be + calculated. */ + std::pair range; + if (linux_address_in_shadow_stack_mem_range (ssp, &range)) + { + /* The shadow stack grows downwards. To compute the previous + shadow stack pointer, we need to increment SSP. */ + CORE_ADDR new_ssp + = ssp + amd64_linux_shadow_stack_element_size_aligned (gdbarch); + + /* If NEW_SSP points to the end of or before (<=) the current + shadow stack memory range we consider NEW_SSP as valid (but + empty). */ + if (new_ssp <= range.second) + return frame_unwind_got_address (this_frame, regnum, new_ssp); + } + } + + /* Return a value which is marked as unavailable in case we could not + calculate a valid previous shadow stack pointer. */ + value *retval + = value::allocate_register (get_next_frame_sentinel_okay (this_frame), + regnum, register_type (gdbarch, regnum)); + retval->mark_bytes_unavailable (0, retval->type ()->length ()); + return retval; +} + +/* Implement the "init_reg" dwarf2_frame_ops method. */ + +static void +amd64_init_reg (gdbarch *gdbarch, int regnum, dwarf2_frame_state_reg *reg, + const frame_info_ptr &this_frame) +{ + if (regnum == gdbarch_pc_regnum (gdbarch)) + reg->how = DWARF2_FRAME_REG_RA; + else if (regnum == gdbarch_sp_regnum (gdbarch)) + reg->how = DWARF2_FRAME_REG_CFA; + else if (regnum == AMD64_PL3_SSP_REGNUM) + { + reg->how = DWARF2_FRAME_REG_FN; + reg->loc.fn = amd64_linux_dwarf2_prev_ssp; + } +} + static void amd64_linux_init_abi_common(struct gdbarch_info info, struct gdbarch *gdbarch, int num_disp_step_buffers) @@ -1974,6 +2057,7 @@ amd64_linux_init_abi_common(struct gdbarch_info info, struct gdbarch *gdbarch, set_gdbarch_remove_non_address_bits_watchpoint (gdbarch, amd64_linux_remove_non_address_bits_watchpoint); + dwarf2_frame_set_init_reg (gdbarch, amd64_init_reg); } static void diff --git a/gdb/linux-tdep.c b/gdb/linux-tdep.c index 1e339b59e2e..c532b8758bf 100644 --- a/gdb/linux-tdep.c +++ b/gdb/linux-tdep.c @@ -47,6 +47,7 @@ #include "gdbsupport/unordered_map.h" #include +#include /* This enum represents the values that the user can choose when informing the Linux kernel about which memory mappings will be @@ -96,6 +97,10 @@ struct smaps_vmflags /* Memory map has memory tagging enabled. */ unsigned int memory_tagging : 1; + + /* Memory map used for shadow stack. */ + + unsigned int shadow_stack_memory : 1; }; /* Data structure that holds the information contained in the @@ -537,6 +542,8 @@ decode_vmflags (char *p, struct smaps_vmflags *v) v->shared_mapping = 1; else if (strcmp (s, "mt") == 0) v->memory_tagging = 1; + else if (strcmp (s, "ss") == 0) + v->shadow_stack_memory = 1; } } @@ -3036,6 +3043,46 @@ show_dump_excluded_mappings (struct ui_file *file, int from_tty, " flag is %s.\n"), value); } +/* See linux-tdep.h. */ + +bool +linux_address_in_shadow_stack_mem_range + (CORE_ADDR addr, std::pair *range) +{ + if (!target_has_execution () || current_inferior ()->fake_pid_p) + return false; + + const int pid = current_inferior ()->pid; + + std::string smaps_file = string_printf ("/proc/%d/smaps", pid); + + gdb::unique_xmalloc_ptr data + = target_fileio_read_stralloc (nullptr, smaps_file.c_str ()); + + if (data == nullptr) + return false; + + const std::vector smaps + = parse_smaps_data (data.get (), std::move (smaps_file)); + + auto find_addr_mem_range = [&addr] (const smaps_data &map) + { + bool addr_in_mem_range + = (addr >= map.start_address && addr < map.end_address); + return (addr_in_mem_range && map.vmflags.shadow_stack_memory); + }; + auto it = std::find_if (smaps.begin (), smaps.end (), find_addr_mem_range); + + if (it != smaps.end ()) + { + range->first = it->start_address; + range->second = it->end_address; + return true; + } + + return false; +} + /* To be called from the various GDB_OSABI_LINUX handlers for the various GNU/Linux architectures and machine types. diff --git a/gdb/linux-tdep.h b/gdb/linux-tdep.h index 7485fc132a6..7083635b976 100644 --- a/gdb/linux-tdep.h +++ b/gdb/linux-tdep.h @@ -117,4 +117,11 @@ extern CORE_ADDR linux_get_hwcap2 (); extern struct link_map_offsets *linux_ilp32_fetch_link_map_offsets (); extern struct link_map_offsets *linux_lp64_fetch_link_map_offsets (); +/* Returns true if ADDR belongs to a shadow stack memory range. If this + is the case, assign the shadow stack memory range to RANGE + [start_address, end_address). */ + +extern bool linux_address_in_shadow_stack_mem_range + (CORE_ADDR addr, std::pair *range); + #endif /* GDB_LINUX_TDEP_H */ diff --git a/gdb/testsuite/gdb.arch/amd64-shadow-stack-cmds.exp b/gdb/testsuite/gdb.arch/amd64-shadow-stack-cmds.exp new file mode 100644 index 00000000000..17f32ce3964 --- /dev/null +++ b/gdb/testsuite/gdb.arch/amd64-shadow-stack-cmds.exp @@ -0,0 +1,88 @@ +# 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 . + +# Test shadow stack enabling for frame level update and the return command. + +require allow_ssp_tests + +standard_testfile amd64-shadow-stack.c + +save_vars { ::env(GLIBC_TUNABLES) } { + + append_environment GLIBC_TUNABLES "glibc.cpu.hwcaps" "SHSTK" + + if { [prepare_for_testing "failed to prepare" ${testfile} ${srcfile} \ + {debug additional_flags="-fcf-protection=return"}] } { + return -1 + } + + clean_restart ${binfile} + if { ![runto_main] } { + return -1 + } + + set call1_line [ gdb_get_line_number "break call1" ] + set call2_line [ gdb_get_line_number "break call2" ] + + # Extract shadow stack pointer inside main, call1 and call2 function. + gdb_breakpoint $call1_line + gdb_breakpoint $call2_line + set ssp_main [get_valueof /x "\$pl3_ssp" 0 "get value of ssp in main"] + gdb_continue_to_breakpoint "break call1" ".*break call1.*" + set ssp_call1 [get_valueof /x "\$pl3_ssp" 0 "get value of ssp in call1"] + gdb_continue_to_breakpoint "break call2" ".*break call2.*" + set ssp_call2 [get_valueof /x "\$pl3_ssp" 0 "get value of ssp in call2"] + + with_test_prefix "test frame level update" { + gdb_test "up" "call1.*" "move to frame 1" + gdb_test "print /x \$pl3_ssp" "= $ssp_call1" "check pl3_ssp of frame 1" + gdb_test "up" "main.*" "move to frame 2" + gdb_test "print /x \$pl3_ssp" "= $ssp_main" "check pl3_ssp of frame 2" + gdb_test "frame 0" "call2.*" "move to frame 0" + gdb_test "print /x \$pl3_ssp" "= $ssp_call2" "check pl3_ssp of frame 0" + } + + with_test_prefix "test return from current frame" { + gdb_test "return (int) 1" "#0.*call1.*" \ + "Test shadow stack return from current frame" \ + "Make.*return now\\? \\(y or n\\) " "y" + + # Potential CET violations often only occur after resuming normal execution. + # Therefore, it is important to test normal program continuation after + # testing the return command. + gdb_continue_to_end + } + + clean_restart ${binfile} + if { ![runto_main] } { + return -1 + } + + with_test_prefix "test return from past frame" { + gdb_breakpoint $call2_line + gdb_continue_to_breakpoint "break call2" ".*break call2.*" + + gdb_test "frame 1" ".*in call1.*" + + gdb_test "return (int) 1" "#0.*main.*" \ + "Test shadow stack return from past frame" \ + "Make.*return now\\? \\(y or n\\) " "y" + + # Potential CET violations often only occur after resuming normal execution. + # Therefore, it is important to test normal program continuation after + # testing the return command. + gdb_continue_to_end + } +} diff --git a/gdb/testsuite/gdb.arch/amd64-shadow-stack.c b/gdb/testsuite/gdb.arch/amd64-shadow-stack.c index 643ef2d5f56..80389730bc2 100644 --- a/gdb/testsuite/gdb.arch/amd64-shadow-stack.c +++ b/gdb/testsuite/gdb.arch/amd64-shadow-stack.c @@ -15,8 +15,21 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ +static int +call2 () +{ + return 42; /* break call2. */ +} + +static int +call1 () +{ + return call2 (); /* break call1. */ +} + int main () { + call1 (); /* break main. */ return 0; } -- 2.34.1 Intel Deutschland GmbH Registered Address: Am Campeon 10, 85579 Neubiberg, Germany Tel: +49 89 99 8853-0, www.intel.de Managing Directors: Sean Fennelly, Jeffrey Schneiderman, Tiffany Doon Silva Chairperson of the Supervisory Board: Nicole Lau Registered Office: Munich Commercial Register: Amtsgericht Muenchen HRB 186928