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 24/24] [DO NOT MERGE] x86/shstk: Depend out everything incompatible with kernel shadow stacks
Date: Wed, 21 Oct 2026 12:51:53 -0700 [thread overview]
Message-ID: <20261021195153.06D8D679@davehans-spike.ostc.intel.com> (raw)
In-Reply-To: <20261021195120.E61EC737@davehans-spike.ostc.intel.com>
Note: this is a 100% vibe-coded pile of goo. It will at least present
the rough scale of the work that's left to do though.
===
Kernel shadow stacks make the CPU check every RET against a second,
hardware-maintained stack of return addresses. Anything that "returns"
somewhere other than where the matching CALL came from takes a #CP.
Quite a lot of kernel code does exactly that, on purpose.
Exclude all of it at Kconfig time. This is deliberately the biggest
hammer available. Each dependency is annotated with the specific
construct that breaks so it can be revisited when that code is taught
about shadow stacks:
* Retpolines, call depth tracking and SRSO Safe-RET all execute a RET
whose return address was not pushed by the matching CALL.
* Function graph tracing, rethook/fprobe, livepatch and error injection
all hijack return addresses or redirect the IP that ftrace_regs_caller
RETs to.
* kprobes emulates CALL and RET by moving the return address on the
regular stack only.
* BPF trampolines return to the grandparent with SKIP_FRAME and
bpf_throw() unwinds frames without executing their RETs.
* kexec, hibernation and S3 resume all RET or jump into saved context
without S_CET, the SSP MSRs or SSP itself being saved or restored.
* EFI mixed mode uses LRETQ/LRET, which are shadow stack checked.
* KVM refuses to load when host S_CET.SH_STK_EN is set and VMX loads
HOST_SSP=0 on every VM-exit.
* Xen PV runs the kernel in ring 3 without FRED.
* kgdb writes regs->ip behind FRED's back.
Also document, in the same place, the features that were examined and
found compatible (rethunk, retbleed untrain, ITS thunks, clear_bhb_loop,
PTI by way of it clearing FRED, IBT, user shadow stacks), and the known
gaps that Kconfig can not express: text_poke_bp() CALL emulation,
direct regs->ip writes, the RSB_CTXSW fill in __switch_to_asm, and EFI
runtime services.
Runtime-selected mitigations (spectre_v2=retpoline on an eIBRS CPU,
retbleed=stuff, RSB filling) can not be caught here and are handled by
a separate runtime check in the CET init code.
---
b/arch/x86/Kconfig | 107 +++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 107 insertions(+)
diff -puN arch/x86/Kconfig~kshstk-kconfig-depend-out-incompatible arch/x86/Kconfig
--- a/arch/x86/Kconfig~kshstk-kconfig-depend-out-incompatible 2026-10-21 12:36:44.598893837 -0700
+++ b/arch/x86/Kconfig 2026-10-21 12:36:44.601893842 -0700
@@ -1865,6 +1865,40 @@ config X86_USER_SHADOW_STACK
If unsure, say N.
+# Kernel shadow stacks make the CPU check every RET against a second,
+# hardware-maintained stack of return addresses. Anything that
+# "returns" somewhere other than where the matching CALL came from
+# takes a #CP. That rules out a surprising amount of kernel code. The
+# dependencies below exclude all of it at build time. Each one names
+# the construct that breaks so it can be revisited when that code is
+# taught about shadow stacks.
+#
+# Examined and believed compatible, so NOT excluded here:
+# - MITIGATION_RETHUNK: __x86_return_thunk is a plain RET.
+# - MITIGATION_UNRET_ENTRY: retbleed_return_thunk is a plain RET and
+# entry_untrain_ret is a balanced CALL/RET.
+# - MITIGATION_ITS: the ITS indirect thunks are JMPs and
+# its_return_thunk is a plain RET. (its=stuff is caught at runtime
+# via X86_FEATURE_CALL_DEPTH.)
+# - MITIGATION_SPECTRE_BHI: clear_bhb_loop() is balanced CALL/RET.
+# - MITIGATION_PAGE_TABLE_ISOLATION: PTI clears X86_FEATURE_FRED at
+# runtime and kernel shadow stacks require FRED, so the two can
+# never be active together.
+# - X86_KERNEL_IBT, X86_USER_SHADOW_STACK: separate CET controls.
+#
+# Known gaps that Kconfig can not express and that the design must
+# still handle:
+# - text_poke_bp() CALL emulation (int3_emulate_call(), used by
+# static_call and ftrace call-site patching and by the boot-time
+# int3_selftest()) pushes the return address only on the regular
+# stack.
+# - Any code that writes regs->ip directly instead of going through
+# regs_set_return_ip() leaves the FRED shadow stack frame stale.
+# - The RSB_CTXSW RSB fill in __switch_to_asm pushes 32 bogus shadow
+# stack entries. It is survivable only because SSP is reloaded
+# with RSTORSSP immediately afterwards.
+# - EFI runtime services run firmware code that is not shadow stack
+# aware while kernel shadow stacks are live.
config X86_KERN_SHADOW_STACK
bool "X86 kernel shadow stack"
depends on X86_FRED # implies X86_64
@@ -1873,6 +1907,79 @@ config X86_KERN_SHADOW_STACK
depends on VMAP_STACK
depends on AS_WRUSS
select X86_CET
+ # The retpoline sequence is "call; mov %reg,(%rsp); ret": the
+ # CALL pushes a shadow stack entry and the RET then uses a
+ # rewritten return address. Mismatch every time.
+ depends on !MITIGATION_RETPOLINE
+ # The indirect call thunks are retpoline-based (see above) and
+ # call_depth_return_thunk() refills the RSB with 16 CALLs, then
+ # does "add $128,%rsp; ret", leaving 16 stale shadow stack entries.
+ depends on !MITIGATION_CALL_DEPTH_TRACKING
+ # Safe-RET is "call srso_safe_ret" followed by
+ # "lea 8(%rsp),%rsp; ret": the RET consumes the caller's return
+ # address while the shadow stack top holds the CALL's.
+ depends on !MITIGATION_SRSO
+ # Replaces return addresses on the stack with return_to_handler,
+ # and return_to_handler itself returns with a retpoline-style
+ # "call; mov %rdi,(%rsp); ret".
+ depends on !FUNCTION_GRAPH_TRACER
+ # kprobe_emulate_call(), kprobe_emulate_ret() and
+ # kprobe_emulate_call_indirect() emulate CALL and RET by moving
+ # the return address on the regular stack only. KRETPROBES and
+ # OPTPROBES hang off this symbol as well.
+ depends on !KPROBES
+ # arch_rethook_trampoline() is a return address hijack: the
+ # probed function RETs to the trampoline instead of its caller.
+ # FPROBE selects RETHOOK.
+ depends on !RETHOOK
+ depends on !FPROBE
+ # klp_ftrace_handler() redirects execution by changing the
+ # ftrace_regs IP. ftrace_regs_caller writes that IP into the
+ # return slot and RETs to it, but the shadow stack still holds
+ # the original address.
+ depends on !LIVEPATCH
+ # override_function_with_return() points regs->ip at
+ # just_return_func, a bare RET that pops the probed function's
+ # return address without the function's own RET ever running.
+ depends on !FUNCTION_ERROR_INJECTION
+ # BPF trampolines with BPF_TRAMP_F_SKIP_FRAME do "add $8,%rsp; ret"
+ # to return to the grandparent. bpf_throw() unwinds BPF frames by
+ # walking the stack and resuming at the exception callback with
+ # the unwound RSP, skipping the intervening RETs.
+ depends on !BPF_JIT
+ # relocate_kernel() switches CR3 and CR4 with a shadow stack live
+ # and uses "pushq $0 ... ret" as the handoff into purgatory. It
+ # needs S_CET.SH_STK_EN cleared first, which nothing does yet.
+ depends on !KEXEC_CORE
+ # restore_registers() RETs on a stack reloaded from the image
+ # with S_CET, the PLx/FRED_SSPx MSRs and SSP itself never saved or
+ # restored. Same problem for do_suspend_lowlevel()/wakeup_long64(),
+ # which resume with "jmp *%rax" into the middle of the function
+ # and then RET.
+ depends on !HIBERNATION
+ depends on !SUSPEND
+ # __efi64_thunk() drops into 32-bit firmware with LRETQ and comes
+ # back with LRET. Far returns are shadow stack checked and expect
+ # a far CALL frame that was never pushed.
+ depends on !EFI_MIXED
+ # KVM snapshots host S_CET at load, refuses to load if SH_STK_EN
+ # is set (kvm_x86_vendor_init()), and VMX programs HOST_SSP=0 for
+ # every VM-exit. vmenter.S also runs FILL_RETURN_BUFFER before
+ # its RET when RSB_VMEXIT or RSB_VMEXIT_LITE is active.
+ depends on !KVM
+ # PV guests run the kernel in ring 3 without FRED. Kernel shadow
+ # stacks are FRED-only.
+ depends on !XEN_PV
+ # kgdb_arch_set_pc() writes regs->ip directly rather than through
+ # regs_set_return_ip(), so the FRED shadow stack frame goes stale.
+ # The breakpoint and single-step paths have not been audited.
+ depends on !KGDB
+ help
+ Shadow stacks for the kernel itself. Every CALL pushes the
+ return address on a second, hardware-protected stack and every
+ RET checks it. Requires FRED. See the comments above for the
+ long list of kernel features this is currently incompatible
+ with.
config INTEL_TDX_HOST
bool "Intel Trust Domain Extensions (TDX) host support"
_
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 ` [RFC][PATCH 17/24] x86/task: Context switch the kernel shadow stack register value Dave Hansen
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 ` Dave Hansen [this message]
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=20261021195153.06D8D679@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®