From: Dave Hansen <dave.hansen@linux.intel.com>
To: linux-kernel@vger.kernel.org
Cc: Andy Lutomirski <luto@kernel.org>,
Peter Zijlstra <peterz@infradead.org>,
Edgecombe, Rick P <rick.p.edgecombe@intel.com>,
Andrew Cooper <andrew.cooper3@citrix.com>,
the arch/x86 maintainers <x86@kernel.org>,
Kees Cook <kees@kernel.org>,
Pawan Gupta <pawan.kumar.gupta@linux.intel.com>,
Borislav Petkov <bp@alien8.de>, Thomas Gleixner <tglx@kernel.org>,
Dave Hansen <dave.hansen@linux.intel.com>
Subject: [RFC][PATCH 17/24] x86/task: Context switch the kernel shadow stack register value
Date: Wed, 21 Oct 2026 12:51:43 -0700 [thread overview]
Message-ID: <20261021195143.2C8D21DD@davehans-spike.ostc.intel.com> (raw)
In-Reply-To: <20261021195120.E61EC737@davehans-spike.ostc.intel.com>
This is the spot where things go off the rails. As I look at this, I
think it may be a mistake to permit a shadow-stack kernel to have non
shadow-stack tasks. It would just be too easy to leverage turn the
ability to set kshstk_ssp=0 into a control flow hijack.
This is also *not* the way the architecture intended context switching
to go. Feedback here would be much appreciated.
I'll continue on describing what is here.
The basic problem set:
1. SSP needs to be context-switched just like RSP:
1a. The kernel SSP value is discrete from the user SSP value
1b. The kernel SSP value needs storage space
2. At *least* the init task does not have a shadow stack mapping. The
expectation is that there will be other tasks. For instance, a
"permissive" mode after a shadow stack violation where the kernel
warns and keeps on running.
== Solution ==
First, establish and maintain kernel SSP storage at:
thread_struct->kshstk_ssp
A 0 value means that the task does not use kernel shadow stacks.
Second, drive MSR_IA32_S_CET from that value. If it is 0, then
clear CET_SHSTK_EN|CET_WRSS_EN during the context switch. If it
is non-zero set those bits.
Third, save and restore the kernel SSP value. This is substantially
more complicated than RSP switching because SSP is not directly
accessible and needs to be loaded with RSTORSSP, which is picky.
It's not picky for no reason. It is *intentionally* picky to foil
stack pivoting attacks. Before it will write SSP, it requires an
in-memory "restore token" be present on the shadow stack.
Just like the regular stack, this switch *must* be done in
assembly.
== Optimizations ==
<<<
These are probably all silly to talk about at this point if a
over over to the more correct RSTORSSP/SAVEPREVSSP is made.
>>>
First, TASK_thread_kshstk_ssp() will always be 0 when kernel shadow
stacks are disabled. This could be ALTERNATIVE()'d out to a plain
jump without the MOV and TEST.
Second, the ENABLE/DISABLE macros could avoid the WRMSR when it is
not changing values.
It might be possible to move the assembly logic like the
MSR_IA32_S_CET or restore token calculations out to C.
---
b/arch/x86/entry/entry_64.S | 87 +++++++++++++++++++++++++++++++++++++
b/arch/x86/include/asm/processor.h | 4 +
b/arch/x86/include/asm/shstk.h | 3 +
b/arch/x86/kernel/asm-offsets.c | 3 +
4 files changed, 97 insertions(+)
diff -puN arch/x86/entry/entry_64.S~per-task-ssp-storage arch/x86/entry/entry_64.S
--- a/arch/x86/entry/entry_64.S~per-task-ssp-storage 2026-10-21 12:36:40.914886779 -0700
+++ b/arch/x86/entry/entry_64.S 2026-10-21 12:36:40.948886844 -0700
@@ -34,6 +34,7 @@
#include <asm/percpu.h>
#include <asm/asm.h>
#include <asm/smap.h>
+#include <asm/shstk.h>
#include <asm/pgtable_types.h>
#include <asm/frame.h>
#include <asm/trapnr.h>
@@ -169,6 +170,87 @@ SYM_INNER_LABEL(entry_SYSRETQ_end, SYM_L
int3
SYM_CODE_END(entry_SYSCALL_64)
+.macro SAVE_KERNEL_SHADOW_STACK
+#ifdef CONFIG_X86_KERN_SHADOW_STACK
+ /* kshstk_ssp==0 means no kernel shadow stack, skip handling */
+ cmpq $0, TASK_thread_kshstk_ssp(%rdi)
+ je .Lprev_no_shstk\@
+ rdsspq %rax
+ movq %rax, TASK_thread_kshstk_ssp(%rdi)
+.Lprev_no_shstk\@:
+#endif
+.endm
+
+.macro ENABLE_KERNEL_SHADOW_STACK
+ movl $MSR_IA32_S_CET, %ecx
+ rdmsr
+ orl $(CET_SHSTK_EN | CET_WRSS_EN), %eax
+ wrmsr
+.endm
+
+.macro DISABLE_KERNEL_SHADOW_STACK
+ movl $MSR_IA32_S_CET, %ecx
+ rdmsr
+ andl $~(CET_SHSTK_EN | CET_WRSS_EN), %eax
+ wrmsr
+.endm
+
+/*
+ * Clobbers rax, rcx, rdx, r8, r9. rdi/rsi must survive for the rest
+ * of __switch_to_asm.
+ *
+ * Dumb and slow. Always does WRMSR, regardless of if changes need to
+ * be made.
+ */
+.macro LOAD_KERNEL_SHADOW_STACK
+#ifdef CONFIG_X86_KERN_SHADOW_STACK
+ movq TASK_thread_kshstk_ssp(%rsi), %r8
+ testq %r8, %r8
+ jz .Lnext_no_shstk\@
+
+ ENABLE_KERNEL_SHADOW_STACK
+
+ /*
+ * This should be just a:
+ *
+ * rstorssp
+ * saveprevssp
+ *
+ * sequence. But, currently, the previous task might not
+ * have a valid SSP, like the init_task. This needs fixing.
+ * But, for now, just hack a Prev-SSP token in.
+ *
+ * The right fix is to give all tasks a valid SSP universally
+ * and then initialize the token once at task creation time,
+ * probably near the spot that ret_from_fork_asm is also
+ * written to the shadow stack.
+ */
+ leaq -8(%r8), %r9
+ movq %r8, %rax
+ /*
+ * Set type=0. Indicates a "Restore token" to satisfy RSTORSSP:
+ * Set mode=1. Indicates 64-bit mode.
+ * */
+ orq $SHSTK_TOKEN_MODE64, %rax
+ wrssq %rax, (%r9)
+
+ rstorssp (%r9)
+
+ /*
+ * Discard the Previous-SSP token left by RSTORSSP.
+ *
+ * This will go away once SAVEPREVSSP is being used properly.
+ */
+ movl $1, %eax
+ incsspq %rax
+ jmp .Lshstk_done\@
+
+.Lnext_no_shstk\@:
+ DISABLE_KERNEL_SHADOW_STACK
+.Lshstk_done\@:
+#endif
+.endm
+
/*
* %rdi: prev task
* %rsi: next task
@@ -191,6 +273,8 @@ SYM_FUNC_START(__switch_to_asm)
movq %rsp, TASK_threadsp(%rdi)
movq TASK_threadsp(%rsi), %rsp
+ SAVE_KERNEL_SHADOW_STACK
+
#ifdef CONFIG_STACKPROTECTOR
movq TASK_stack_canary(%rsi), %rbx
movq %rbx, PER_CPU_VAR(__stack_chk_guard)
@@ -205,6 +289,9 @@ SYM_FUNC_START(__switch_to_asm)
*/
FILL_RETURN_BUFFER %r12, RSB_CLEAR_LOOPS, X86_FEATURE_RSB_CTXSW
+ /* Note that this clobbers tons of stuff: */
+ LOAD_KERNEL_SHADOW_STACK
+
/* restore callee-saved registers */
popq %r15
popq %r14
diff -puN arch/x86/include/asm/processor.h~per-task-ssp-storage arch/x86/include/asm/processor.h
--- a/arch/x86/include/asm/processor.h~per-task-ssp-storage 2026-10-21 12:36:40.927886804 -0700
+++ b/arch/x86/include/asm/processor.h 2026-10-21 12:36:40.949886846 -0700
@@ -532,6 +532,10 @@ struct thread_struct {
struct thread_shstk shstk;
#endif
+#ifdef CONFIG_X86_KERN_SHADOW_STACK
+ /* Kernel shadow stack register: */
+ void *kshstk_ssp;
+#endif
};
#ifdef CONFIG_X86_DEBUG_FPU
diff -puN arch/x86/include/asm/shstk.h~per-task-ssp-storage arch/x86/include/asm/shstk.h
--- a/arch/x86/include/asm/shstk.h~per-task-ssp-storage 2026-10-21 12:36:40.943886835 -0700
+++ b/arch/x86/include/asm/shstk.h 2026-10-21 12:36:40.949886846 -0700
@@ -2,6 +2,9 @@
#ifndef _ASM_X86_SHSTK_H
#define _ASM_X86_SHSTK_H
+#define SHSTK_TOKEN_MODE64 BIT_ULL(0) /* Token created in 64-bit mode */
+#define SHSTK_TOKEN_TYPE BIT_ULL(1) /* Types: Restore: 0, Prev-SSP: 1 */
+
#ifndef __ASSEMBLER__
#include <linux/types.h>
diff -puN arch/x86/kernel/asm-offsets.c~per-task-ssp-storage arch/x86/kernel/asm-offsets.c
--- a/arch/x86/kernel/asm-offsets.c~per-task-ssp-storage 2026-10-21 12:36:40.945886838 -0700
+++ b/arch/x86/kernel/asm-offsets.c 2026-10-21 12:36:40.949886846 -0700
@@ -43,6 +43,9 @@ static void __used common(void)
BLANK();
OFFSET(TASK_threadsp, task_struct, thread.sp);
+#ifdef CONFIG_X86_KERN_SHADOW_STACK
+ OFFSET(TASK_thread_kshstk_ssp, task_struct, thread.kshstk_ssp);
+#endif
#ifdef CONFIG_STACKPROTECTOR
OFFSET(TASK_stack_canary, task_struct, stack_canary);
#endif
_
next prev parent reply other threads:[~2026-10-21 19:51 UTC|newest]
Thread overview: 29+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-21 19:51 [RFC][PATCH 00/24] x86 Kernel Shadow Stacks Dave Hansen
2026-10-21 19:51 ` [RFC][PATCH 01/24] kernel/fork: Break out vmap task allocation into helper Dave Hansen
2026-10-21 19:51 ` [RFC][PATCH 02/24] kernel/fork: Use sizeof() union instead of TASK_SIZE Dave Hansen
2026-10-21 19:51 ` [RFC][PATCH 03/24] kernel/sched: Move thread stack to its own structure Dave Hansen
2026-10-21 19:51 ` [RFC][PATCH 04/24] kernel/fork: Allow arch-specific stack initialization Dave Hansen
2026-10-21 19:51 ` [RFC][PATCH 05/24] kernel/task: Add shadow stack to thread structure Dave Hansen
2026-10-21 19:51 ` [RFC][PATCH 06/24] kernel/task: Add "runtime" check for shadow stack support Dave Hansen
2026-10-21 19:51 ` [RFC][PATCH 07/24] kernel/fork: Clear shadow stacks alongside normal stacks Dave Hansen
2026-10-21 19:51 ` [RFC][PATCH 08/24] x86/cpu: Add basic x86 kernel shadow stack infrastructure Dave Hansen
2026-10-21 19:51 ` [RFC][PATCH 09/24] x86/cea: Refactor "CEA" macros to deal with multiple stacks Dave Hansen
2026-10-21 19:51 ` [RFC][PATCH 10/24] x86/cea: Refactor stack mapping macros Dave Hansen
2026-10-21 19:51 ` [RFC][PATCH 11/24] x86/mm: Add kernel shadow stack permission macros Dave Hansen
2026-10-21 19:51 ` [RFC][PATCH 12/24] x86/cea: Map and use FRED exception shadow stacks Dave Hansen
2026-10-21 21:40 ` Peter Zijlstra
2026-10-21 22:59 ` Dave Hansen
2026-10-21 19:51 ` [RFC][PATCH 13/24] x86/task: Grow thread_union for " Dave Hansen
2026-10-21 19:51 ` [RFC][PATCH 14/24] x86/mm: Establish shadow stack permissions in thread mapping Dave Hansen
2026-10-21 19:51 ` [RFC][PATCH 15/24] x86/task: Hook up arch hook to detect shadow stack enabling Dave Hansen
2026-10-21 19:51 ` [RFC][PATCH 16/24] x86/mm: Kill shadow stack page permission warning Dave Hansen
2026-10-21 19:51 ` Dave Hansen [this message]
2026-10-21 19:51 ` [RFC][PATCH 18/24] kernel/task: Add a helper to get the thread_union Dave Hansen
2026-10-21 19:51 ` [RFC][PATCH 19/24] x86/process: Initialize cloned task shadow stack Dave Hansen
2026-10-21 19:51 ` [RFC][PATCH 20/24] x86/entry: Stash kernel SSP on FRED entry Dave Hansen
2026-10-21 19:51 ` [RFC][PATCH 21/24] x86/extable: Update shadow stack entry frame during RIP overwrites Dave Hansen
2026-10-21 19:51 ` [RFC][PATCH 22/24] kernel/task: Add a helper to locate the top of the shadow stack Dave Hansen
2026-10-21 19:51 ` [RFC][PATCH 23/24] x86/entry: Populate FRED register for user=>kernel transitions Dave Hansen
2026-10-21 19:51 ` [RFC][PATCH 24/24] [DO NOT MERGE] x86/shstk: Depend out everything incompatible with kernel shadow stacks Dave Hansen
2026-10-21 21:36 ` [RFC][PATCH 00/24] x86 Kernel Shadow Stacks Peter Zijlstra
2026-10-21 21:53 ` Peter Zijlstra
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261021195143.2C8D21DD@davehans-spike.ostc.intel.com \
--to=dave.hansen@linux.intel.com \
--cc=andrew.cooper3@citrix.com \
--cc=bp@alien8.de \
--cc=kees@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=luto@kernel.org \
--cc=pawan.kumar.gupta@linux.intel.com \
--cc=peterz@infradead.org \
--cc=rick.p.edgecombe@intel.com \
--cc=tglx@kernel.org \
--cc=x86@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®