Skip to content

Commit 37b84b7

Browse files
chen-hu-97cmb69
authored andcommitted
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.
1 parent 08f9b50 commit 37b84b7

File tree

5 files changed

+151
-1
lines changed

5 files changed

+151
-1
lines changed

NEWS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ PHP NEWS
2323
. Fix GH-9649: Signal handlers now do a no-op instead of crashing when
2424
executed on threads not managed by TSRM. (Kévin Dunglas)
2525
. Fixed potential NULL pointer dereference Windows shm*() functions. (cmb)
26+
. Added shadow stack support for fibers. (Chen Hu)
2627

2728
- Fileinfo:
2829
. Upgrade bundled libmagic to 5.43. (Anatol)

Zend/asm/jump_x86_64_sysv_elf_gas.S

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626

2727
# if defined __CET__
2828
# include <cet.h>
29+
# define SHSTK_ENABLED (__CET__ & 0x2)
30+
# define BOOST_CONTEXT_SHADOW_STACK (SHSTK_ENABLED && SHADOW_STACK_SYSCALL)
2931
# else
3032
# define _CET_ENDBR
3133
# endif
@@ -50,12 +52,38 @@ jump_fcontext:
5052
movq %rbx, 0x28(%rsp) /* save RBX */
5153
movq %rbp, 0x30(%rsp) /* save RBP */
5254

55+
#if BOOST_CONTEXT_SHADOW_STACK
56+
/* grow the stack to reserve space for shadow stack pointer(SSP) */
57+
leaq -0x8(%rsp), %rsp
58+
/* read the current SSP and store it */
59+
rdsspq %rcx
60+
movq %rcx, (%rsp)
61+
# endif
62+
5363
/* store RSP (pointing to context-data) in RAX */
5464
movq %rsp, %rax
5565

5666
/* restore RSP (pointing to context-data) from RDI */
5767
movq %rdi, %rsp
5868

69+
#if BOOST_CONTEXT_SHADOW_STACK
70+
/* first 8 bytes are SSP */
71+
movq (%rsp), %rcx
72+
leaq 0x8(%rsp), %rsp
73+
74+
/* Restore target(new) shadow stack */
75+
rstorssp -8(%rcx)
76+
/* restore token for previous shadow stack is pushed */
77+
/* on previous shadow stack after saveprevssp */
78+
saveprevssp
79+
80+
/* when return, jump_fcontext jump to restored return address */
81+
/* (r8) instead of RET. This miss of RET implies us to unwind */
82+
/* shadow stack accordingly. Otherwise mismatch occur */
83+
movq $1, %rcx
84+
incsspq %rcx
85+
# endif
86+
5987
movq 0x38(%rsp), %r8 /* restore return-address */
6088

6189
#if !defined(BOOST_USE_TSX)

Zend/asm/make_x86_64_sysv_elf_gas.S

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626

2727
# if defined __CET__
2828
# include <cet.h>
29+
# define SHSTK_ENABLED (__CET__ & 0x2)
30+
# define BOOST_CONTEXT_SHADOW_STACK (SHSTK_ENABLED && SHADOW_STACK_SYSCALL)
2931
# else
3032
# define _CET_ENDBR
3133
# endif
@@ -36,6 +38,12 @@
3638
.align 16
3739
make_fcontext:
3840
_CET_ENDBR
41+
42+
#if BOOST_CONTEXT_SHADOW_STACK
43+
/* the new shadow stack pointer (SSP) */
44+
movq -0x8(%rdi), %r9
45+
#endif
46+
3947
/* first arg of make_fcontext() == top of context-stack */
4048
movq %rdi, %rax
4149

@@ -67,13 +75,59 @@ make_fcontext:
6775
/* will be entered after context-function returns */
6876
movq %rcx, 0x30(%rax)
6977

