diff mbox series

x86: Get the previous shadow stack pointer from the restore token

Message ID 20231216155609.562884-1-hjl.tools@gmail.com
State New
Headers show
Series x86: Get the previous shadow stack pointer from the restore token | expand

Commit Message

H.J. Lu Dec. 16, 2023, 3:56 p.m. UTC
Linux CET kernel places a restore token on shadow stack followed by
optional additional information for signal handler to enhance security.
The restore token is the previous shadow stack pointer with bit 63 set.
It is usually transparent to user programs since kernel will pop the
restore token and additional information when signal handler returns.
But when an exception is thrown from a signal handler, now we need to
pop the restore token and additional information from shadow stack.
For x86-64, we just need to get the previous shadow stack pointer from
the restore token.  For i386, shadow stack is unsupported.

To be compatible with the old unwinder which doesn't use the restore
token to skip shadow stack frames used by signal handler, Linux kernel
won't put additional information after the restore token by default.
Define __cet_features to 1 to indicate that unwinder uses the restore
token to skip shadow stack frames used by signal handler.  It can be
checked by glibc before enabling additional information in shadow stack
for signal handler.

	* config/i386/libgcc-glibc.ver: Add __cet_features to
	GCC_CET_FEATURES.
	* config/i386/shadow-stack-unwind.h (_Unwind_Frames_Increment):
	Only define for x86-64.  Get the previous shadow stack pointer
	from the restore token and skip to the previous frame.
	(__cet_features): New.
---
 libgcc/config/i386/libgcc-glibc.ver      |  4 ++
 libgcc/config/i386/shadow-stack-unwind.h | 84 +++++++-----------------
 2 files changed, 29 insertions(+), 59 deletions(-)

Comments

Richard Biener Dec. 16, 2023, 4:41 p.m. UTC | #1
> Am 16.12.2023 um 16:56 schrieb H.J. Lu <hjl.tools@gmail.com>:
> 
> Linux CET kernel places a restore token on shadow stack followed by
> optional additional information for signal handler to enhance security.
> The restore token is the previous shadow stack pointer with bit 63 set.
> It is usually transparent to user programs since kernel will pop the
> restore token and additional information when signal handler returns.
> But when an exception is thrown from a signal handler, now we need to
> pop the restore token and additional information from shadow stack.
> For x86-64, we just need to get the previous shadow stack pointer from
> the restore token.  For i386, shadow stack is unsupported.
> 
> To be compatible with the old unwinder which doesn't use the restore
> token to skip shadow stack frames used by signal handler, Linux kernel
> won't put additional information after the restore token by default.
> Define __cet_features to 1 to indicate that unwinder uses the restore
> token to skip shadow stack frames used by signal handler.  It can be
> checked by glibc before enabling additional information in shadow stack
> for signal handler.

Doesn’t the check need to be two ways
To support kernels not doing this?  Or
Not do it if the high bit isn’t set?

Richard 

