mirror of
https://github.com/php/php-src.git
synced 2024-11-23 09:54:15 +08:00
Fiber: add shadow stack support
Shadow stack is part of Intel's Control-Flow Enforcement Technology (CET). Whenever a function is called, the return address is pushed onto both the regular stack and the shadow stack. When that function returns, the return addresses are popped off both stacks and compared; if they fail to match, #CP raised. With this commit, we create shadow stack for each fiber context and switch the shadow stack accordingly during fcontext switch. Signed-off-by: Chen, Hu <hu1.chen@intel.com> Closes GH-9283.
This commit is contained in:
parent
08f9b50a9c
commit
37b84b7e32
1
NEWS
1
NEWS
@ -23,6 +23,7 @@ PHP NEWS
|
||||
. Fix GH-9649: Signal handlers now do a no-op instead of crashing when
|
||||
executed on threads not managed by TSRM. (Kévin Dunglas)
|
||||
. Fixed potential NULL pointer dereference Windows shm*() functions. (cmb)
|
||||
. Added shadow stack support for fibers. (Chen Hu)
|
||||
|
||||
- Fileinfo:
|
||||
. Upgrade bundled libmagic to 5.43. (Anatol)
|
||||
|
@ -26,6 +26,8 @@
|
||||
|
||||
# if defined __CET__
|
||||
# include <cet.h>
|
||||
# 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)
|
||||
|
@ -26,6 +26,8 @@
|
||||
|
||||
# if defined __CET__
|
||||
# include <cet.h>
|
||||
# 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
|
||||
|
||||
|
@ -63,6 +63,16 @@
|
||||
# include <sanitizer/common_interface_defs.h>
|
||||
#endif
|
||||
|
||||
# if defined __CET__
|
||||
# include <cet.h>
|
||||
# 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
|
||||
|
27
configure.ac
27
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 <unistd.h>
|
||||
#include <sys/mman.h>
|
||||
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
|
||||
|
Loading…
Reference in New Issue
Block a user