[PATCH v2] x86-64/cet: Check the restore token in longjmp
Noah Goldstein
goldstein.w.n@gmail.com
Thu Jan 4 19:51:00 GMT 2024
On Tue, Jan 2, 2024 at 7:03 AM H.J. Lu <hjl.tools@gmail.com> wrote:
>
> Changes in v2:
>
> 1. Merge __longjmp.S and __longjmp_chk.S by adding CHECK_INVALID_LONGJMP.
> 2. Remove a branch in the restore token searching loop.
> 3. Cache the target shadow stack pointer.
>
> setcontext and swapcontext put a restore token on the old shadow stack
> which is used to restore the target shadow stack when switching user
> contexts. When longjmp from a user context, the target shadow stack
> can be different from the current shadow stack and INCSSP can't be
> used to restore the shadow stack pointer to the target shadow stack.
>
> Update longjmp to search for a restore token. If found, use the token
> to restore the shadow stack pointer before using INCSSP to pop the
> shadow stack. Stop the token search and use INCSSP if the shadow stack
> entry value is the same as the current shadow stack pointer.
>
> It is a user error if there is a shadow stack switch without leaving a
> restore token on the old shadow stack.
>
> The only difference between __longjmp.S and __longjmp_chk.S is that
> __longjmp_chk.S has a check for invalid longjmp usages. Merge
> __longjmp.S and __longjmp_chk.S by adding the CHECK_INVALID_LONGJMP
> macro.
> ---
> .../unix/sysv/linux/x86_64/____longjmp_chk.S | 179 ++++--------------
> sysdeps/x86/__longjmp_cancel.S | 3 +
> sysdeps/x86_64/__longjmp.S | 47 ++++-
> 3 files changed, 84 insertions(+), 145 deletions(-)
>
> diff --git a/sysdeps/unix/sysv/linux/x86_64/____longjmp_chk.S b/sysdeps/unix/sysv/linux/x86_64/____longjmp_chk.S
> index deb6398f43..9aa24620b9 100644
> --- a/sysdeps/unix/sysv/linux/x86_64/____longjmp_chk.S
> +++ b/sysdeps/unix/sysv/linux/x86_64/____longjmp_chk.S
> @@ -15,18 +15,7 @@
> License along with the GNU C Library; if not, see
> <https://www.gnu.org/licenses/>. */
>
> -#include <sysdep.h>
> -#include <pointer_guard.h>
> -#include <jmpbuf-offsets.h>
> -#include <asm-syntax.h>
> -#include <stap-probe.h>
> #include <sigaltstack-offsets.h>
> -#include <jmp_buf-ssp.h>
> -
> -/* Don't restore shadow stack register if shadow stack isn't enabled. */
> -#if !SHSTK_ENABLED
> -# undef SHADOW_STACK_POINTER_OFFSET
> -#endif
>
> .section .rodata.str1.1,"aMS",@progbits,1
> .type longjmp_msg,@object
> @@ -34,136 +23,48 @@ longjmp_msg:
> .string "longjmp causes uninitialized stack frame"
> .size longjmp_msg, .-longjmp_msg
>
> -
> -//#define __longjmp ____longjmp_chk
> -
> #ifdef PIC
> -# define CALL_FAIL sub $8, %RSP_LP; \
> - cfi_remember_state; \
> - cfi_def_cfa_offset(16); \
> - lea longjmp_msg(%rip), %RDI_LP; \
> - call HIDDEN_JUMPTARGET(__fortify_fail); \
> - nop; \
> - cfi_restore_state
> +# define LOAD_MSG lea longjmp_msg(%rip), %RDI_LP
> #else
> -# define CALL_FAIL sub $8, %RSP_LP; \
> - cfi_remember_state; \
> - cfi_def_cfa_offset(16); \
> - mov $longjmp_msg, %RDI_LP; \
> - call HIDDEN_JUMPTARGET(__fortify_fail); \
> - nop; \
> - cfi_restore_state
> +# define LOAD_MSG mov $longjmp_msg, %RDI_LP
> #endif
>
> -/* Jump to the position specified by ENV, causing the
> - setjmp call there to return VAL, or 1 if VAL is 0.
> - void __longjmp (__jmp_buf env, int val). */
> - .text
> -ENTRY(____longjmp_chk)
> - /* Restore registers. */
> - mov (JB_RSP*8)(%rdi), %R8_LP
> - mov (JB_RBP*8)(%rdi),%R9_LP
> - mov (JB_PC*8)(%rdi), %RDX_LP
> -#ifdef PTR_DEMANGLE
> - PTR_DEMANGLE (%R8_LP)
> - PTR_DEMANGLE (%R9_LP)
> - PTR_DEMANGLE (%RDX_LP)
> -# ifdef __ILP32__
> - /* We ignored the high bits of the %rbp value because only the low
> - bits are mangled. But we cannot presume that %rbp is being used
> - as a pointer and truncate it, so recover the high bits. */
> - movl (JB_RBP*8 + 4)(%rdi), %eax
> - shlq $32, %rax
> - orq %rax, %r9
> -# endif
> -#endif
> -
> - cmp %R8_LP, %RSP_LP
> - jbe .Lok
> -
> - /* Save function parameters. */
> - movq %rdi, %r10
> - cfi_register (%rdi, %r10)
> - movl %esi, %ebx
> - cfi_register (%rsi, %rbx)
> -
> - xorl %edi, %edi
> - lea -sizeSS(%rsp), %RSI_LP
> - movl $__NR_sigaltstack, %eax
> - syscall
> - /* Without working sigaltstack we cannot perform the test. */
> - testl %eax, %eax
> - jne .Lok2
> - testl $1, (-sizeSS + oSS_FLAGS)(%rsp)
> - jz .Lfail
> -
> - mov (-sizeSS + oSS_SP)(%rsp), %RAX_LP
> - add (-sizeSS + oSS_SIZE)(%rsp), %RAX_LP
> - sub %R8_LP, %RAX_LP
> - cmp (-sizeSS + oSS_SIZE)(%rsp), %RAX_LP
> - jae .Lok2
> -
> -.Lfail: CALL_FAIL
> -
> -.Lok2: movq %r10, %rdi
> - cfi_restore (%rdi)
> - movl %ebx, %esi
> - cfi_restore (%rsi)
> -
> +#define CHECK_INVALID_LONGJMP \
> + cmp %R8_LP, %RSP_LP; \
> + jbe .Lok; \
> + /* Save function parameters. */ \
> + movq %rdi, %r10; \
> + cfi_register (%rdi, %r10); \
> + movl %esi, %ebx; \
> + cfi_register (%rsi, %rbx); \
> + xorl %edi, %edi; \
> + lea -sizeSS(%rsp), %RSI_LP; \
> + movl $__NR_sigaltstack, %eax; \
> + syscall; \
> + /* Without working sigaltstack we cannot perform the test. */ \
> + testl %eax, %eax; \
> + jne .Lok2; \
> + testl $1, (-sizeSS + oSS_FLAGS)(%rsp); \
> + jz .Lfail; \
> + mov (-sizeSS + oSS_SP)(%rsp), %RAX_LP; \
> + add (-sizeSS + oSS_SIZE)(%rsp), %RAX_LP; \
> + sub %R8_LP, %RAX_LP; \
> + cmp (-sizeSS + oSS_SIZE)(%rsp), %RAX_LP; \
> + jae .Lok2; \
> +.Lfail: \
> + sub $8, %RSP_LP; \
> + cfi_remember_state; \
> + cfi_def_cfa_offset(16); \
> + LOAD_MSG; \
> + call HIDDEN_JUMPTARGET(__fortify_fail); \
> + nop; \
what is this nop for?
If you want `.Lok2` aligned just use `.p2align`?
> + cfi_restore_state; \
> +.Lok2: \
> + movq %r10, %rdi; \
> + cfi_restore (%rdi); \
> + movl %ebx, %esi; \
> + cfi_restore (%rsi); \
> .Lok:
> -#ifdef SHADOW_STACK_POINTER_OFFSET
> -# if IS_IN (libc) && defined SHARED && defined FEATURE_1_OFFSET
> - /* Check if Shadow Stack is enabled. */
> - testl $X86_FEATURE_1_SHSTK, %fs:FEATURE_1_OFFSET
> - jz L(skip_ssp)
> -# else
> - xorl %eax, %eax
> -# endif
> - /* Check and adjust the Shadow-Stack-Pointer. */
> - rdsspq %rax
> - /* And compare it with the saved ssp value. */
> - subq SHADOW_STACK_POINTER_OFFSET(%rdi), %rax
> - je L(skip_ssp)
> - /* Count the number of frames to adjust and adjust it
> - with incssp instruction. The instruction can adjust
> - the ssp by [0..255] value only thus use a loop if
> - the number of frames is bigger than 255. */
> - negq %rax
> - shrq $3, %rax
> - /* NB: We saved Shadow-Stack-Pointer of setjmp. Since we are
> - restoring Shadow-Stack-Pointer of setjmp's caller, we
> - need to unwind shadow stack by one more frame. */
> - addq $1, %rax
> - movl $255, %ebx
> -L(loop):
> - cmpq %rbx, %rax
> - cmovb %rax, %rbx
> - incsspq %rbx
> - subq %rbx, %rax
> - ja L(loop)
> -L(skip_ssp):
> -#endif
> - LIBC_PROBE (longjmp, 3, LP_SIZE@%RDI_LP, -4@%esi, LP_SIZE@%RDX_LP)
> - /* We add unwind information for the target here. */
> - cfi_def_cfa(%rdi, 0)
> - cfi_register(%rsp,%r8)
> - cfi_register(%rbp,%r9)
> - cfi_register(%rip,%rdx)
> - cfi_offset(%rbx,JB_RBX*8)
> - cfi_offset(%r12,JB_R12*8)
> - cfi_offset(%r13,JB_R13*8)
> - cfi_offset(%r14,JB_R14*8)
> - cfi_offset(%r15,JB_R15*8)
> - movq (JB_RBX*8)(%rdi), %rbx
> - movq (JB_R12*8)(%rdi), %r12
> - movq (JB_R13*8)(%rdi), %r13
> - movq (JB_R14*8)(%rdi), %r14
> - movq (JB_R15*8)(%rdi), %r15
> - /* Set return value for setjmp. */
> - movl %esi, %eax
> - mov %R8_LP, %RSP_LP
> - movq %r9,%rbp
> - LIBC_PROBE (longjmp_target, 3,
> - LP_SIZE@%RDI_LP, -4@%eax, LP_SIZE@%RDX_LP)
> - jmpq *%rdx
> -END (____longjmp_chk)
> +
> +#define __longjmp ____longjmp_chk
> +#include <__longjmp.S>
> diff --git a/sysdeps/x86/__longjmp_cancel.S b/sysdeps/x86/__longjmp_cancel.S
> index e71b304257..b03f52b308 100644
> --- a/sysdeps/x86/__longjmp_cancel.S
> +++ b/sysdeps/x86/__longjmp_cancel.S
> @@ -16,5 +16,8 @@
> License along with the GNU C Library; if not, see
> <https://www.gnu.org/licenses/>. */
>
> +/* Don't restore shadow stack register for __longjmp_cancel. */
> +#define DO_NOT_RESTORE_SHADOW_STACK
> +
> #define __longjmp __longjmp_cancel
> #include <__longjmp.S>
> diff --git a/sysdeps/x86_64/__longjmp.S b/sysdeps/x86_64/__longjmp.S
> index c9f70f8e2a..22fedc4997 100644
> --- a/sysdeps/x86_64/__longjmp.S
> +++ b/sysdeps/x86_64/__longjmp.S
> @@ -22,14 +22,15 @@
> #include <asm-syntax.h>
> #include <stap-probe.h>
>
> -/* Don't restore shadow stack register if
> - 1. Shadow stack isn't enabled. Or
> - 2. __longjmp is defined for __longjmp_cancel.
> - */
> -#if !SHSTK_ENABLED || defined __longjmp
> +/* Don't restore shadow stack register if shadow stack isn't enabled. */
> +#if !SHSTK_ENABLED || defined DO_NOT_RESTORE_SHADOW_STACK
> # undef SHADOW_STACK_POINTER_OFFSET
> #endif
>
> +#ifndef CHECK_INVALID_LONGJMP
> +# define CHECK_INVALID_LONGJMP
> +#endif
> +
> /* Jump to the position specified by ENV, causing the
> setjmp call there to return VAL, or 1 if VAL is 0.
> void __longjmp (__jmp_buf env, int val). */
> @@ -52,6 +53,9 @@ ENTRY(__longjmp)
> orq %rax, %r9
> # endif
> #endif
> +
> + CHECK_INVALID_LONGJMP
This only runs if `__longjmp` is defined `____longjmp_chk` correct?
As far as I can tell its only used once. Maybe have the code here i.e:
#ifdef SHADOW_STACK_POINT_OFFSET
//cur code
#else
//CHECK_INVALID_LONGJMP code
#endif
or am I misunderstanding?
> +
> #ifdef SHADOW_STACK_POINTER_OFFSET
> # if IS_IN (libc) && defined SHARED && defined FEATURE_1_OFFSET
> /* Check if Shadow Stack is enabled. */
> @@ -63,9 +67,40 @@ ENTRY(__longjmp)
> /* Check and adjust the Shadow-Stack-Pointer. */
> /* Get the current ssp. */
> rdsspq %rax
> + /* Save the current ssp. */
> + movq %rax, %r10
> /* And compare it with the saved ssp value. */
> - subq SHADOW_STACK_POINTER_OFFSET(%rdi), %rax
> + movq SHADOW_STACK_POINTER_OFFSET(%rdi), %rcx
> + subq %rcx, %rax
> je L(skip_ssp)
> +
> + /* Save the target ssp. */
> + movq %rcx, %r11
> +
> +L(find_restore_token_loop):
> + /* Look for a restore token. */
> + movq -8(%rcx), %rbx
> + andq $-8, %rbx
> + cmpq %rcx, %rbx
> + /* Find the restore token. */
> + je L(restore_shadow_stack)
> +
> + /* Try the next slot. */
> + subq $8, %rcx
> + /* Stop if the current ssp is found. */
> + cmpq %rcx, %r10
> + jne L(find_restore_token_loop)
> + jmp L(no_shadow_stack_token)
> +
> +L(restore_shadow_stack):
> + /* Restore the target shadow stack. */
> + rstorssp -8(%rcx)
> + /* Save the restore token on the old shadow stack. */
> + saveprevssp
> + rdsspq %rax
> + subq %r11, %rax
> +
> +L(no_shadow_stack_token):
> /* Count the number of frames to adjust and adjust it
> with incssp instruction. The instruction can adjust
> the ssp by [0..255] value only thus use a loop if
> --
> 2.43.0
>
More information about the Libc-alpha
mailing list