>    * config/i386/libgcc-glibc.ver: Add __cet_features to
>    GCC_CET_FEATURES.
>    * config/i386/shadow-stack-unwind.h (_Unwind_Frames_Increment):
>    Only define for x86-64.  Get the previous shadow stack pointer
>    from the restore token and skip to the previous frame.
>    (__cet_features): New.
> ---
> libgcc/config/i386/libgcc-glibc.ver      |  4 ++
> libgcc/config/i386/shadow-stack-unwind.h | 84 +++++++-----------------
> 2 files changed, 29 insertions(+), 59 deletions(-)
> 
> diff --git a/libgcc/config/i386/libgcc-glibc.ver b/libgcc/config/i386/libgcc-glibc.ver
> index 1c4665719da..9a8525757a4 100644
> --- a/libgcc/config/i386/libgcc-glibc.ver
> +++ b/libgcc/config/i386/libgcc-glibc.ver
> @@ -152,6 +152,10 @@ GCC_4.8.0 {
>   __cpu_model
>   __cpu_indicator_init
> }
> +
> +GCC_CET_FEATURES {
> +  __cet_features
> +}
> %else
> GCC_4.4.0 {
>   __addtf3
> diff --git a/libgcc/config/i386/shadow-stack-unwind.h b/libgcc/config/i386/shadow-stack-unwind.h
> index e07ab4a10e4..afcce4b482d 100644
> --- a/libgcc/config/i386/shadow-stack-unwind.h
> +++ b/libgcc/config/i386/shadow-stack-unwind.h
> @@ -43,18 +43,15 @@ see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see
>     }                        \
>     while (0)
> 
> -/* Linux CET kernel places a restore token on shadow stack for signal
> -   handler to enhance security.  The restore token is 8 byte and aligned
> -   to 8 bytes.  It is usually transparent to user programs since kernel
> -   will pop the restore token when signal handler returns.  But when an
> -   exception is thrown from a signal handler, now we need to pop the
> -   restore token from shadow stack.  For x86-64, we just need to treat
> -   the signal frame as normal frame.  For i386, we need to search for
> -   the restore token to check if the original shadow stack is 8 byte
> -   aligned.  If the original shadow stack is 8 byte aligned, we just
> -   need to pop 2 slots, one restore token, from shadow stack.  Otherwise,
> -   we need to pop 3 slots, one restore token + 4 byte padding, from
> -   shadow stack.
> +/* Linux CET kernel places a restore token on shadow stack followed by
> +   additional information for signal handler to enhance security.  The
> +   restore token is the previous shadow stack pointer with bit 63 set.
> +   It is usually transparent to user programs since kernel will pop the
> +   restore token and additional information when signal handler returns.
> +   But when an exception is thrown from a signal handler, now we need to
> +   pop the restore token and additional information from shadow stack.
> +   For x86-64, we just need to get the previous shadow stack pointer from
> +   the restore token.  For i386, shadow stack is unsupported.
> 
>    When popping a stack frame, we compare the return address on normal
>    stack against the return address on shadow stack.  If they don't match,
> @@ -66,65 +63,34 @@ see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see
>    3. Signal stack frame since kernel puts a restore token on shadow
>       stack.
>  */
> -#undef _Unwind_Frames_Increment
> #ifdef __x86_64__
> +#undef _Unwind_Frames_Increment
> #define _Unwind_Frames_Increment(exc, context, frames)    \
>     {                            \
>       frames++;                        \
> -      if (exc->exception_class != 0            \
> -      && _Unwind_GetIP (context) != 0        \
> -      && !_Unwind_IsSignalFrame (context))        \
> +      _Unwind_Word ssp = _get_ssp ();            \
> +      if (ssp != 0)                    \
>    {                        \
> -      _Unwind_Word ssp = _get_ssp ();        \
> -      if (ssp != 0)                    \
> +      ssp += 8 * frames;            \
> +      if (_Unwind_IsSignalFrame (context))        \
>        {                        \
> -          ssp += 8 * frames;            \
> -          _Unwind_Word ra = *(_Unwind_Word *) ssp;    \
> -          if (ra != _Unwind_GetIP (context))    \
> -        return _URC_FATAL_PHASE2_ERROR;        \
> +          /* Get the previous SSP.  */        \
> +          _Unwind_Word prev_ssp            \
> +        = ((*(_Unwind_Word *) ssp)        \
> +           & ~0x8000000000000000LL);        \
> +          /* Skip to the previous frame.  */    \
> +          frames += (prev_ssp - ssp) / 8 - 1;    \
>        }                        \
> -    }                        \
> -    }
> -#else
> -#define _Unwind_Frames_Increment(exc, context, frames)    \
> -  if (_Unwind_IsSignalFrame (context))            \
> -    do                            \
> -      {                            \
> -    _Unwind_Word ssp, prev_ssp, token;        \
> -    ssp = _get_ssp ();                \
> -    if (ssp != 0)                    \
> -      {                        \
> -        /* Align shadow stack pointer to the next    \
> -           8 byte aligned boundary.  */        \
> -        ssp = (ssp + 4) & ~7;            \
> -        do                        \
> -          {                        \
> -        /* Look for a restore token.  */    \
> -        token = (*(_Unwind_Word *) (ssp - 8));    \
> -        prev_ssp = token & ~7;            \
> -        if (prev_ssp == ssp)            \
> -          break;                \
> -        ssp += 8;                \
> -          }                        \
> -        while (1);                    \
> -        frames += (token & 0x4) ? 3 : 2;        \
> -      }                        \
> -      }                            \
> -    while (0);                        \
> -  else                            \
> -    {                            \
> -      frames++;                        \
> -      if (exc->exception_class != 0            \
> -      && _Unwind_GetIP (context) != 0)        \
> -    {                        \
> -      _Unwind_Word ssp = _get_ssp ();        \
> -      if (ssp != 0)                    \
> +      else if (_Unwind_GetIP (context) != 0        \
> +           && exc->exception_class != 0)    \
>        {                        \
> -          ssp += 4 * frames;            \
>          _Unwind_Word ra = *(_Unwind_Word *) ssp;    \
>          if (ra != _Unwind_GetIP (context))    \
>        return _URC_FATAL_PHASE2_ERROR;        \
>        }                        \
>    }                        \
>     }
> +
> +/* Bit 0: Unwinder uses the restore token in signal frame.  */
> +const int __cet_features = 1;
> #endif
> -- 
> 2.43.0
>
H.J. Lu Dec. 16, 2023, 4:51 p.m. UTC | #2
On Sat, Dec 16, 2023 at 8:41 AM Richard Biener
<richard.guenther@gmail.com> wrote:
>
>
>
> > Am 16.12.2023 um 16:56 schrieb H.J. Lu <hjl.tools@gmail.com>:
> >
> > Linux CET kernel places a restore token on shadow stack followed by
> > optional additional information for signal handler to enhance security.
> > The restore token is the previous shadow stack pointer with bit 63 set.
> > It is usually transparent to user programs since kernel will pop the
> > restore token and additional information when signal handler returns.
> > But when an exception is thrown from a signal handler, now we need to
> > pop the restore token and additional information from shadow stack.
> > For x86-64, we just need to get the previous shadow stack pointer from
> > the restore token.  For i386, shadow stack is unsupported.
> >
> > To be compatible with the old unwinder which doesn't use the restore
> > token to skip shadow stack frames used by signal handler, Linux kernel
> > won't put additional information after the restore token by default.
> > Define __cet_features to 1 to indicate that unwinder uses the restore
> > token to skip shadow stack frames used by signal handler.  It can be
> > checked by glibc before enabling additional information in shadow stack
> > for signal handler.
>
> Doesn’t the check need to be two ways
> To support kernels not doing this?  Or
> Not do it if the high bit isn’t set?