78+
#if BOOST_CONTEXT_SHADOW_STACK
79+
/* Populate the shadow stack */
80+
81+
/* get original SSP */
82+
rdsspq %r8
83+
/* restore new shadow stack */
84+
rstorssp -0x8(%r9)
85+
/* save the restore token on the original shadow stack */
86+
saveprevssp
87+
/* push the address of "jmp trampoline" to the new shadow stack */
88+
/* as well as the stack */
89+
call 1f
90+
jmp trampoline
91+
1:
92+
/* save address of "jmp trampoline" as return-address */
93+
/* for context-function */
94+
pop 0x38(%rax)
95+
/* Get the new SSP. */
96+
rdsspq %r9
97+
/* restore original shadow stack */
98+
rstorssp -0x8(%r8)
99+
/* save the restore token on the new shadow stack. */
100+
saveprevssp
101+
102+
/* now the new shadow stack looks like:
103+
base-> +------------------------------+
104+
| address of "jmp trampoline" |
105+
SSP-> +------------------------------+
106+
| restore token |
107+
+------------------------------+
108+
*/
109+
110+
/* reserve space for the new SSP */
111+
leaq -0x8(%rax), %rax
112+
/* save the new SSP to this fcontext */
113+
movq %r9, (%rax)
114+
#endif
115+
70116
ret /* return pointer to context-data */
71117

72118
trampoline:
73119
/* store return address on stack */
74120
/* fix stack alignment */
75121
_CET_ENDBR
122+
#if BOOST_CONTEXT_SHADOW_STACK
123+
/* save address of "jmp *%rbp" as return-address */
124+
/* on stack and shadow stack */
125+
call 2f
126+
jmp *%rbp
127+
2:
128+
#else
76129
push %rbp
130+
#endif
77131
/* jump to context-function */
78132
jmp *%rbx
79133

Zend/zend_fibers.c

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,16 @@
6363
# include <sanitizer/common_interface_defs.h>
6464
#endif
6565

66+
# if defined __CET__
67+
# include <cet.h>
68+
# define SHSTK_ENABLED (__CET__ & 0x2)
69+
# define BOOST_CONTEXT_SHADOW_STACK (SHSTK_ENABLED && SHADOW_STACK_SYSCALL)
70+
# define __NR_map_shadow_stack 451
71+
# ifndef SHADOW_STACK_SET_TOKEN
72+
# define SHADOW_STACK_SET_TOKEN 0x1
73+
#endif
74+
#endif
75+
6676
/* Encapsulates the fiber C stack with extension for debugging tools. */
6777
struct _zend_fiber_stack {
6878
void *pointer;
@@ -80,6 +90,10 @@ struct _zend_fiber_stack {
8090
#ifdef ZEND_FIBER_UCONTEXT
8191
/* Embedded ucontext to avoid unnecessary memory allocations. */
8292
ucontext_t ucontext;
93+
#elif BOOST_CONTEXT_SHADOW_STACK
94+
/* Shadow stack: base, size */
95+
void *ss_base;
96+
size_t ss_size;
8397
#endif
8498
};
8599

@@ -228,6 +242,23 @@ static zend_fiber_stack *zend_fiber_stack_allocate(size_t size)
228242
stack->pointer = (void *) ((uintptr_t) pointer + ZEND_FIBER_GUARD_PAGES * page_size);
229243
stack->size = stack_size;
230244

245+
#if !defined(ZEND_FIBER_UCONTEXT) && BOOST_CONTEXT_SHADOW_STACK
246+
/* shadow stack saves ret address only, need less space */
247+
stack->ss_size= stack_size >> 5;
248+
249+
/* align shadow stack to 8 bytes. */
250+
stack->ss_size = (stack->ss_size + 7) & ~7;
251+
252+
/* issue syscall to create shadow stack for the new fcontext */
253+
/* SHADOW_STACK_SET_TOKEN option will put "restore token" on the new shadow stack */
254+
stack->ss_base = (void *)syscall(__NR_map_shadow_stack, 0, stack->ss_size, SHADOW_STACK_SET_TOKEN);
255+
256+
if (stack->ss_base == MAP_FAILED) {
257+
zend_throw_exception_ex(NULL, 0, "Fiber shadow stack allocate failed: mmap failed: %s (%d)", strerror(errno), errno);
258+
return NULL;
259+
}
260+
#endif
261+
231262
#ifdef VALGRIND_STACK_REGISTER
232263
uintptr_t base = (uintptr_t) stack->pointer;
233264
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)
257288
munmap(pointer, stack->size + ZEND_FIBER_GUARD_PAGES * page_size);
258289
#endif
259290

