x86: Use pad in pthread_unwind_buf to preserve shadow stack register
Commit Message
On Fri, Apr 6, 2018 at 5:59 AM, H.J. Lu <hjl.tools@gmail.com> wrote:
> On Thu, Apr 5, 2018 at 9:46 PM, Carlos O'Donell <carlos@redhat.com> wrote:
>> On 03/30/2018 12:41 PM, H.J. Lu wrote:
>>> On Thu, Mar 29, 2018 at 1:20 PM, Florian Weimer <fw@deneb.enyo.de> wrote:
>>>> * H. J. Lu:
>>>>
>>>>> On Thu, Mar 29, 2018 at 1:15 PM, Florian Weimer <fw@deneb.enyo.de> wrote:
>>>>>> * H. J. Lu:
>>>>>>
>>>>>>> You need to make a choice. You either don't introduce a new symbol
>>>>>>> version or don't save shadow stack for thread cancellation. You
>>>>>>> can't have both.
>>>>>> I don't understand. We have room to save the shadow stack pointer in
>>>>>> the existing struct.
>>>>> No, we don't have room in struct pthread_unwind_buf:
>>>>>
>>>>> Note: There is an unused pointer space in pthread_unwind_buf_data. But
>>>>> it isn't suitable for saving and restoring shadow stack register since
>>>>> x32 is a 64-bit process with 32-bit software pointer and kernel may
>>>>> place x32 shadow stack above 4GB. We need to save and restore 64-bit
>>>>> shadow stack register for x32.
>>>> We have for void * fields. They are subsequently overwritten by
>>>> __pthread_register_cancel. But __sigsetjmp can write to them first
>>>> without causing any harm. We just need a private __longjmp_cancel
>>>> that doesn't restore the shadow stack pointer.
>>> Here is the patch which does that. Any comments?
>>
>> OK, so I have reviewed https://github.com/hjl-tools/glibc/ hjl/cet/master,
>> please confirm that this is the correct branch of the required implementation
>> for CET. It really helps to review the rest of the patch set, you should be
>> preparing this as a patch set instead of having it reviewed one-at-a-time.
>> This issue was already raised in the thread with Zack.
>
> Thanks for your feedbacks.
>
>> Architecture:
>>
>> * We avoid a "flag day" with feature_1 TCB flag to switch to a new ABI,
>> which we have discussed is a fragile process which should be avoided if
>> a supportable alternative solution exists.
>>
>> * We avoid versioned symbols, this makes CET backportable, and this has a
>> bigger benefit for long-term stable distributions.
>>
>> * A key design problem has been that cancellation jump buffers within glibc
>> are truncated to optimize on-stack size, and this means that setjmp could
>> write beyond the structure because setjmp now tries to save the shadowstack
>> pointer into space that the cancellation jump buffer did not allocate.
>> For the record this optimization seems premature and I'm sad we did it, this
>> is a lesson we should learn from.
>>
>> * We have all agreed to the following concepts:
>>
>> * The cancellation process, in particular the unwinder, never returns from
>> any of the functions we call, it just keeps calling into the unwinder to
>> jump to the next unwound cancellation function all the way to the thread
>> start routine. Therefore because we never return from one of these functions
>> we never need to restore the shadow stack, and consequently wherever it is
>> stored in the cancellation jump buffer can be overwritten if we need the
>> space (it's a dead store).
>>
>> * The corollary to this is that function calls made from cancellation handlers
>> will continue to advance the shadowstack from the deepest point at which
>> cancellation is initiated from. This means that the depth of the shadowstack
>> doesn't match the depth of the real stack while we are unwinding. I don't
>> know if this will have consequences on analysis tooling or not, or debug
>> tools during unwinding. It's a fairly advanced situation and corner case,
>> and restoring the shadowstack is not useful becuase we don't need it and
>> simplifies the implementation.
>>
>> * The cancellation jump buffer has private data used for chaining the cancel
>> jump buffers together such that the custom unwinder can follow them and
>> call them in sequence. This space constitutes 4 void *'s which is space
>> that setjmp can write to, because we will just overwrite it when we register
>> the cancel handler.
>>
>> * If the new shadowstack-enabled setjmp stores the shadowstack pointer into
>> the space taken by the 4 void*'s then we won't overflow the stack, and we
>> don't need to change the layout of the cancellation jump buffer. The 4 void*'s
>> are sufficient, even for x32 to write a 64-bit shadow stack address.
>>
>> * After fixing the cancellation jump buffers the following work needs to be reviewed:
>>
>> * Add feature_1 in tcb head to track CET status and make it easily available
>> to runtime for checking.
>>
>> * Save and restore shadowstack in setjmp/longjmp.
>>
>> * Add CET support to ld.so et. al. and track runtime status.
>>
>> * Adjust vfork for shadow stack usage.
>>
>> * Add ENDBR or NOTRACK where required in assembly.
>>
>> * CET and makecontext incompatible.
>> - Probably need to discuss which default is appropriate.
>> - Should the user get CET automatically disabled in makecontext() et. al. silently?
>> - Should your current solution, which is to error out during the build, and require
>> flag changes, be the default? This forces the user to review the security for their
>> application.
>
> I'd like to reserve 4 slots in ucontext for shadow stack:
>
> https://github.com/hjl-tools/glibc/commit/9bf6aefa8fb45f8df140d42ce9cf890bb24076e1
>
> It should be binary backward compatible. I will investigate if there is a way
> to support shadow stack with existing API. Otherwise, we need to add a new
> API for ucontext functions with shadow stack.
>
>> * prctl for CET.
>
> We have been experimenting different approaches to get the best implementation.
> I am expecting that this patch may change as we collect more data.
>
>> * The work to review after this patch appears to be less contentious in terms of
>> the kinds of changes that are required. Most of the changes are internal details
>> of enabling CET and not ABI details, with the exception of the possible pain we
>> might cause with makecontext() being unsupported and what default position to take
>> there.
>>
>> Design:
>>
>> * Overall the implementation looks exactly how I might expect it to look, but some
>> of the math that places the shadowstack pointer appears to need either commenting
>> or fixing because I don't understand it. You need to make it easy for me to see
>> that we have placed the shadowstack pointer into the 4 pad words.
>>
>> Details:
>>
>> * One comment needs filling out a bit more, noted below.
>>
>>> 0001-x86-Use-pad-in-pthread_unwind_buf-to-preserve-shadow.patch
>>>
>>>
>>> From f222537447f5ec879427f318b7c0396362b7453a Mon Sep 17 00:00:00 2001
>>> From: "H.J. Lu" <hjl.tools@gmail.com>
>>> Date: Sat, 24 Feb 2018 17:23:54 -0800
>>> Subject: [PATCH] x86: Use pad in pthread_unwind_buf to preserve shadow stack
>>> register
>>>
>>> The pad array in struct pthread_unwind_buf is used by setjmp to save
>>> shadow stack register. We assert that size of struct pthread_unwind_buf
>>> is no less than offset of shadow stack pointer + shadow stack pointer
>>> size.
>>>
>>
>> OK.
>>
>>> Since functions, like LIBC_START_MAIN, START_THREAD_DEFN as well as
>>> these with thread cancellation, call setjmp, but never return after
>>> __libc_unwind_longjmp, __libc_unwind_longjmp, which is defined as
>>> __libc_longjmp on x86, doesn't need to restore shadow stack register.
>>
>> OK.
>>
>>> __libc_longjmp, which is a private interface for thread cancellation
>>> implementation in libpthread, is changed to call __longjmp_cancel,
>>> instead of __longjmp. __longjmp_cancel is a new internal function
>>> in libc, which is similar to __longjmp, but doesn't restore shadow
>>> stack register.
>>
>> OK. Good. I like the use of a __longjmp_cancel name to call out what's
>> going on in the API (clear semantics).
>>
>>>
>>> The compatibility longjmp and siglongjmp in libpthread.so are changed
>>> to call __libc_siglongjmp, instead of __libc_longjmp, so that they will
>>> restore shadow stack register.
>>
>> OK.
>>
>>>
>>> Tested with build-many-glibcs.py.
>>>
>>> * nptl/pthread_create.c (START_THREAD_DEFN): Clear previous
>>> handlers after setjmp.
>>> * setjmp/longjmp.c (__libc_longjmp): Don't define alias if
>>> defined.
>>> * sysdeps/unix/sysv/linux/x86/setjmpP.h (_JUMP_BUF_SIGSET_NSIG):
>>> Changed to 97.
>>> * sysdeps/x86/Makefile (sysdep_routines): Add __longjmp_cancel.
>>> * sysdeps/x86/__longjmp_cancel.S: New file.
>>> * sysdeps/x86/longjmp.c: Likewise.
>>> * sysdeps/x86/nptl/pt-longjmp.c: Likewise.
>>
>> This looks much better.
>>
>>> ---
>>> nptl/pthread_create.c | 9 ++--
>>> setjmp/longjmp.c | 2 +
>>> sysdeps/unix/sysv/linux/x86/setjmpP.h | 4 +-
>>> sysdeps/x86/Makefile | 4 ++
>>> sysdeps/x86/__longjmp_cancel.S | 20 ++++++++
>>> sysdeps/x86/longjmp.c | 45 ++++++++++++++++
>>> sysdeps/x86/nptl/pt-longjmp.c | 97 +++++++++++++++++++++++++++++++++++
>>> 7 files changed, 176 insertions(+), 5 deletions(-)
>>> create mode 100644 sysdeps/x86/__longjmp_cancel.S
>>> create mode 100644 sysdeps/x86/longjmp.c
>>> create mode 100644 sysdeps/x86/nptl/pt-longjmp.c
>>>
>>> diff --git a/nptl/pthread_create.c b/nptl/pthread_create.c
>>> index caaf07c134..1c5b3780c6 100644
>>> --- a/nptl/pthread_create.c
>>> +++ b/nptl/pthread_create.c
>>> @@ -427,12 +427,15 @@ START_THREAD_DEFN
>>> compilers without that support we do use setjmp. */
>>> struct pthread_unwind_buf unwind_buf;
>>>
>>> - /* No previous handlers. */
>>> + int not_first_call;
>>> + not_first_call = setjmp ((struct __jmp_buf_tag *) unwind_buf.cancel_jmp_buf);
>>> +
>>> + /* No previous handlers. NB: This must be done after setjmp since
>>> + the same space may be used by setjmp to store extra data which
>>> + should never be used by __libc_unwind_longjmp. */
>>
>> Suggest:
>> ~~~
>> No previous handlers. NB: This must be done after setjmp since
>> the private space in the unwind jump buffer may overlap space
>> used by setjmp to store extra architecture-specific information
>> which is never be used by the cancellation-specific
>> __libc_unwind_longjmp.
>>
>> The private space is allowed to overlap because the unwinder never
>> has to return through any of the jumped-to call frames, and thus
>> only a minimum amount of saved data need be stored, and for example,
>> need not include the process signal mask information. This is all
>> an optimization to reduce stack usage when pushing cancellation
>> handlers.
>> ~~~
>
> Will fix it.
>
>>> unwind_buf.priv.data.prev = NULL;
>>> unwind_buf.priv.data.cleanup = NULL;
>>>
>>> - int not_first_call;
>>> - not_first_call = setjmp ((struct __jmp_buf_tag *) unwind_buf.cancel_jmp_buf);
>>
>> OK.
>>
>>> if (__glibc_likely (! not_first_call))
>>> {
>>> /* Store the new cleanup handler info. */
>>> diff --git a/setjmp/longjmp.c b/setjmp/longjmp.c
>>> index a2a7065a85..453889e103 100644
>>> --- a/setjmp/longjmp.c
>>> +++ b/setjmp/longjmp.c
>>> @@ -40,9 +40,11 @@ __libc_siglongjmp (sigjmp_buf env, int val)
>>> }
>>>
>>> #ifndef __libc_siglongjmp
>>> +# ifndef __libc_longjmp
>>> /* __libc_longjmp is a private interface for cancellation implementation
>>> in libpthread. */
>>> strong_alias (__libc_siglongjmp, __libc_longjmp)
>>> +# endif
>>
>> OK.
>>
>>> weak_alias (__libc_siglongjmp, _longjmp)
>>> weak_alias (__libc_siglongjmp, longjmp)
>>> weak_alias (__libc_siglongjmp, siglongjmp)
>>> diff --git a/sysdeps/unix/sysv/linux/x86/setjmpP.h b/sysdeps/unix/sysv/linux/x86/setjmpP.h
>>> index c0ed767a0d..90a6bbcf32 100644
>>> --- a/sysdeps/unix/sysv/linux/x86/setjmpP.h
>>> +++ b/sysdeps/unix/sysv/linux/x86/setjmpP.h
>>> @@ -22,8 +22,8 @@
>>> #include <bits/types/__sigset_t.h>
>>>
>>> /* The biggest signal number + 1. As of kernel 4.14, x86 _NSIG is 64.
>>> - Define it to 513 to leave some rooms for future use. */
>>> -#define _JUMP_BUF_SIGSET_NSIG 513
>>> + Define it to 97 to leave some rooms for future use. */
>>
>> OK.
>>
>>> +#define _JUMP_BUF_SIGSET_NSIG 97
>>
>> Please provide proof in the way of a comment or rewriting this constant
>> to show that it places the shadow stack pointer on both x86_64 and x32
>> into the range of the private pad.
>
> sysdeps/x86/nptl/pt-longjmp.c has
>
> _Static_assert ((sizeof (struct pthread_unwind_buf)
>>= (SHADOW_STACK_POINTER_OFFSET
> + SHADOW_STACK_POINTER_SIZE)),
> "size of struct pthread_unwind_buf < "
> "(SHADOW_STACK_POINTER_OFFSET + SHADOW_STACK_POINTER_SIZE)");
>
> If shadow stack pointer is saved in the offset bigger than the size of
> struct pthread_unwind_buf, assert will trigger at compile-time.
>
>> Also, from commit f33632ccd1dec3217583fcfdd965afb62954203c,
>> where did this math come from?
>>
>> ((_JUMP_BUF_SIGSET_NSIG - 1 + 7) / (8 * sizeof (unsigned long int)))
>>
>> Why the +7?
>
> _JUMP_BUF_SIGSET_NSIG is the biggest signal number + 1.
> _JUMP_BUF_SIGSET_NSIG - 1 gives the biggest signal number.
> _JUMP_BUF_SIGSET_NSIG - 1 + 7 rounds up to the number of bytes
> which are needed to store the biggest signal number.
>
>>> /* Number of longs to hold all signals. */
>>> #define _JUMP_BUF_SIGSET_NWORDS \
>>> ((_JUMP_BUF_SIGSET_NSIG - 1 + 7) / (8 * sizeof (unsigned long int)))
>>> diff --git a/sysdeps/x86/Makefile b/sysdeps/x86/Makefile
>>> index 0d0326c21a..d25d6f0ae4 100644
>>> --- a/sysdeps/x86/Makefile
>>> +++ b/sysdeps/x86/Makefile
>>> @@ -8,3 +8,7 @@ sysdep-dl-routines += dl-get-cpu-features
>>> tests += tst-get-cpu-features
>>> tests-static += tst-get-cpu-features-static
>>> endif
>>> +
>>> +ifeq ($(subdir),setjmp)
>>> +sysdep_routines += __longjmp_cancel
>>> +endif
>>
>> OK.
>>
>>> diff --git a/sysdeps/x86/__longjmp_cancel.S b/sysdeps/x86/__longjmp_cancel.S
>>> new file mode 100644
>>> index 0000000000..b57dbfa376
>>> --- /dev/null
>>> +++ b/sysdeps/x86/__longjmp_cancel.S
>>> @@ -0,0 +1,20 @@
>>> +/* __longjmp_cancel for x86.
>>> + Copyright (C) 2018 Free Software Foundation, Inc.
>>> + This file is part of the GNU C Library.
>>> +
>>> + The GNU C Library is free software; you can redistribute it and/or
>>> + modify it under the terms of the GNU Lesser General Public
>>> + License as published by the Free Software Foundation; either
>>> + version 2.1 of the License, or (at your option) any later version.
>>> +
>>> + The GNU C Library is distributed in the hope that it will be useful,
>>> + but WITHOUT ANY WARRANTY; without even the implied warranty of
>>> + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
>>> + Lesser General Public License for more details.
>>> +
>>> + You should have received a copy of the GNU Lesser General Public
>>> + License along with the GNU C Library; if not, see
>>> + <http://www.gnu.org/licenses/>. */
>>> +
>>> +#define __longjmp __longjmp_cancel
>>> +#include <__longjmp.S>
>>
>> OK.
>>
>>> diff --git a/sysdeps/x86/longjmp.c b/sysdeps/x86/longjmp.c
>>> new file mode 100644
>>> index 0000000000..a53f31e1dd
>>> --- /dev/null
>>> +++ b/sysdeps/x86/longjmp.c
>>> @@ -0,0 +1,45 @@
>>> +/* __libc_siglongjmp for x86.
>>> + Copyright (C) 2018 Free Software Foundation, Inc.
>>> + This file is part of the GNU C Library.
>>> +
>>> + The GNU C Library is free software; you can redistribute it and/or
>>> + modify it under the terms of the GNU Lesser General Public
>>> + License as published by the Free Software Foundation; either
>>> + version 2.1 of the License, or (at your option) any later version.
>>> +
>>> + The GNU C Library is distributed in the hope that it will be useful,
>>> + but WITHOUT ANY WARRANTY; without even the implied warranty of
>>> + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
>>> + Lesser General Public License for more details.
>>> +
>>> + You should have received a copy of the GNU Lesser General Public
>>> + License along with the GNU C Library; if not, see
>>> + <http://www.gnu.org/licenses/>. */
>>> +
>>> +#define __libc_longjmp __redirect___libc_longjmp
>>> +#include <setjmp/longjmp.c>
>>> +#undef __libc_longjmp
>>> +
>>> +extern void __longjmp_cancel (__jmp_buf __env, int __val)
>>> + __attribute__ ((__noreturn__)) attribute_hidden;
>>> +
>>> +/* Since __libc_longjmp is a private interface for cancellation
>>> + implementation in libpthread, there is no need to restore shadow
>>> + stack register. */
>>> +
>>> +void
>>> +__libc_longjmp (sigjmp_buf env, int val)
>>> +{
>>> + /* Perform any cleanups needed by the frames being unwound. */
>>> + _longjmp_unwind (env, val);
>>
>> OK.
>>
>>> +
>>> + if (env[0].__mask_was_saved)
>>> + /* Restore the saved signal mask. */
>>> + (void) __sigprocmask (SIG_SETMASK,
>>> + (sigset_t *) &env[0].__saved_mask,
>>> + (sigset_t *) NULL);
>>
>> OK.
>>
>>> +
>>> + /* Call the machine-dependent function to restore machine state
>>> + without shadow stack. */
>>> + __longjmp_cancel (env[0].__jmpbuf, val ?: 1);
>>
>> OK.
>>
>>> +}
>>> diff --git a/sysdeps/x86/nptl/pt-longjmp.c b/sysdeps/x86/nptl/pt-longjmp.c
>>> new file mode 100644
>>> index 0000000000..7eb8651cfe
>>> --- /dev/null
>>> +++ b/sysdeps/x86/nptl/pt-longjmp.c
>>> @@ -0,0 +1,97 @@
>>> +/* ABI compatibility for 'longjmp' and 'siglongjmp' symbols in libpthread ABI.
>>> + X86 version.
>>> + Copyright (C) 18 Free Software Foundation, Inc.
>>> + This file is part of the GNU C Library.
>>> +
>>> + The GNU C Library is free software; you can redistribute it and/or
>>> + modify it under the terms of the GNU Lesser General Public
>>> + License as published by the Free Software Foundation; either
>>> + version 2.1 of the License, or (at your option) any later version.
>>> +
>>> + The GNU C Library is distributed in the hope that it will be useful,
>>> + but WITHOUT ANY WARRANTY; without even the implied warranty of
>>> + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
>>> + Lesser General Public License for more details.
>>> +
>>> + You should have received a copy of the GNU Lesser General Public
>>> + License along with the GNU C Library; if not, see
>>> + <http://www.gnu.org/licenses/>. */
>>> +
>>> +/* <nptl/descr.h> has
>>> +
>>> +struct pthread_unwind_buf
>>> +{
>>> + struct
>>> + {
>>> + __jmp_buf jmp_buf;
>>> + int mask_was_saved;
>>> + } cancel_jmp_buf[1];
>>> +
>>> + union
>>> + {
>>> + void *pad[4];
>>> + struct
>>> + {
>>> + struct pthread_unwind_buf *prev;
>>> + struct _pthread_cleanup_buffer *cleanup;
>>> + int canceltype;
>>> + } data;
>>> + } priv;
>>> +};
>>> +
>>> + The pad array in struct pthread_unwind_buf is used by setjmp to save
>>
>> This appears to be SHADOW_STACK_POINTER_OFFSET in subsequent patches.
>>
>> However on your hjl/cet/master branch it appears that this offset is not
>> defined to be *just after* the mask_was_saved?
>
> sysdeps/unix/sysv/linux/x86/setjmpP.h has
>
> typedef struct
> {
> unsigned long int __val[_JUMP_BUF_SIGSET_NWORDS];
> } __jmp_buf_sigset_t;
>
> typedef union
> {
> __sigset_t __saved_mask_compat;
> struct
> {
> __jmp_buf_sigset_t __saved_mask;
> /* Used for shadow stack pointer. */
> unsigned long int __shadow_stack_pointer;
> } __saved;
> } __jmpbuf_arch_t;
>
> __shadow_stack_pointer is placed after __saved_mask, aka mask_was_saved.
>
>>> + shadow stack register. Assert that size of struct pthread_unwind_buf
>>> + is no less than offset of shadow stack pointer plus shadow stack
>>> + pointer size.
>>> +
>>> + NB: setjmp is called in libpthread to save shadow stack register. But
>>> + __libc_unwind_longjmp doesn't restore shadow stack register since they
>>> + never return after longjmp. */
>>> +
>>> +#include <pthreadP.h>
>>> +#include <jmp_buf-ssp.h>
>>> +
>>> +#ifdef __x86_64__
>>> +# define SHADOW_STACK_POINTER_SIZE 8
>>> +#else
>>> +# define SHADOW_STACK_POINTER_SIZE 4
>>> +#endif
>>> +
>>> +_Static_assert ((sizeof (struct pthread_unwind_buf)
>>> + >= (SHADOW_STACK_POINTER_OFFSET
>>> + + SHADOW_STACK_POINTER_SIZE)),
>>> + "size of struct pthread_unwind_buf < "
>>> + "(SHADOW_STACK_POINTER_OFFSET + SHADOW_STACK_POINTER_SIZE)");
>>
>> OK.
>>
>>> +
>>> +#include <shlib-compat.h>
>>> +
>>> +/* libpthread once had its own longjmp (and siglongjmp alias), though there
>>> + was no apparent reason for it. There is no use in having a separate
>>> + symbol in libpthread, but the historical ABI requires it. For static
>>> + linking, there is no need to provide anything here--the libc version
>>> + will be linked in. For shared library ABI compatibility, there must be
>>> + longjmp and siglongjmp symbols in libpthread.so.
>>> +
>>> + With an IFUNC resolver, it would be possible to avoid the indirection,
>>> + but the IFUNC resolver might run before the __libc_longjmp symbol has
>>> + been relocated, in which case the IFUNC resolver would not be able to
>>> + provide the correct address. */
>>> +
>>> +#if SHLIB_COMPAT (libpthread, GLIBC_2_0, GLIBC_2_22)
>>> +
>>> +static void __attribute__ ((noreturn, used))
>>> +longjmp_compat (jmp_buf env, int val)
>>> +{
>>> + /* NB: We call __libc_siglongjmp, instead of __libc_longjmp, since
>>> + __libc_longjmp is a private interface for cancellation which
>>> + doesn't restore shadow stack register. */
>>> + __libc_siglongjmp (env, val);
>>
>> OK.
>>
>>> +}
>>> +
>>> +strong_alias (longjmp_compat, longjmp_alias)
>>> +compat_symbol (libpthread, longjmp_alias, longjmp, GLIBC_2_0);
>>> +
>>> +strong_alias (longjmp_alias, siglongjmp_alias)
>>> +compat_symbol (libpthread, siglongjmp_alias, siglongjmp, GLIBC_2_0);
>>> +
>>> +#endif
>>> -- 2.14.3
>>
>>
Here is the updated patch. OK for master?
I will submit a CET patch set after this patch is merged.
Thanks.
Comments
On 04/06/2018 03:26 PM, H.J. Lu wrote:
> Here is the updated patch. OK for master?
>
> I will submit a CET patch set after this patch is merged.
>
> Thanks.
>
I will wait for a conclusion around the incorrect
_JUMP_BUF_SIGSET_NWORDS computation.
From a1f8a06212dd3c5ed445fdc4e23424b766d41932 Mon Sep 17 00:00:00 2001
From: "H.J. Lu" <hjl.tools@gmail.com>
Date: Sat, 24 Feb 2018 17:23:54 -0800
Subject: [PATCH] x86: Use pad in pthread_unwind_buf to preserve shadow stack
register
The pad array in struct pthread_unwind_buf is used by setjmp to save
shadow stack register. We assert that size of struct pthread_unwind_buf
is no less than offset of shadow stack pointer + shadow stack pointer
size.
Since functions, like LIBC_START_MAIN, START_THREAD_DEFN as well as
these with thread cancellation, call setjmp, but never return after
__libc_unwind_longjmp, __libc_unwind_longjmp, which is defined as
__libc_longjmp on x86, doesn't need to restore shadow stack register.
__libc_longjmp, which is a private interface for thread cancellation
implementation in libpthread, is changed to call __longjmp_cancel,
instead of __longjmp. __longjmp_cancel is a new internal function
in libc, which is similar to __longjmp, but doesn't restore shadow
stack register.
The compatibility longjmp and siglongjmp in libpthread.so are changed
to call __libc_siglongjmp, instead of __libc_longjmp, so that they will
restore shadow stack register.
Tested with build-many-glibcs.py.
* nptl/pthread_create.c (START_THREAD_DEFN): Clear previous
handlers after setjmp.
* setjmp/longjmp.c (__libc_longjmp): Don't define alias if
defined.
* sysdeps/unix/sysv/linux/x86/setjmpP.h (_JUMP_BUF_SIGSET_NSIG):
Changed to 97.
* sysdeps/x86/Makefile (sysdep_routines): Add __longjmp_cancel.
* sysdeps/x86/__longjmp_cancel.S: New file.
* sysdeps/x86/longjmp.c: Likewise.
* sysdeps/x86/nptl/pt-longjmp.c: Likewise.
---
nptl/pthread_create.c | 18 +++++--
setjmp/longjmp.c | 2 +
sysdeps/unix/sysv/linux/x86/setjmpP.h | 4 +-
sysdeps/x86/Makefile | 4 ++
sysdeps/x86/__longjmp_cancel.S | 20 ++++++++
sysdeps/x86/longjmp.c | 45 ++++++++++++++++
sysdeps/x86/nptl/pt-longjmp.c | 97 +++++++++++++++++++++++++++++++++++
7 files changed, 185 insertions(+), 5 deletions(-)
create mode 100644 sysdeps/x86/__longjmp_cancel.S
create mode 100644 sysdeps/x86/longjmp.c
create mode 100644 sysdeps/x86/nptl/pt-longjmp.c
@@ -427,12 +427,24 @@ START_THREAD_DEFN
compilers without that support we do use setjmp. */
struct pthread_unwind_buf unwind_buf;
- /* No previous handlers. */
+ int not_first_call;
+ not_first_call = setjmp ((struct __jmp_buf_tag *) unwind_buf.cancel_jmp_buf);
+
+ /* No previous handlers. NB: This must be done after setjmp since
+ the private space in the unwind jump buffer may overlap space
+ used by setjmp to store extra architecture-specific information
+ which is never be used by the cancellation-specific
+ __libc_unwind_longjmp.
+
+ The private space is allowed to overlap because the unwinder never
+ has to return through any of the jumped-to call frames, and thus
+ only a minimum amount of saved data need be stored, and for example,
+ need not include the process signal mask information. This is all
+ an optimization to reduce stack usage when pushing cancellation
+ handlers. */
unwind_buf.priv.data.prev = NULL;
unwind_buf.priv.data.cleanup = NULL;
- int not_first_call;
- not_first_call = setjmp ((struct __jmp_buf_tag *) unwind_buf.cancel_jmp_buf);
if (__glibc_likely (! not_first_call))
{
/* Store the new cleanup handler info. */
@@ -40,9 +40,11 @@ __libc_siglongjmp (sigjmp_buf env, int val)
}
#ifndef __libc_siglongjmp
+# ifndef __libc_longjmp
/* __libc_longjmp is a private interface for cancellation implementation
in libpthread. */
strong_alias (__libc_siglongjmp, __libc_longjmp)
+# endif
weak_alias (__libc_siglongjmp, _longjmp)
weak_alias (__libc_siglongjmp, longjmp)
weak_alias (__libc_siglongjmp, siglongjmp)
@@ -22,8 +22,8 @@
#include <bits/types/__sigset_t.h>
/* The biggest signal number + 1. As of kernel 4.14, x86 _NSIG is 64.
- Define it to 513 to leave some rooms for future use. */
-#define _JUMP_BUF_SIGSET_NSIG 513
+ Define it to 97 to leave some rooms for future use. */
+#define _JUMP_BUF_SIGSET_NSIG 97
/* Number of longs to hold all signals. */
#define _JUMP_BUF_SIGSET_NWORDS \
((_JUMP_BUF_SIGSET_NSIG - 1 + 7) / (8 * sizeof (unsigned long int)))
@@ -8,3 +8,7 @@ sysdep-dl-routines += dl-get-cpu-features
tests += tst-get-cpu-features
tests-static += tst-get-cpu-features-static
endif
+
+ifeq ($(subdir),setjmp)
+sysdep_routines += __longjmp_cancel
+endif
new file mode 100644
@@ -0,0 +1,20 @@
+/* __longjmp_cancel for x86.
+ Copyright (C) 2018 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <http://www.gnu.org/licenses/>. */
+
+#define __longjmp __longjmp_cancel
+#include <__longjmp.S>
new file mode 100644
@@ -0,0 +1,45 @@
+/* __libc_siglongjmp for x86.
+ Copyright (C) 2018 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <http://www.gnu.org/licenses/>. */
+
+#define __libc_longjmp __redirect___libc_longjmp
+#include <setjmp/longjmp.c>
+#undef __libc_longjmp
+
+extern void __longjmp_cancel (__jmp_buf __env, int __val)
+ __attribute__ ((__noreturn__)) attribute_hidden;
+
+/* Since __libc_longjmp is a private interface for cancellation
+ implementation in libpthread, there is no need to restore shadow
+ stack register. */
+
+void
+__libc_longjmp (sigjmp_buf env, int val)
+{
+ /* Perform any cleanups needed by the frames being unwound. */
+ _longjmp_unwind (env, val);
+
+ if (env[0].__mask_was_saved)
+ /* Restore the saved signal mask. */
+ (void) __sigprocmask (SIG_SETMASK,
+ (sigset_t *) &env[0].__saved_mask,
+ (sigset_t *) NULL);
+
+ /* Call the machine-dependent function to restore machine state
+ without shadow stack. */
+ __longjmp_cancel (env[0].__jmpbuf, val ?: 1);
+}
new file mode 100644
@@ -0,0 +1,97 @@
+/* ABI compatibility for 'longjmp' and 'siglongjmp' symbols in libpthread ABI.
+ X86 version.
+ Copyright (C) 18 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <http://www.gnu.org/licenses/>. */
+
+/* <nptl/descr.h> has
+
+struct pthread_unwind_buf
+{
+ struct
+ {
+ __jmp_buf jmp_buf;
+ int mask_was_saved;
+ } cancel_jmp_buf[1];
+
+ union
+ {
+ void *pad[4];
+ struct
+ {
+ struct pthread_unwind_buf *prev;
+ struct _pthread_cleanup_buffer *cleanup;
+ int canceltype;
+ } data;
+ } priv;
+};
+
+ The pad array in struct pthread_unwind_buf is used by setjmp to save
+ shadow stack register. Assert that size of struct pthread_unwind_buf
+ is no less than offset of shadow stack pointer plus shadow stack
+ pointer size.
+
+ NB: setjmp is called in libpthread to save shadow stack register. But
+ __libc_unwind_longjmp doesn't restore shadow stack register since they
+ never return after longjmp. */
+
+#include <pthreadP.h>
+#include <jmp_buf-ssp.h>
+
+#ifdef __x86_64__
+# define SHADOW_STACK_POINTER_SIZE 8
+#else
+# define SHADOW_STACK_POINTER_SIZE 4
+#endif
+
+_Static_assert ((sizeof (struct pthread_unwind_buf)
+ >= (SHADOW_STACK_POINTER_OFFSET
+ + SHADOW_STACK_POINTER_SIZE)),
+ "size of struct pthread_unwind_buf < "
+ "(SHADOW_STACK_POINTER_OFFSET + SHADOW_STACK_POINTER_SIZE)");
+
+#include <shlib-compat.h>
+
+/* libpthread once had its own longjmp (and siglongjmp alias), though there
+ was no apparent reason for it. There is no use in having a separate
+ symbol in libpthread, but the historical ABI requires it. For static
+ linking, there is no need to provide anything here--the libc version
+ will be linked in. For shared library ABI compatibility, there must be
+ longjmp and siglongjmp symbols in libpthread.so.
+
+ With an IFUNC resolver, it would be possible to avoid the indirection,
+ but the IFUNC resolver might run before the __libc_longjmp symbol has
+ been relocated, in which case the IFUNC resolver would not be able to
+ provide the correct address. */
+
+#if SHLIB_COMPAT (libpthread, GLIBC_2_0, GLIBC_2_22)
+
+static void __attribute__ ((noreturn, used))
+longjmp_compat (jmp_buf env, int val)
+{
+ /* NB: We call __libc_siglongjmp, instead of __libc_longjmp, since
+ __libc_longjmp is a private interface for cancellation which
+ doesn't restore shadow stack register. */
+ __libc_siglongjmp (env, val);
+}
+
+strong_alias (longjmp_compat, longjmp_alias)
+compat_symbol (libpthread, longjmp_alias, longjmp, GLIBC_2_0);
+
+strong_alias (longjmp_alias, siglongjmp_alias)
+compat_symbol (libpthread, siglongjmp_alias, siglongjmp, GLIBC_2_0);
+
+#endif
--
2.14.3