The updated unwinder works with and without additional information
after the restore token on signal handler shadow stack.  By default,
kernel won't put anything after the restore token.  Glibc will issue a
syscall to enable additional information for the updated unwinder.   If
the syscall fails because of the older kernel, the updated unwinder
still works.  The updated kernel may put additional information after
the restore token for the updated unwinder.

Here is the prototype glibc patch:

https://gitlab.com/x86-glibc/glibc/-/commit/45b26655c775e8f23408c38d2432d0c72720ba42

> Richard
>
> >    * config/i386/libgcc-glibc.ver: Add __cet_features to
> >    GCC_CET_FEATURES.
> >    * config/i386/shadow-stack-unwind.h (_Unwind_Frames_Increment):
> >    Only define for x86-64.  Get the previous shadow stack pointer
> >    from the restore token and skip to the previous frame.
> >    (__cet_features): New.
> > ---
> > libgcc/config/i386/libgcc-glibc.ver      |  4 ++
> > libgcc/config/i386/shadow-stack-unwind.h | 84 +++++++-----------------
> > 2 files changed, 29 insertions(+), 59 deletions(-)
> >
> > diff --git a/libgcc/config/i386/libgcc-glibc.ver b/libgcc/config/i386/libgcc-glibc.ver
> > index 1c4665719da..9a8525757a4 100644
> > --- a/libgcc/config/i386/libgcc-glibc.ver
> > +++ b/libgcc/config/i386/libgcc-glibc.ver
> > @@ -152,6 +152,10 @@ GCC_4.8.0 {
> >   __cpu_model
> >   __cpu_indicator_init
> > }
> > +
> > +GCC_CET_FEATURES {
> > +  __cet_features
> > +}
> > %else
> > GCC_4.4.0 {
> >   __addtf3
> > diff --git a/libgcc/config/i386/shadow-stack-unwind.h b/libgcc/config/i386/shadow-stack-unwind.h
> > index e07ab4a10e4..afcce4b482d 100644
> > --- a/libgcc/config/i386/shadow-stack-unwind.h
> > +++ b/libgcc/config/i386/shadow-stack-unwind.h
> > @@ -43,18 +43,15 @@ see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see
> >     }                        \
> >     while (0)
> >
> > -/* Linux CET kernel places a restore token on shadow stack for signal
> > -   handler to enhance security.  The restore token is 8 byte and aligned
> > -   to 8 bytes.  It is usually transparent to user programs since kernel
> > -   will pop the restore token when signal handler returns.  But when an
> > -   exception is thrown from a signal handler, now we need to pop the
> > -   restore token from shadow stack.  For x86-64, we just need to treat
> > -   the signal frame as normal frame.  For i386, we need to search for
> > -   the restore token to check if the original shadow stack is 8 byte
> > -   aligned.  If the original shadow stack is 8 byte aligned, we just
> > -   need to pop 2 slots, one restore token, from shadow stack.  Otherwise,
> > -   we need to pop 3 slots, one restore token + 4 byte padding, from
> > -   shadow stack.
> > +/* Linux CET kernel places a restore token on shadow stack followed by
> > +   additional information for signal handler to enhance security.  The
> > +   restore token is the previous shadow stack pointer with bit 63 set.
> > +   It is usually transparent to user programs since kernel will pop the
> > +   restore token and additional information when signal handler returns.
> > +   But when an exception is thrown from a signal handler, now we need to
> > +   pop the restore token and additional information from shadow stack.
> > +   For x86-64, we just need to get the previous shadow stack pointer from
> > +   the restore token.  For i386, shadow stack is unsupported.
> >
> >    When popping a stack frame, we compare the return address on normal
> >    stack against the return address on shadow stack.  If they don't match,
> > @@ -66,65 +63,34 @@ see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see
> >    3. Signal stack frame since kernel puts a restore token on shadow
> >       stack.
> >  */
> > -#undef _Unwind_Frames_Increment
> > #ifdef __x86_64__
> > +#undef _Unwind_Frames_Increment
> > #define _Unwind_Frames_Increment(exc, context, frames)    \
> >     {                            \
> >       frames++;                        \
> > -      if (exc->exception_class != 0            \
> > -      && _Unwind_GetIP (context) != 0        \
> > -      && !_Unwind_IsSignalFrame (context))        \
> > +      _Unwind_Word ssp = _get_ssp ();            \
> > +      if (ssp != 0)                    \
> >    {                        \
> > -      _Unwind_Word ssp = _get_ssp ();        \
> > -      if (ssp != 0)                    \
> > +      ssp += 8 * frames;            \
> > +      if (_Unwind_IsSignalFrame (context))        \
> >        {                        \
> > -          ssp += 8 * frames;            \
> > -          _Unwind_Word ra = *(_Unwind_Word *) ssp;    \
> > -          if (ra != _Unwind_GetIP (context))    \
> > -        return _URC_FATAL_PHASE2_ERROR;        \
> > +          /* Get the previous SSP.  */        \
> > +          _Unwind_Word prev_ssp            \
> > +        = ((*(_Unwind_Word *) ssp)        \
> > +           & ~0x8000000000000000LL);        \
> > +          /* Skip to the previous frame.  */    \
> > +          frames += (prev_ssp - ssp) / 8 - 1;    \
> >        }                        \
> > -    }                        \
> > -    }
> > -#else
> > -#define _Unwind_Frames_Increment(exc, context, frames)    \
> > -  if (_Unwind_IsSignalFrame (context))            \
> > -    do                            \
> > -      {                            \
> > -    _Unwind_Word ssp, prev_ssp, token;        \
> > -    ssp = _get_ssp ();                \
> > -    if (ssp != 0)                    \
> > -      {                        \
> > -        /* Align shadow stack pointer to the next    \
> > -           8 byte aligned boundary.  */        \
> > -        ssp = (ssp + 4) & ~7;            \
> > -        do                        \
> > -          {                        \
> > -        /* Look for a restore token.  */    \
> > -        token = (*(_Unwind_Word *) (ssp - 8));    \
> > -        prev_ssp = token & ~7;            \
> > -        if (prev_ssp == ssp)            \
> > -          break;                \
> > -        ssp += 8;                \
> > -          }                        \
> > -        while (1);                    \
> > -        frames += (token & 0x4) ? 3 : 2;        \
> > -      }                        \
> > -      }                            \
> > -    while (0);                        \
> > -  else                            \
> > -    {                            \
> > -      frames++;                        \
> > -      if (exc->exception_class != 0            \
> > -      && _Unwind_GetIP (context) != 0)        \
> > -    {                        \
> > -      _Unwind_Word ssp = _get_ssp ();        \
> > -      if (ssp != 0)                    \
> > +      else if (_Unwind_GetIP (context) != 0        \
> > +           && exc->exception_class != 0)    \
> >        {                        \
> > -          ssp += 4 * frames;            \
> >          _Unwind_Word ra = *(_Unwind_Word *) ssp;    \
> >          if (ra != _Unwind_GetIP (context))    \
> >        return _URC_FATAL_PHASE2_ERROR;        \
> >        }                        \
> >    }                        \
> >     }
> > +
> > +/* Bit 0: Unwinder uses the restore token in signal frame.  */
> > +const int __cet_features = 1;
> > #endif
> > --
> > 2.43.0
> >
diff mbox series

