From: Thiago Jung Bauermann <thiago.bauermann@linaro.org>
To: "Schimpe, Christina" <christina.schimpe@intel.com>
Cc: Luis Machado <luis.machado@arm.com>,
"gdb-patches@sourceware.org" <gdb-patches@sourceware.org>,
"eliz@gnu.org" <eliz@gnu.org>
Subject: Re: [PATCH v4 07/11] gdb: Handle shadow stack pointer register unwinding for amd64 linux.
Date: Mon, 23 Jun 2025 20:26:26 -0300 [thread overview]
Message-ID: <877c12vy3h.fsf@linaro.org> (raw)
In-Reply-To: <SN7PR11MB76388605C7D433A4ED16C741F979A@SN7PR11MB7638.namprd11.prod.outlook.com> (Christina Schimpe's message of "Mon, 23 Jun 2025 14:55:13 +0000")
Hello Christina,
"Schimpe, Christina" <christina.schimpe@intel.com> writes:
>> -----Original Message-----
>> From: Thiago Jung Bauermann <thiago.bauermann@linaro.org>
>> Sent: Friday, June 20, 2025 3:43 AM
>> To: Luis Machado <luis.machado@arm.com>
>> Cc: Schimpe, Christina <christina.schimpe@intel.com>; gdb-
>> patches@sourceware.org; eliz@gnu.org
>> Subject: Re: [PATCH v4 07/11] gdb: Handle shadow stack pointer register
>> unwinding for amd64 linux.
>>
>> Luis Machado <luis.machado@arm.com> writes:
>>
>> > On 6/17/25 13:11, Christina Schimpe wrote:
>> >> + 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<CORE_ADDR, CORE_ADDR> 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). */
>> >
>> > I couldn't quite understand the difference between the empty case and
>> > the unavailable case. But maybe I just don't fully understand the feature.
>> >
>> > Would it be possible to make the comment a bit more clear?
>>
>> I understood it to mean that if new_ssp == range.second, then it points to
>> the top of the stack and there aren't any elements.
>>
>> Whereas if new_ssp points outside of the shadow stack area, then it's
>> garbage and we failed to unwind it, hence the unavailable value.
>>
>> Christina, please correct me if I'm wrong.
>>
>> But now looking at this again, I think there's an off-by-one error:
>> range.second is the first address outside of the memory range, so the
>> comparison needs to be strictly less than. And the shadow stack will be
>> empty if new_ssp == range.second - 1 (there's no need to check for that,
>> though).
>>
>> >> + 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;
>> >> +}
>
> Hi Thiago,
>
>> Whereas if new_ssp points outside of the shadow stack area, then it's
>> garbage and we failed to unwind it, hence the unavailable value.
>
> Yes, this is how it should be. 😊
>
> But I think the current implementation should be correct.
> I wrote a small testprogram, which enables shadow stack manually using ARCH_PRCTL in enable_ssp:
>
> ~~~
>
> int call ()
> {
> return 0;
> }
>
> int enable_ssp ()
> {
> int ret;
> if (ARCH_PRCTL(ARCH_SHSTK_ENABLE, ARCH_SHSTK_SHSTK)) {
> printf("[FAIL]\tEnabling shadow stack failed\n");
> return 1;
> }
>
> ret = call (); // stop in enable_ssp
>
> if (ARCH_PRCTL(ARCH_SHSTK_DISABLE, ARCH_SHSTK_SHSTK)) {
> ret = 1;
> printf("[FAIL]\tDisabling shadow stack failed\n");
> }
> return ret;
> }
>
> int main ()
> {
> return enable_ssp ();
> }
>
> ~~~
>
> If we stop in enable_ssp, ("ret = call ()"), we have the scenario that shadow stack is enabled,
> but no call instruction has been executed yet.
> So we'll have an empty shadow stack, but SSP is valid and pointing to the end address
> (==range.second) of the shadow stack address space:
Thanks for the program. I see that Intel and AArch64 behave differently
in this respect. I believe that this is because on AArch64, a new stack
contains an initial value of 0 at the top. From the Linux kernel
documentation¹:
When a stack is allocated by enabling GCS or during thread creation
then the top 8 bytes of the stack will be initialised to 0 and
GCSPR_EL0 will be set to point to the address of this 0 value, this
can be used to detect the top of the stack.
> ~~~
> (gdb) r
> Starting program: /tmp/main
> [Thread debugging using libthread_db enabled]
> Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
>
> Breakpoint 1, enable_ssp () at main.c:52
> 52 ret = call (); // stop in enable_ssp
> (gdb) p $pl3_ssp
> $1 = (void *) 0x7ffff7c00000
> (gdb) info proc mappings
> process 94192
> Mapped address spaces:
>
> Start Addr End Addr Size Offset Perms File
> [...]
> 0x0000555555558000 0x0000555555559000 0x1000 0x3000 rw-p /tmp/main
> 0x00007ffff7400000 0x00007ffff7c00000 0x800000 0x0 rw-p
> [...]
On AArch64 the GCSPR is within the GCS memory range:
(gdb) r
Starting program: /home/bauermann/tmp/test-gcs
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/aarch64-linux-gnu/libthread_db.so.1".
Breakpoint 1, enable_gcspr () at test-gcs.c:63
⚠ warning: Source file is more recent than executable.
63 ret = call (); // stop in enable_ssp
(gdb) p $gcspr
$1 = (void *) 0xfffff7bffff8
(gdb) info proc mappings
process 3228
Mapped address spaces:
Start Addr End Addr Size Offset Perms File
⋮
0x0000fffff7800000 0x0000fffff7c00000 0x400000 0x0 rw-p
> ~~~
>
> Checking the unwinding we see that one frame above SSP becomes unavailable:
> ~~~
> (gdb) up
> #1 0x00005555555551d4 in main () at main.c:63
> 63 return enable_ssp ();
> (gdb) p $pl3_ssp
> $3 = <unavailable>
> (gdb) down
> #0 enable_ssp () at main.c:52
> 52 ret = call (); // stop in enable_ssp
> (gdb) p $pl3_ssp
> $4 = (void *) 0x7ffff7c00000
What I get on AArch64. The last command shows the zeroed entry at the
top of the stack.
(gdb) up
#1 0x0000aaaaaaaa0900 in main () at test-gcs.c:77
77 return enable_gcspr ();
(gdb) p $gcspr
$2 = <unavailable>
(gdb) down
#0 enable_gcspr () at test-gcs.c:63
63 ret = call (); // stop in enable_ssp
(gdb) p $gcspr
$3 = (void *) 0xfffff7bffff8
(gdb) x/xg $gcspr
0xfffff7bffff8: 0x0000000000000000
> ~~~
>
> Or unwinding from call:
> ~~~
> Breakpoint 1, call () at main.c:41
> 41 return 0;
> (gdb) p $pl3_ssp
> $1 = (void *) 0x7ffff7bffff8
> (gdb) up
> #1 0x000055555555518a in enable_ssp () at main.c:52
> 52 ret = call (); // stop in enable_ssp
> (gdb) p $pl3_ssp
> $2 = (void *) 0x7ffff7c00000
> (gdb) up
> #2 0x00005555555551d4 in main () at main.c:63
> 63 return enable_ssp ();
> (gdb) p $pl3_ssp
> $3 = <unavailable>
> (gdb) down
> #1 0x000055555555518a in enable_ssp () at main.c:52
> 52 ret = call (); // stop in enable_ssp
> (gdb) p $pl3_ssp
> $4 = (void *) 0x7ffff7c00000
> (gdb) down
> #0 call () at main.c:41
> 41 return 0;
> (gdb) p $pl3_ssp
> $5 = (void *) 0x7ffff7bffff8
In this example the GCSPR is also always within the GCS range. And you
can see the zeroed entry at the top as well:
Breakpoint 2, call () at test-gcs.c:50
50 return 0;
(gdb) p $gcspr
$4 = (void *) 0xfffff7bffff0
(gdb) x/xg $gcspr
0xfffff7bffff0: 0x0000aaaaaaaa08a4
(gdb) x/2xg $gcspr
0xfffff7bffff0: 0x0000aaaaaaaa08a4 0x0000000000000000
(gdb) up
#1 0x0000aaaaaaaa08a4 in enable_gcspr () at test-gcs.c:63
63 ret = call (); // stop in enable_ssp
(gdb) p $gcspr
$5 = (void *) 0xfffff7bffff8
(gdb) x/xg $gcspr
0xfffff7bffff8: 0x0000000000000000
(gdb) up
#2 0x0000aaaaaaaa0900 in main () at test-gcs.c:77
77 return enable_gcspr ();
(gdb) p $gcspr
$6 = <unavailable>
(gdb) down
#1 0x0000aaaaaaaa08a4 in enable_gcspr () at test-gcs.c:63
63 ret = call (); // stop in enable_ssp
(gdb) p $gcspr
$7 = (void *) 0xfffff7bffff8
(gdb) down
#0 call () at test-gcs.c:50
50 return 0;
(gdb) p $gcspr
$8 = (void *) 0xfffff7bffff0
> ~~~
>
> So the check
>
> "if (new_ssp <= range.second)"
>
> seems correct to me.
This is a subtle difference between our systems. Thanks for the example
and the explanation. It was very helpful.
--
Thiago
¹ https://www.kernel.org/doc/html/latest/arch/arm64/gcs.html#allocation-of-guarded-control-stacks
next prev parent reply other threads:[~2025-06-23 23:27 UTC|newest]
Thread overview: 53+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-06-17 12:11 [PATCH v4 00/11] Add CET shadow stack support Christina Schimpe
2025-06-17 12:11 ` [PATCH v4 01/11] gdbserver: Add optional runtime register set type Christina Schimpe
2025-06-19 9:27 ` Luis Machado
2025-06-17 12:11 ` [PATCH v4 02/11] gdbserver: Add assert in x86_linux_read_description Christina Schimpe
2025-06-19 9:27 ` Luis Machado
2025-06-17 12:11 ` [PATCH v4 03/11] gdb: Sync up x86-gcc-cpuid.h with cpuid.h from gcc 14 branch Christina Schimpe
2025-06-17 18:12 ` Tom Tromey
2025-06-20 12:39 ` Schimpe, Christina
2025-06-17 12:11 ` [PATCH v4 04/11] gdb, gdbserver: Use xstate_bv for target description creation on x86 Christina Schimpe
2025-06-19 9:23 ` Luis Machado
2025-06-23 12:46 ` Schimpe, Christina
2025-06-23 12:56 ` Luis Machado
2025-06-24 13:46 ` Schimpe, Christina
2025-06-26 16:03 ` Luis Machado
2025-06-17 12:11 ` [PATCH v4 05/11] gdb, gdbserver: Add support of Intel shadow stack pointer register Christina Schimpe
2025-06-17 12:20 ` Eli Zaretskii
2025-06-19 9:24 ` Luis Machado
2025-06-23 13:05 ` Schimpe, Christina
2025-06-17 12:11 ` [PATCH v4 06/11] gdb: amd64 linux coredump support with shadow stack Christina Schimpe
2025-06-19 9:24 ` Luis Machado
2025-06-23 13:16 ` Schimpe, Christina
2025-06-17 12:11 ` [PATCH v4 07/11] gdb: Handle shadow stack pointer register unwinding for amd64 linux Christina Schimpe
2025-06-19 9:25 ` Luis Machado
2025-06-20 1:42 ` Thiago Jung Bauermann
2025-06-23 14:55 ` Schimpe, Christina
2025-06-23 23:26 ` Thiago Jung Bauermann [this message]
2025-06-23 15:00 ` Schimpe, Christina
2025-06-23 15:06 ` Luis Machado
2025-06-23 23:36 ` Thiago Jung Bauermann
2025-06-20 1:52 ` Thiago Jung Bauermann
2025-06-17 12:11 ` [PATCH v4 08/11] gdb, gdbarch: Enable inferior calls for shadow stack support Christina Schimpe
2025-06-19 9:25 ` Luis Machado
2025-06-23 17:49 ` Schimpe, Christina
2025-06-17 12:11 ` [PATCH v4 09/11] gdb: Implement amd64 linux shadow stack support for inferior calls Christina Schimpe
2025-06-17 12:21 ` Eli Zaretskii
2025-06-19 9:25 ` Luis Machado
2025-06-27 19:52 ` Schimpe, Christina
2025-06-28 10:38 ` Luis Machado
2025-06-28 20:03 ` Thiago Jung Bauermann
2025-06-28 21:05 ` Thiago Jung Bauermann
2025-06-17 12:11 ` [PATCH v4 10/11] gdb, gdbarch: Introduce gdbarch method to get the shadow stack pointer Christina Schimpe
2025-06-17 18:16 ` Tom Tromey
2025-06-20 12:59 ` Schimpe, Christina
2025-06-19 9:26 ` Luis Machado
2025-06-23 18:00 ` Schimpe, Christina
2025-06-17 12:11 ` [PATCH v4 11/11] gdb: Enable displaced stepping with shadow stack on amd64 linux Christina Schimpe
2025-06-17 12:22 ` Eli Zaretskii
2025-06-17 15:16 ` Schimpe, Christina
2025-06-19 9:26 ` Luis Machado
2025-06-23 18:24 ` Schimpe, Christina
2025-06-24 8:05 ` Luis Machado
2025-06-27 19:26 ` Schimpe, Christina
2025-06-28 10:35 ` Luis Machado
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=877c12vy3h.fsf@linaro.org \
--to=thiago.bauermann@linaro.org \
--cc=christina.schimpe@intel.com \
--cc=eliz@gnu.org \
--cc=gdb-patches@sourceware.org \
--cc=luis.machado@arm.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox