From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from simark.ca by simark.ca with LMTP id AHgaIpaoX2hkXSMAWB0awg (envelope-from ) for ; Sat, 28 Jun 2025 04:32:22 -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=nMYc/LRD; dkim-atps=neutral Received: by simark.ca (Postfix, from userid 112) id 876B71E11E; Sat, 28 Jun 2025 04:32:22 -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 DF9301E089 for ; Sat, 28 Jun 2025 04:32:21 -0400 (EDT) Received: from server2.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 8A6B83854A9C for ; Sat, 28 Jun 2025 08:32:21 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 8A6B83854A9C 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=nMYc/LRD Received: from mgamail.intel.com (mgamail.intel.com [192.198.163.19]) by sourceware.org (Postfix) with ESMTPS id 70891385B538 for ; Sat, 28 Jun 2025 08:29:12 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 70891385B538 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 70891385B538 Authentication-Results: server2.sourceware.org; arc=none smtp.remote-ip=192.198.163.19 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1751099352; cv=none; b=Y3XOM0RGAS6Sd1TVHExeSXfZdFw9ZSj/21yS7Tw9xAi/NB1vfp9ELOZFdHA5VY+kU0QZBF3mMPknFWYGyqTSd7XabkZ4VWjdAhqI4gvN/Ayg7418IpYpNgL28LfvLEedmQij01otkpruwtppp4MrmyDnq+eVc21aFjUWv1aNE40= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1751099352; c=relaxed/simple; bh=LQA/3EfiGHFt2sQ+RNVLWpoNhR8MWmfb2R68qTFBFQk=; h=DKIM-Signature:From:To:Subject:Date:Message-ID:MIME-Version; b=ezAz7nCV0d3up6LS8tEi+dk73LPT79XSkXe4hlEk+fs8NMlJUK21FwVL2smd7IDOWBVDq0Ox73jumA4KUO8by0N9aaaRmzVeOk/bTwqlK1G6q0JwD7LQsgiL1Gi7PBp3sFWV4VCKKJhs6LUKd4kiGfyplk53PuT0BG7ihPXG8NY= ARC-Authentication-Results: i=1; server2.sourceware.org DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 70891385B538 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=intel.com; i=@intel.com; q=dns/txt; s=Intel; t=1751099353; x=1782635353; h=from:to:cc:subject:date:message-id:in-reply-to: references:mime-version:content-transfer-encoding; bh=LQA/3EfiGHFt2sQ+RNVLWpoNhR8MWmfb2R68qTFBFQk=; b=nMYc/LRDMWHI2DPNyKknLITg9q0Q65UsMFPIItCgBrI/w64+1xSHs41z JuuwoH3IZbSRYMyLXQypbv7CbzHd5VOqDLhu116SfhlkV7ZeR9ZgyLcjg b2VbXHhMWZYUuoZFJqTqrXOgdVCIksqTzIquaHO6f0Wvp1HAtXUucvehJ /EqLfgL1H7X3hSdSGbE4gvUS0aTHC4+VZdVyxdT3EIGdCp7/ZW+EJHSfm LiQqtc3KRdRgONU5XAHK2T2lJWqhUJPksxxjJwcYyjvrQ9xcl0WNAZP2U KCi5nXNKrHsVvfOVi4A1k5LTXAVLCOIGEjbSvGyKNxcobmcO7Bqs8me4m g==; X-CSE-ConnectionGUID: W71oWIsbS9GNMhGM3fKegg== X-CSE-MsgGUID: tXsVJWDGSjqrDzYeyBSobQ== X-IronPort-AV: E=McAfee;i="6800,10657,11477"; a="52518180" X-IronPort-AV: E=Sophos;i="6.16,272,1744095600"; d="scan'208";a="52518180" Received: from orviesa004.jf.intel.com ([10.64.159.144]) by fmvoesa113.fm.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 28 Jun 2025 01:29:12 -0700 X-CSE-ConnectionGUID: oqaBBJWRSu+x0OwmxtjtrQ== X-CSE-MsgGUID: tFX5SZQCSpKFOVokDVhefg== X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="6.16,272,1744095600"; d="scan'208";a="157538110" Received: from 0007e934a912.jf.intel.com (HELO localhost) ([10.165.58.208]) by orviesa004-auth.jf.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 28 Jun 2025 01:29:12 -0700 From: Christina Schimpe To: gdb-patches@sourceware.org Cc: thiago.bauermann@linaro.org, luis.machado@arm.com Subject: [PATCH v5 10/12] gdb: Implement amd64 linux shadow stack support for inferior calls. Date: Sat, 28 Jun 2025 01:28:08 -0700 Message-ID: <20250628082810.332526-11-christina.schimpe@intel.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20250628082810.332526-1-christina.schimpe@intel.com> References: <20250628082810.332526-1-christina.schimpe@intel.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit 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 This patch enables inferior calls to support Intel's Control-Flow Enforcement Technology (CET), which provides the shadow stack feature for the x86 architecture. Following the restriction of the linux kernel, enable inferior calls for amd64 only. Reviewed-by: Thiago Jung Bauermann Reviewed-By: Eli Zaretskii Reviewed-By: Luis Machado --- gdb/amd64-linux-tdep.c | 63 +++++++++++++++++++ gdb/doc/gdb.texinfo | 29 +++++++++ .../gdb.arch/amd64-shadow-stack-cmds.exp | 55 +++++++++++++++- 3 files changed, 146 insertions(+), 1 deletion(-) diff --git a/gdb/amd64-linux-tdep.c b/gdb/amd64-linux-tdep.c index f6ae5395870..899fe2df02c 100644 --- a/gdb/amd64-linux-tdep.c +++ b/gdb/amd64-linux-tdep.c @@ -1932,6 +1932,67 @@ amd64_linux_shadow_stack_element_size_aligned (gdbarch *gdbarch) return (binfo->bits_per_word / binfo->bits_per_byte); } +/* Read the shadow stack pointer register and return its value, if + possible. */ + +static std::optional +amd64_linux_get_shadow_stack_pointer (gdbarch *gdbarch, regcache *regcache) +{ + const i386_gdbarch_tdep *tdep = gdbarch_tdep (gdbarch); + + if (tdep == nullptr || tdep->ssp_regnum < 0) + return {}; + + CORE_ADDR ssp; + if (regcache_raw_read_unsigned (regcache, tdep->ssp_regnum, &ssp) + != REG_VALID) + return {}; + + /* Dependent on the target in case the shadow stack pointer is + unavailable, the ssp register can be invalid or 0x0 when shadow stack + is supported by HW and the linux kernel but not enabled for the + current thread. */ + if (ssp == 0x0) + return {}; + + return ssp; +} + +/* If shadow stack is enabled, push the address NEW_ADDR to the shadow + stack and increment the shadow stack pointer accordingly. */ + +static void +amd64_linux_shadow_stack_push (gdbarch *gdbarch, CORE_ADDR new_addr, + regcache *regcache) +{ + std::optional ssp + = amd64_linux_get_shadow_stack_pointer (gdbarch, regcache); + if (!ssp.has_value ()) + return; + + /* The shadow stack grows downwards. To push addresses to the stack, + we need to decrement SSP. */ + const int element_size + = amd64_linux_shadow_stack_element_size_aligned (gdbarch); + const CORE_ADDR new_ssp = *ssp - element_size; + + /* Using /proc/PID/smaps we can only check if NEW_SSP points to shadow + stack memory. If it doesn't, we assume the stack is full. */ + std::pair memrange; + if (!linux_address_in_shadow_stack_mem_range (new_ssp, &memrange)) + error (_("No space left on the shadow stack.")); + + /* On x86 there can be a shadow stack token at bit 63. For x32, the + address size is only 32 bit. Thus, we must use ELEMENT_SIZE (and + not gdbarch_addr_bit) to determine the width of the address to be + written. */ + const bfd_endian byte_order = gdbarch_byte_order (gdbarch); + write_memory_unsigned_integer (new_ssp, element_size, byte_order, + (ULONGEST) new_addr); + + i386_gdbarch_tdep *tdep = gdbarch_tdep (gdbarch); + regcache_raw_write_unsigned (regcache, tdep->ssp_regnum, new_ssp); +} /* Implement shadow stack pointer unwinding. For each new shadow stack pointer check if its address is still in the shadow stack memory range. @@ -2059,6 +2120,8 @@ 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); + + set_gdbarch_shadow_stack_push (gdbarch, amd64_linux_shadow_stack_push); dwarf2_frame_set_init_reg (gdbarch, amd64_init_reg); } diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo index 0881ac4aee5..b5120b78426 100644 --- a/gdb/doc/gdb.texinfo +++ b/gdb/doc/gdb.texinfo @@ -27037,6 +27037,35 @@ registers @end itemize +@subsubsection Intel Control-Flow Enforcement Technology. +@cindex Intel Control-Flow Enforcement Technology. + +The @dfn{Intel Control-Flow Enforcement Technology} (@acronym{Intel CET}) +provides two capabilities to defend against ``Return-oriented Programming'' +and ``call/jmp-oriented programming'' style control-flow attacks: + +@itemize @bullet +@item Shadow Stack: +A shadow stack is a second stack for a program. It holds the return +addresses pushed by the call instruction. The @code{RET} instruction pops the +return addresses from both call and shadow stack. If the return addresses from +the two stacks do not match, the processor signals a control protection +exception. +@item Indirect Branch Tracking (IBT): +When IBT is enabled, the CPU implements a state machine that tracks indirect +@code{JMP} and @code{CALL} instructions. The state machine can be either IDLE +or WAIT_FOR_ENDBRANCH. In WAIT_FOR_ENDBRANCH state the next instruction in +the program stream must be an @code{ENDBR} instruction, otherwise the +processor signals a control protection exception. +@end itemize + +Impact on Call/Print: +Inferior calls in @value{GDBN} reset the current PC to the beginning of the +function that is called. No call instruction is executed, but the @code{RET} +instruction actually is. To avoid a control protection exception due to the +missing return address on the shadow stack, @value{GDBN} pushes the new return +address to the shadow stack and updates the shadow stack pointer. + @node Alpha @subsection Alpha diff --git a/gdb/testsuite/gdb.arch/amd64-shadow-stack-cmds.exp b/gdb/testsuite/gdb.arch/amd64-shadow-stack-cmds.exp index 17f32ce3964..622612d2f7d 100644 --- a/gdb/testsuite/gdb.arch/amd64-shadow-stack-cmds.exp +++ b/gdb/testsuite/gdb.arch/amd64-shadow-stack-cmds.exp @@ -13,12 +13,29 @@ # 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. +# Test shadow stack enabling for frame level update, the return and the +# call commands. +# As potential CET violations often only occur after resuming normal +# execution, test normal program continuation after each return or call +# commands. require allow_ssp_tests standard_testfile amd64-shadow-stack.c +proc restart_and_run_infcall_call2 {} { + global binfile + clean_restart ${binfile} + if { ![runto_main] } { + return -1 + } + set inside_infcall_str "The program being debugged stopped while in a function called from GDB" + gdb_breakpoint [ gdb_get_line_number "break call2" ] + gdb_continue_to_breakpoint "break call2" ".*break call2.*" + gdb_test "call (int) call2()" \ + "Breakpoint \[0-9\]*, call2.*$inside_infcall_str.*" +} + save_vars { ::env(GLIBC_TUNABLES) } { append_environment GLIBC_TUNABLES "glibc.cpu.hwcaps" "SHSTK" @@ -33,6 +50,42 @@ save_vars { ::env(GLIBC_TUNABLES) } { return -1 } + with_test_prefix "test inferior call and continue" { + gdb_breakpoint [ gdb_get_line_number "break call1" ] + gdb_continue_to_breakpoint "break call1" ".*break call1.*" + + gdb_test "call (int) call2()" "= 42" + + gdb_continue_to_end + } + + with_test_prefix "test return inside an inferior call" { + restart_and_run_infcall_call2 + + gdb_test "return" "\#0.*call2.*" \ + "Test shadow stack return inside an inferior call" \ + "Make.*return now\\? \\(y or n\\) " "y" + + gdb_continue_to_end + } + + with_test_prefix "test return 'above' an inferior call" { + restart_and_run_infcall_call2 + + gdb_test "frame 2" "call2 ().*" "move to frame 'above' inferior call" + + gdb_test "return" "\#0.*call1.*" \ + "Test shadow stack return 'above' an inferior call" \ + "Make.*return now\\? \\(y or n\\) " "y" + + gdb_continue_to_end + } + + 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" ] -- 2.43.0