Patch

diff --git a/libgcc/config/i386/libgcc-glibc.ver b/libgcc/config/i386/libgcc-glibc.ver
index 1c4665719da..9a8525757a4 100644
--- a/libgcc/config/i386/libgcc-glibc.ver
+++ b/libgcc/config/i386/libgcc-glibc.ver
@@ -152,6 +152,10 @@  GCC_4.8.0 {
   __cpu_model
   __cpu_indicator_init
 }
+
+GCC_CET_FEATURES {
+  __cet_features
+}
 %else
 GCC_4.4.0 {
   __addtf3
diff --git a/libgcc/config/i386/shadow-stack-unwind.h b/libgcc/config/i386/shadow-stack-unwind.h
index e07ab4a10e4..afcce4b482d 100644
--- a/libgcc/config/i386/shadow-stack-unwind.h
+++ b/libgcc/config/i386/shadow-stack-unwind.h
@@ -43,18 +43,15 @@  see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see
     }						\
     while (0)
 
-/* Linux CET kernel places a restore token on shadow stack for signal
-   handler to enhance security.  The restore token is 8 byte and aligned
-   to 8 bytes.  It is usually transparent to user programs since kernel
-   will pop the restore token when signal handler returns.  But when an
-   exception is thrown from a signal handler, now we need to pop the
-   restore token from shadow stack.  For x86-64, we just need to treat
-   the signal frame as normal frame.  For i386, we need to search for
-   the restore token to check if the original shadow stack is 8 byte
-   aligned.  If the original shadow stack is 8 byte aligned, we just
-   need to pop 2 slots, one restore token, from shadow stack.  Otherwise,
-   we need to pop 3 slots, one restore token + 4 byte padding, from
-   shadow stack.
+/* Linux CET kernel places a restore token on shadow stack followed by
+   additional information for signal handler to enhance security.  The
+   restore token is the previous shadow stack pointer with bit 63 set.
+   It is usually transparent to user programs since kernel will pop the
+   restore token and additional information when signal handler returns.
+   But when an exception is thrown from a signal handler, now we need to
+   pop the restore token and additional information from shadow stack.
+   For x86-64, we just need to get the previous shadow stack pointer from
+   the restore token.  For i386, shadow stack is unsupported.
 
    When popping a stack frame, we compare the return address on normal
    stack against the return address on shadow stack.  If they don't match,
@@ -66,65 +63,34 @@  see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see
    3. Signal stack frame since kernel puts a restore token on shadow
       stack.
  */