291+
#if !defined(ZEND_FIBER_UCONTEXT) && BOOST_CONTEXT_SHADOW_STACK
292+
munmap(stack->ss_base, stack->ss_size);
293+
#endif
294+
260295
efree(stack);
261296
}
262297
#ifdef ZEND_FIBER_UCONTEXT
@@ -341,6 +376,13 @@ ZEND_API bool zend_fiber_init_context(zend_fiber_context *context, void *kind, z
341376
// Stack grows down, calculate the top of the stack. make_fcontext then shifts pointer to lower 16-byte boundary.
342377
void *stack = (void *) ((uintptr_t) context->stack->pointer + context->stack->size);
343378

379+
#if BOOST_CONTEXT_SHADOW_STACK
380+
// pass the shadow stack pointer to make_fcontext
381+
// i.e., link the new shadow stack with the new fcontext
382+
// TODO should be a better way?
383+
*((unsigned long*) (stack - 8)) = (unsigned long)context->stack->ss_base + context->stack->ss_size;
384+
#endif
385+
344386
context->handle = make_fcontext(stack, context->stack->size, zend_fiber_trampoline);
345387
ZEND_ASSERT(context->handle != NULL && "make_fcontext() never returns NULL");
346388
#endif

configure.ac

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1295,10 +1295,35 @@ else
12951295
fiber_asm="no"
12961296
fi
12971297

1298+
dnl Check whether syscall to create shadow stack exists, should be a better way, but...
1299+
AC_CACHE_CHECK([whether syscall to create shadow stack exists], ac_cv_syscall_shadow_stack_exists,
1300+
[AC_RUN_IFELSE([AC_LANG_SOURCE([[
1301+
#include <unistd.h>
1302+
#include <sys/mman.h>
1303+
int main(void) {
1304+
/* test if syscall 451, i.e., map_shadow_stack is available */
1305+
void* base = (void *)syscall(451, 0, 0x20000, 0x1);
1306+
if (base != (void*)-1) {
1307+
munmap(base, 0x20000);
1308+
return 0;
1309+
}
1310+
else
1311+
return 1;
1312+
}
1313+
]])], [ac_cv_syscall_shadow_stack_exists=yes], [ac_cv_syscall_shadow_stack_exists=no])
1314+
])
1315+
if test "$ac_cv_syscall_shadow_stack_exists" = yes; then
1316+
AC_DEFINE([SHADOW_STACK_SYSCALL], 1, [ ])
1317+
# asm file can't see macro from AC_DEFINE, workaround this via cflag
1318+
fiber_asm_cflag="-DSHADOW_STACK_SYSCALL=1"
1319+
# if the syscall doesn't exist, we may block the final ELF from __PROPERTY_SHSTK
1320+
# via redefine macro as "-D__CET__=1"
1321+
fi
1322+
12981323
if test "$fiber_asm" = 'yes'; then
12991324
AC_MSG_CHECKING([for fiber switching context])
13001325
AC_DEFINE([ZEND_FIBER_ASM], 1, [ ])
1301-
PHP_ADD_SOURCES(Zend/asm, make_${fiber_asm_file}.S jump_${fiber_asm_file}.S)
1326+
PHP_ADD_SOURCES(Zend/asm, make_${fiber_asm_file}.S jump_${fiber_asm_file}.S, "$fiber_asm_cflag")
13021327
AC_MSG_RESULT([$fiber_asm_file])
13031328
else
13041329
if test "$fiber_os" = 'mac'; then

0 commit comments

Comments
 (0)