about summary refs log tree commit diff
path: root/sysdeps/unix/sysv/linux/x86_64/swapcontext.S
diff options
context:
space:
mode:
Diffstat (limited to 'sysdeps/unix/sysv/linux/x86_64/swapcontext.S')
-rw-r--r--sysdeps/unix/sysv/linux/x86_64/swapcontext.S129
1 files changed, 129 insertions, 0 deletions
diff --git a/sysdeps/unix/sysv/linux/x86_64/swapcontext.S b/sysdeps/unix/sysv/linux/x86_64/swapcontext.S
index 1110c479fa..92d3371807 100644
--- a/sysdeps/unix/sysv/linux/x86_64/swapcontext.S
+++ b/sysdeps/unix/sysv/linux/x86_64/swapcontext.S
@@ -18,6 +18,7 @@
    <http://www.gnu.org/licenses/>.  */
 
 #include <sysdep.h>
+#include <asm/prctl.h>
 
 #include "ucontext_i.h"
 
@@ -67,6 +68,7 @@ ENTRY(__swapcontext)
 
 	/* The syscall destroys some registers, save them.  */
 	movq	%rsi, %r12
+	movq	%rdi, %r9
 
 	/* Save the current signal mask and install the new one with
 	   rt_sigprocmask (SIG_BLOCK, newset, oldset,_NSIG/8).  */
@@ -99,6 +101,133 @@ ENTRY(__swapcontext)
 	movq	oR14(%rdx), %r14
 	movq	oR15(%rdx), %r15
 
+#if SHSTK_ENABLED
+	/* Check if shadow stack is enabled.  */
+	testl	$X86_FEATURE_1_SHSTK, %fs:FEATURE_1_OFFSET
+	jz	L(no_shstk)
+
+	xorl	%eax, %eax
+	cmpq	%fs:SSP_BASE_OFFSET, %rax
+	jnz	L(shadow_stack_bound_recorded)
+
+	/* Get the base address and size of the default shadow stack
+	   which must be the current shadow stack since nothing has
+	   been recorded yet.  */
+	sub	$24, %RSP_LP
+	mov	%RSP_LP, %RSI_LP
+	movl	$ARCH_CET_STATUS, %edi
+	movl	$__NR_arch_prctl, %eax
+	syscall
+	testq	%rax, %rax
+	jz	L(continue_no_err)
+
+	/* This should never happen.  */
+	hlt
+
+L(continue_no_err):
+	/* Record the base of the current shadow stack.  */
+	movq	8(%rsp), %rax
+	movq	%rax, %fs:SSP_BASE_OFFSET
+	add	$24, %RSP_LP
+
+L(shadow_stack_bound_recorded):
+        /* If we unwind the stack, we can't undo stack unwinding.  Just
+	   save the target shadow stack pointer as the current shadow
+	   stack pointer.   */
+	movq	oSSP(%rdx), %rcx
+	movq	%rcx, oSSP(%r9)
+
+	/* Save the base of the current shadow stack.  */
+	movq	%fs:SSP_BASE_OFFSET, %rax
+	movq	%rax, (oSSP + 8)(%r9)
+
+	/* If the base of the target shadow stack is the same as the
+	   base of the current shadow stack, we unwind the shadow
+	   stack.  Otherwise it is a stack switch and we look for a
+	   restore token.  */
+	movq	oSSP(%rdx), %rsi
+	movq	%rsi, %rdi
+
+	/* Get the base of the target shadow stack.  */
+	movq	(oSSP + 8)(%rdx), %rcx
+	cmpq	%fs:SSP_BASE_OFFSET, %rcx
+	je	L(unwind_shadow_stack)
+
+L(find_restore_token_loop):
+	/* Look for a restore token.  */
+	movq	-8(%rsi), %rax
+	andq	$-8, %rax
+	cmpq	%rsi, %rax
+	je	L(restore_shadow_stack)
+
+	/* Try the next slot.  */
+	subq	$8, %rsi
+	jmp	L(find_restore_token_loop)
+
+L(restore_shadow_stack):
+        /* The target shadow stack will be restored.  Save the current
+	   shadow stack pointer.  */
+	rdsspq	%rcx
+	movq	%rcx, oSSP(%r9)
+
+	/* Restore the target shadow stack.  */
+	rstorssp -8(%rsi)
+
+	/* Save the restore token on the old shadow stack.  NB: This
+	   restore token may be checked by setcontext or swapcontext
+	   later.  */
+	saveprevssp
+
+	/* Record the new shadow stack base that was switched to.   */
+	movq	(oSSP + 8)(%rdx), %rax
+	movq	%rax, %fs:SSP_BASE_OFFSET
+
+L(unwind_shadow_stack):
+	rdsspq	%rcx
+	subq	%rdi, %rcx
+	je	L(skip_unwind_shadow_stack)
+	negq	%rcx
+	shrq	$3, %rcx
+	movl	$255, %esi
+L(loop):
+	cmpq	%rsi, %rcx
+	cmovb	%rcx, %rsi
+	incsspq	%rsi
+	subq	%rsi, %rcx
+	ja	L(loop)
+
+L(skip_unwind_shadow_stack):
+	/* Setup registers used for passing args.  */
+	movq	oRDI(%rdx), %rdi
+	movq	oRSI(%rdx), %rsi
+	movq	oRCX(%rdx), %rcx
+	movq	oR8(%rdx), %r8
+	movq	oR9(%rdx), %r9
+
+	/* Get the return address set with getcontext.  */
+	movq	oRIP(%rdx), %r10
+
+	/* Setup finally %rdx.  */
+	movq	oRDX(%rdx), %rdx
+
+	/* Check if return address is valid for the case when setcontext
+	   is invoked from __start_context with linked context.  */
+	rdsspq	%rax
+	cmpq	(%rax), %r10
+	/* Clear rax to indicate success.  NB: Don't use xorl to keep
+	   EFLAGS for jne.  */
+	movl	$0, %eax
+	jne	L(jmp)
+	/* Return to the new context if return address valid.  */
+	pushq	%r10
+	ret
+
+L(jmp):
+	/* Jump to the new context directly.  */
+	jmp	*%r10
+
+L(no_shstk):
+#endif
 	/* The following ret should return to the address set with
 	getcontext.  Therefore push the address on the stack.  */
 	movq	oRIP(%rdx), %rcx