x86-64/cet: Check the restore token in longjmp

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.
Reviewed-by: Noah Goldstein <goldstein.w.n@gmail.com>
This commit is contained in:
H.J. Lu 2024-01-02 07:03:29 -08:00
parent e9f5dc7e4a
commit 35694d3416
3 changed files with 84 additions and 145 deletions

View File

@ -15,18 +15,7 @@
License along with the GNU C Library; if not, see License along with the GNU C Library; if not, see
<https://www.gnu.org/licenses/>. */ <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 <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 .section .rodata.str1.1,"aMS",@progbits,1
.type longjmp_msg,@object .type longjmp_msg,@object
@ -34,136 +23,48 @@ longjmp_msg:
.string "longjmp causes uninitialized stack frame" .string "longjmp causes uninitialized stack frame"
.size longjmp_msg, .-longjmp_msg .size longjmp_msg, .-longjmp_msg
//#define __longjmp ____longjmp_chk
#ifdef PIC #ifdef PIC
# define CALL_FAIL sub $8, %RSP_LP; \ # define LOAD_MSG lea longjmp_msg(%rip), %RDI_LP
cfi_remember_state; \
cfi_def_cfa_offset(16); \
lea longjmp_msg(%rip), %RDI_LP; \
call HIDDEN_JUMPTARGET(__fortify_fail); \
nop; \
cfi_restore_state
#else #else
# define CALL_FAIL sub $8, %RSP_LP; \ # define LOAD_MSG mov $longjmp_msg, %RDI_LP
cfi_remember_state; \
cfi_def_cfa_offset(16); \
mov $longjmp_msg, %RDI_LP; \
call HIDDEN_JUMPTARGET(__fortify_fail); \
nop; \
cfi_restore_state
#endif #endif
/* Jump to the position specified by ENV, causing the #define CHECK_INVALID_LONGJMP \
setjmp call there to return VAL, or 1 if VAL is 0. cmp %R8_LP, %RSP_LP; \
void __longjmp (__jmp_buf env, int val). */ jbe .Lok; \
.text /* Save function parameters. */ \
ENTRY(____longjmp_chk) movq %rdi, %r10; \
/* Restore registers. */ cfi_register (%rdi, %r10); \
mov (JB_RSP*8)(%rdi), %R8_LP movl %esi, %ebx; \
mov (JB_RBP*8)(%rdi),%R9_LP cfi_register (%rsi, %rbx); \
mov (JB_PC*8)(%rdi), %RDX_LP xorl %edi, %edi; \
#ifdef PTR_DEMANGLE lea -sizeSS(%rsp), %RSI_LP; \
PTR_DEMANGLE (%R8_LP) movl $__NR_sigaltstack, %eax; \
PTR_DEMANGLE (%R9_LP) syscall; \
PTR_DEMANGLE (%RDX_LP) /* Without working sigaltstack we cannot perform the test. */ \
# ifdef __ILP32__ testl %eax, %eax; \
/* We ignored the high bits of the %rbp value because only the low jne .Lok2; \
bits are mangled. But we cannot presume that %rbp is being used testl $1, (-sizeSS + oSS_FLAGS)(%rsp); \
as a pointer and truncate it, so recover the high bits. */ jz .Lfail; \
movl (JB_RBP*8 + 4)(%rdi), %eax mov (-sizeSS + oSS_SP)(%rsp), %RAX_LP; \
shlq $32, %rax add (-sizeSS + oSS_SIZE)(%rsp), %RAX_LP; \
orq %rax, %r9 sub %R8_LP, %RAX_LP; \
# endif cmp (-sizeSS + oSS_SIZE)(%rsp), %RAX_LP; \
#endif jae .Lok2; \
.Lfail: \
cmp %R8_LP, %RSP_LP sub $8, %RSP_LP; \
jbe .Lok cfi_remember_state; \
cfi_def_cfa_offset(16); \
/* Save function parameters. */ LOAD_MSG; \
movq %rdi, %r10 call HIDDEN_JUMPTARGET(__fortify_fail); \
cfi_register (%rdi, %r10) nop; \
movl %esi, %ebx cfi_restore_state; \
cfi_register (%rsi, %rbx) .Lok2: \
movq %r10, %rdi; \
xorl %edi, %edi cfi_restore (%rdi); \
lea -sizeSS(%rsp), %RSI_LP movl %ebx, %esi; \
movl $__NR_sigaltstack, %eax cfi_restore (%rsi); \
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)
.Lok: .Lok:
#ifdef SHADOW_STACK_POINTER_OFFSET
# if IS_IN (libc) && defined SHARED && defined FEATURE_1_OFFSET #define __longjmp ____longjmp_chk
/* Check if Shadow Stack is enabled. */ #include <__longjmp.S>
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)

View File

@ -16,5 +16,8 @@
License along with the GNU C Library; if not, see License along with the GNU C Library; if not, see
<https://www.gnu.org/licenses/>. */ <https://www.gnu.org/licenses/>. */
/* Don't restore shadow stack register for __longjmp_cancel. */
#define DO_NOT_RESTORE_SHADOW_STACK
#define __longjmp __longjmp_cancel #define __longjmp __longjmp_cancel
#include <__longjmp.S> #include <__longjmp.S>

View File

@ -22,14 +22,15 @@
#include <asm-syntax.h> #include <asm-syntax.h>
#include <stap-probe.h> #include <stap-probe.h>
/* Don't restore shadow stack register if /* Don't restore shadow stack register if shadow stack isn't enabled. */
1. Shadow stack isn't enabled. Or #if !SHSTK_ENABLED || defined DO_NOT_RESTORE_SHADOW_STACK
2. __longjmp is defined for __longjmp_cancel.
*/
#if !SHSTK_ENABLED || defined __longjmp
# undef SHADOW_STACK_POINTER_OFFSET # undef SHADOW_STACK_POINTER_OFFSET
#endif #endif
#ifndef CHECK_INVALID_LONGJMP
# define CHECK_INVALID_LONGJMP
#endif
/* Jump to the position specified by ENV, causing the /* Jump to the position specified by ENV, causing the
setjmp call there to return VAL, or 1 if VAL is 0. setjmp call there to return VAL, or 1 if VAL is 0.
void __longjmp (__jmp_buf env, int val). */ void __longjmp (__jmp_buf env, int val). */
@ -52,6 +53,9 @@ ENTRY(__longjmp)
orq %rax, %r9 orq %rax, %r9
# endif # endif
#endif #endif
CHECK_INVALID_LONGJMP
#ifdef SHADOW_STACK_POINTER_OFFSET #ifdef SHADOW_STACK_POINTER_OFFSET
# if IS_IN (libc) && defined SHARED && defined FEATURE_1_OFFSET # if IS_IN (libc) && defined SHARED && defined FEATURE_1_OFFSET
/* Check if Shadow Stack is enabled. */ /* Check if Shadow Stack is enabled. */
@ -63,9 +67,40 @@ ENTRY(__longjmp)
/* Check and adjust the Shadow-Stack-Pointer. */ /* Check and adjust the Shadow-Stack-Pointer. */
/* Get the current ssp. */ /* Get the current ssp. */
rdsspq %rax rdsspq %rax
/* Save the current ssp. */
movq %rax, %r10
/* And compare it with the saved ssp value. */ /* 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) 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 /* Count the number of frames to adjust and adjust it
with incssp instruction. The instruction can adjust with incssp instruction. The instruction can adjust
the ssp by [0..255] value only thus use a loop if the ssp by [0..255] value only thus use a loop if