diff --git a/Zend/asm/jump_x86_64_sysv_elf_gas.S b/Zend/asm/jump_x86_64_sysv_elf_gas.S index c675c8c774c23..1a7709d93d7c8 100644 --- a/Zend/asm/jump_x86_64_sysv_elf_gas.S +++ b/Zend/asm/jump_x86_64_sysv_elf_gas.S @@ -26,6 +26,8 @@ # if defined __CET__ # include +# define SHSTK_ENABLED (__CET__ & 0x2) +# define BOOST_CONTEXT_SHADOW_STACK (SHSTK_ENABLED && SHADOW_STACK_SYSCALL) # else # define _CET_ENDBR # endif @@ -50,12 +52,38 @@ jump_fcontext: movq %rbx, 0x28(%rsp) /* save RBX */ movq %rbp, 0x30(%rsp) /* save RBP */ +#if BOOST_CONTEXT_SHADOW_STACK + /* grow the stack to reserve space for shadow stack pointer(SSP) */ + leaq -0x8(%rsp), %rsp + /* read the current SSP and store it */ + rdsspq %rcx + movq %rcx, (%rsp) +# endif + /* store RSP (pointing to context-data) in RAX */ movq %rsp, %rax /* restore RSP (pointing to context-data) from RDI */ movq %rdi, %rsp +#if BOOST_CONTEXT_SHADOW_STACK + /* first 8 bytes are SSP */ + movq (%rsp), %rcx + leaq 0x8(%rsp), %rsp + + /* Restore target(new) shadow stack */ + rstorssp -8(%rcx) + /* restore token for previous shadow stack is pushed */ + /* on previous shadow stack after saveprevssp */ + saveprevssp + + /* when return, jump_fcontext jump to restored return address */ + /* (r8) instead of RET. This miss of RET implies us to unwind */ + /* shadow stack accordingly. Otherwise mismatch occur */ + movq $1, %rcx + incsspq %rcx +# endif + movq 0x38(%rsp), %r8 /* restore return-address */ #if !defined(BOOST_USE_TSX) diff --git a/Zend/asm/make_x86_64_sysv_elf_gas.S b/Zend/asm/make_x86_64_sysv_elf_gas.S index d422c6972df9c..110c2d3d4a66d 100644 --- a/Zend/asm/make_x86_64_sysv_elf_gas.S +++ b/Zend/asm/make_x86_64_sysv_elf_gas.S @@ -26,6 +26,8 @@ # if defined __CET__ # include +# define SHSTK_ENABLED (__CET__ & 0x2) +# define BOOST_CONTEXT_SHADOW_STACK (SHSTK_ENABLED && SHADOW_STACK_SYSCALL) # else # define _CET_ENDBR # endif @@ -36,6 +38,12 @@ .align 16 make_fcontext: _CET_ENDBR + +#if BOOST_CONTEXT_SHADOW_STACK + /* the new shadow stack pointer (SSP) */ + movq -0x8(%rdi), %r9 +#endif + /* first arg of make_fcontext() == top of context-stack */ movq %rdi, %rax @@ -67,13 +75,59 @@ make_fcontext: /* will be entered after context-function returns */ movq %rcx, 0x30(%rax) +#if BOOST_CONTEXT_SHADOW_STACK + /* Populate the shadow stack */ + + /* get original SSP */ + rdsspq %r8 + /* restore new shadow stack */ + rstorssp -0x8(%r9) + /* save the restore token on the original shadow stack */ + saveprevssp + /* push the address of "jmp trampoline" to the new shadow stack */ + /* as well as the stack */ + call 1f + jmp trampoline +1: + /* save address of "jmp trampoline" as return-address */ + /* for context-function */ + pop 0x38(%rax) + /* Get the new SSP. */ + rdsspq %r9 + /* restore original shadow stack */ + rstorssp -0x8(%r8) + /* save the restore token on the new shadow stack. */ + saveprevssp + + /* now the new shadow stack looks like: + base-> +------------------------------+ + | address of "jmp trampoline" | + SSP-> +------------------------------+ + | restore token | + +------------------------------+ + */ + + /* reserve space for the new SSP */ + leaq -0x8(%rax), %rax + /* save the new SSP to this fcontext */ + movq %r9, (%rax) +#endif + ret /* return pointer to context-data */ trampoline: /* store return address on stack */ /* fix stack alignment */ _CET_ENDBR +#if BOOST_CONTEXT_SHADOW_STACK + /* save address of "jmp *%rbp" as return-address */ + /* on stack and shadow stack */ + call 2f + jmp *%rbp +2: +#else push %rbp +#endif /* jump to context-function */ jmp *%rbx diff --git a/Zend/zend_fibers.c b/Zend/zend_fibers.c index 2df1f0917b32e..f5246e74251dd 100644 --- a/Zend/zend_fibers.c +++ b/Zend/zend_fibers.c @@ -63,6 +63,16 @@ # include #endif +# if defined __CET__ +# include +# define SHSTK_ENABLED (__CET__ & 0x2) +# define BOOST_CONTEXT_SHADOW_STACK (SHSTK_ENABLED && SHADOW_STACK_SYSCALL) +# define __NR_map_shadow_stack 451 +# ifndef SHADOW_STACK_SET_TOKEN +# define SHADOW_STACK_SET_TOKEN 0x1 +#endif +#endif + /* Encapsulates the fiber C stack with extension for debugging tools. */ struct _zend_fiber_stack { void *pointer; @@ -80,6 +90,10 @@ struct _zend_fiber_stack { #ifdef ZEND_FIBER_UCONTEXT /* Embedded ucontext to avoid unnecessary memory allocations. */ ucontext_t ucontext; +#elif BOOST_CONTEXT_SHADOW_STACK + /* Shadow stack: base, size */ + void *ss_base; + size_t ss_size; #endif }; @@ -228,6 +242,23 @@ static zend_fiber_stack *zend_fiber_stack_allocate(size_t size) stack->pointer = (void *) ((uintptr_t) pointer + ZEND_FIBER_GUARD_PAGES * page_size); stack->size = stack_size; +#if !defined(ZEND_FIBER_UCONTEXT) && BOOST_CONTEXT_SHADOW_STACK + /* shadow stack saves ret address only, need less space */ + stack->ss_size= stack_size >> 5; + + /* align shadow stack to 8 bytes. */ + stack->ss_size = (stack->ss_size + 7) & ~7; + + /* issue syscall to create shadow stack for the new fcontext */ + /* SHADOW_STACK_SET_TOKEN option will put "restore token" on the new shadow stack */ + stack->ss_base = (void *)syscall(__NR_map_shadow_stack, 0, stack->ss_size, SHADOW_STACK_SET_TOKEN); + + if (stack->ss_base == MAP_FAILED) { + zend_throw_exception_ex(NULL, 0, "Fiber shadow stack allocate failed: mmap failed: %s (%d)", strerror(errno), errno); + return NULL; + } +#endif + #ifdef VALGRIND_STACK_REGISTER uintptr_t base = (uintptr_t) stack->pointer; stack->valgrind_stack_id = VALGRIND_STACK_REGISTER(base, base + stack->size); @@ -257,6 +288,10 @@ static void zend_fiber_stack_free(zend_fiber_stack *stack) munmap(pointer, stack->size + ZEND_FIBER_GUARD_PAGES * page_size); #endif +#if !defined(ZEND_FIBER_UCONTEXT) && BOOST_CONTEXT_SHADOW_STACK + munmap(stack->ss_base, stack->ss_size); +#endif + efree(stack); } #ifdef ZEND_FIBER_UCONTEXT @@ -341,6 +376,13 @@ ZEND_API bool zend_fiber_init_context(zend_fiber_context *context, void *kind, z // Stack grows down, calculate the top of the stack. make_fcontext then shifts pointer to lower 16-byte boundary. void *stack = (void *) ((uintptr_t) context->stack->pointer + context->stack->size); +#if BOOST_CONTEXT_SHADOW_STACK + // pass the shadow stack pointer to make_fcontext + // i.e., link the new shadow stack with the new fcontext + // TODO should be a better way? + *((unsigned long*) (stack - 8)) = (unsigned long)context->stack->ss_base + context->stack->ss_size; +#endif + context->handle = make_fcontext(stack, context->stack->size, zend_fiber_trampoline); ZEND_ASSERT(context->handle != NULL && "make_fcontext() never returns NULL"); #endif diff --git a/configure.ac b/configure.ac index 6f4f647c19995..aa898c601f55e 100644 --- a/configure.ac +++ b/configure.ac @@ -1295,10 +1295,35 @@ else fiber_asm="no" fi +dnl Check whether syscall to create shadow stack exists, should be a better way, but... +AC_CACHE_CHECK([whether syscall to create shadow stack exists], ac_cv_syscall_shadow_stack_exists, +[AC_RUN_IFELSE([AC_LANG_SOURCE([[ +#include +#include +int main(void) { + /* test if syscall 451, i.e., map_shadow_stack is available */ + void* base = (void *)syscall(451, 0, 0x20000, 0x1); + if (base != (void*)-1) { + munmap(base, 0x20000); + return 0; + } + else + return 1; +} + ]])], [ac_cv_syscall_shadow_stack_exists=yes], [ac_cv_syscall_shadow_stack_exists=no]) +]) +if test "$ac_cv_syscall_shadow_stack_exists" = yes; then + AC_DEFINE([SHADOW_STACK_SYSCALL], 1, [ ]) + # asm file can't see macro from AC_DEFINE, workaround this via cflag + fiber_asm_cflag="-DSHADOW_STACK_SYSCALL=1" + # if the syscall doesn't exist, we may block the final ELF from __PROPERTY_SHSTK + # via redefine macro as "-D__CET__=1" +fi + if test "$fiber_asm" = 'yes'; then AC_MSG_CHECKING([for fiber switching context]) AC_DEFINE([ZEND_FIBER_ASM], 1, [ ]) - PHP_ADD_SOURCES(Zend/asm, make_${fiber_asm_file}.S jump_${fiber_asm_file}.S) + PHP_ADD_SOURCES(Zend/asm, make_${fiber_asm_file}.S jump_${fiber_asm_file}.S, "$fiber_asm_cflag") AC_MSG_RESULT([$fiber_asm_file]) else if test "$fiber_os" = 'mac'; then