-#undef _Unwind_Frames_Increment
 #ifdef __x86_64__
+#undef _Unwind_Frames_Increment
 #define _Unwind_Frames_Increment(exc, context, frames)	\
     {							\
       frames++;						\
-      if (exc->exception_class != 0			\
-	  && _Unwind_GetIP (context) != 0		\
-	  && !_Unwind_IsSignalFrame (context))		\
+      _Unwind_Word ssp = _get_ssp ();			\
+      if (ssp != 0)					\
 	{						\
-	  _Unwind_Word ssp = _get_ssp ();		\
-	  if (ssp != 0)					\
+	  ssp += 8 * frames;			\
+	  if (_Unwind_IsSignalFrame (context))		\
 	    {						\
-	      ssp += 8 * frames;			\
-	      _Unwind_Word ra = *(_Unwind_Word *) ssp;	\
-	      if (ra != _Unwind_GetIP (context))	\
-		return _URC_FATAL_PHASE2_ERROR;		\
+	      /* Get the previous SSP.  */		\
+	      _Unwind_Word prev_ssp			\
+		= ((*(_Unwind_Word *) ssp)		\
+		   & ~0x8000000000000000LL);		\
+	      /* Skip to the previous frame.  */	\
+	      frames += (prev_ssp - ssp) / 8 - 1;	\
 	    }						\
-	}						\
-    }
-#else
-#define _Unwind_Frames_Increment(exc, context, frames)	\
-  if (_Unwind_IsSignalFrame (context))			\
-    do							\
-      {							\
-	_Unwind_Word ssp, prev_ssp, token;		\
-	ssp = _get_ssp ();				\
-	if (ssp != 0)					\
-	  {						\
-	    /* Align shadow stack pointer to the next	\
-	       8 byte aligned boundary.  */		\
-	    ssp = (ssp + 4) & ~7;			\
-	    do						\
-	      {						\
-		/* Look for a restore token.  */	\
-		token = (*(_Unwind_Word *) (ssp - 8));	\
-		prev_ssp = token & ~7;			\
-		if (prev_ssp == ssp)			\
-		  break;				\
-		ssp += 8;				\
-	      }						\
-	    while (1);					\
-	    frames += (token & 0x4) ? 3 : 2;		\
-	  }						\
-      }							\
-    while (0);						\
-  else							\
-    {							\
-      frames++;						\
-      if (exc->exception_class != 0			\
-	  && _Unwind_GetIP (context) != 0)		\
-	{						\
-	  _Unwind_Word ssp = _get_ssp ();		\
-	  if (ssp != 0)					\
+	  else if (_Unwind_GetIP (context) != 0		\
+		   && exc->exception_class != 0)	\
 	    {						\
-	      ssp += 4 * frames;			\
 	      _Unwind_Word ra = *(_Unwind_Word *) ssp;	\
 	      if (ra != _Unwind_GetIP (context))	\
 		return _URC_FATAL_PHASE2_ERROR;		\
 	    }						\
 	}						\
     }
+
+/* Bit 0: Unwinder uses the restore token in signal frame.  */
+const int __cet_features = 1;
 #endif