From bf6465d0461234ccd45ae34d5e2375a0bee0081d Mon Sep 17 00:00:00 2001 From: "H.J. Lu" Date: Mon, 10 Feb 2020 07:58:45 -0800 Subject: [PATCH] i386: Properly pop restore token in signal frame 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. This patch also includes 2 tests, one has a restore token with 4 byte padding and one without. Tested on Linux/x86-64 CET machine with and without -m32. libgcc/ PR libgcc/85334 * config/i386/shadow-stack-unwind.h (_Unwind_Frames_Increment): New. gcc/testsuite/ PR libgcc/85334 * g++.target/i386/pr85334-1.C: New test. * g++.target/i386/pr85334-2.C: Likewise. --- gcc/ChangeLog | 6 ++++ gcc/testsuite/ChangeLog | 6 ++++ gcc/testsuite/g++.target/i386/pr85334-1.C | 55 +++++++++++++++++++++++++++++++ gcc/testsuite/g++.target/i386/pr85334-2.C | 48 +++++++++++++++++++++++++++ libgcc/config/i386/shadow-stack-unwind.h | 43 ++++++++++++++++++++++++ 5 files changed, 158 insertions(+) create mode 100644 gcc/testsuite/g++.target/i386/pr85334-1.C create mode 100644 gcc/testsuite/g++.target/i386/pr85334-2.C diff --git a/gcc/ChangeLog b/gcc/ChangeLog index 60bd347..e416516 100644 --- a/gcc/ChangeLog +++ b/gcc/ChangeLog @@ -1,3 +1,9 @@ +2020-02-10 H.J. Lu + + PR libgcc/85334 + * config/i386/shadow-stack-unwind.h (_Unwind_Frames_Increment): + New. + 2020-02-10 Richard Earnshaw PR target/91913 diff --git a/gcc/testsuite/ChangeLog b/gcc/testsuite/ChangeLog index b3966ec..64835ff 100644 --- a/gcc/testsuite/ChangeLog +++ b/gcc/testsuite/ChangeLog @@ -1,3 +1,9 @@ +2020-02-10 H.J. Lu + + PR libgcc/85334 + * g++.target/i386/pr85334-1.C: New test. + * g++.target/i386/pr85334-2.C: Likewise. + 2020-02-10 Jakub Jelinek PR other/93641 diff --git a/gcc/testsuite/g++.target/i386/pr85334-1.C b/gcc/testsuite/g++.target/i386/pr85334-1.C new file mode 100644 index 0000000..3c5ccad --- /dev/null +++ b/gcc/testsuite/g++.target/i386/pr85334-1.C @@ -0,0 +1,55 @@ +// { dg-do run } +// { dg-require-effective-target cet } +// { dg-additional-options "-fexceptions -fnon-call-exceptions -fcf-protection" } + +// Delta between numbers of call stacks of pr85334-1.C and pr85334-2.C is 1. + +#include +#include + +void sighandler (int signo, siginfo_t * si, void * uc) +{ + throw (5); +} + +char * +__attribute ((noinline, noclone)) +dosegv () +{ + * ((volatile int *)0) = 12; + return 0; +} + +int +__attribute ((noinline, noclone)) +func2 () +{ + try { + dosegv (); + } + catch (int x) { + return (x != 5); + } + return 1; +} + +int +__attribute ((noinline, noclone)) +func1 () +{ + return func2 (); +} + +int main () +{ + struct sigaction sa; + int status; + + sa.sa_sigaction = sighandler; + sa.sa_flags = SA_SIGINFO; + + status = sigaction (SIGSEGV, & sa, NULL); + status = sigaction (SIGBUS, & sa, NULL); + + return func1 (); +} diff --git a/gcc/testsuite/g++.target/i386/pr85334-2.C b/gcc/testsuite/g++.target/i386/pr85334-2.C new file mode 100644 index 0000000..e2b5afe --- /dev/null +++ b/gcc/testsuite/g++.target/i386/pr85334-2.C @@ -0,0 +1,48 @@ +// { dg-do run } +// { dg-require-effective-target cet } +// { dg-additional-options "-fexceptions -fnon-call-exceptions -fcf-protection" } + +// Delta between numbers of call stacks of pr85334-1.C and pr85334-2.C is 1. + +#include +#include + +void sighandler (int signo, siginfo_t * si, void * uc) +{ + throw (5); +} + +char * +__attribute ((noinline, noclone)) +dosegv () +{ + * ((volatile int *)0) = 12; + return 0; +} + +int +__attribute ((noinline, noclone)) +func1 () +{ + try { + dosegv (); + } + catch (int x) { + return (x != 5); + } + return 1; +} + +int main () +{ + struct sigaction sa; + int status; + + sa.sa_sigaction = sighandler; + sa.sa_flags = SA_SIGINFO; + + status = sigaction (SIGSEGV, & sa, NULL); + status = sigaction (SIGBUS, & sa, NULL); + + return func1 (); +} diff --git a/libgcc/config/i386/shadow-stack-unwind.h b/libgcc/config/i386/shadow-stack-unwind.h index a0244d2e..201b2153 100644 --- a/libgcc/config/i386/shadow-stack-unwind.h +++ b/libgcc/config/i386/shadow-stack-unwind.h @@ -49,3 +49,46 @@ 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. */ +#ifndef __x86_64__ +#undef _Unwind_Frames_Increment +#define _Unwind_Frames_Increment(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++; +#endif -- 2.7.4