Mirror of the gdb-patches mailing list
 help / color / mirror / Atom feed
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

  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