[PATCH v5 4/6] x86/shstk: support via prctl

From: Bill Roberts

Date: Mon Nov 09 2026 - 13:48:45 EST


On x86, userspace shadow stack state is managed through the
architecture-specific arch_prctl() interface. Other architectures,
including arm64 and RISC-V, use the generic shadow stack prctl()
interface instead.

This difference requires userspace runtimes, toolchains, and libc
implementations to support architecture-specific interfaces for
managing shadow stack state. It also complicates efforts to provide
consistent LSM mediation of control-flow integrity state changes.

Implement the generic PR_SET_SHADOW_STACK_STATUS,
PR_GET_SHADOW_STACK_STATUS, and PR_LOCK_SHADOW_STACK_STATUS
operations on x86 using the existing shadow stack helpers.
Retain the arch_prctl() interface for compatibility with
existing userspace.

Unlike arch_prctl(), which enables or disables individual features,
PR_SET_SHADOW_STACK_STATUS specifies the desired state of the
supported feature set. Translate this state into the corresponding
shadow stack and WRSS operations, changing only features whose
state differs from the requested state.

Avoid unnecessary operations because the underlying shadow stack
helpers check locked bits before checking whether the requested
feature state is already satisfied. If enabling or disabling WRSS
fails after enabling shadow stack, restore the original shadow
stack state.

Disabling shadow stack also disables WRSS, so do not disable WRSS
separately when clearing the entire feature set. This avoids
allowing a locked WRSS feature to prevent shadow stack disablement.

Reject unsupported feature combinations and use the existing
shadow stack helpers to enforce feature locking.

Signed-off-by: Bill Roberts <bill.roberts@xxxxxxx>
---
arch/x86/kernel/shstk.c | 80 +++++++++++++++++++++++++++++++++++++++++
1 file changed, 80 insertions(+)

diff --git a/arch/x86/kernel/shstk.c b/arch/x86/kernel/shstk.c
index eb690ba90180..6dfe310634b0 100644
--- a/arch/x86/kernel/shstk.c
+++ b/arch/x86/kernel/shstk.c
@@ -18,6 +18,7 @@
#include <linux/sizes.h>
#include <linux/user.h>
#include <linux/syscalls.h>
+#include <linux/prctl.h>
#include <asm/msr.h>
#include <asm/fpu/xstate.h>
#include <asm/fpu/types.h>
@@ -630,3 +631,82 @@ bool shstk_is_enabled(void)
{
return features_enabled(ARCH_SHSTK_SHSTK);
}
+
+/* Handles the generic prctl interface for PR_SET_SHADOW_STACK_STATUS and its feature bits */
+int arch_set_shadow_stack_status(struct task_struct *t, unsigned long status)
+{
+ int rc;
+ bool shstk_was_enabled = features_enabled(ARCH_SHSTK_SHSTK);
+
+ /*
+ * Enable/disable the full feature set and preserve the original state for rollback.
+ * Only act on changed features, since shstk_prctl() checks locked bits before checking
+ * feature status.
+ */
+ switch (status) {
+ case 0:
+ /*
+ * Do not explicitly disable WRSS. Disabling shadow stack also
+ * disables WRSS, and a locked WRSS must not prevent this.
+ */
+ return shstk_was_enabled ?
+ shstk_prctl(t, ARCH_SHSTK_DISABLE, ARCH_SHSTK_SHSTK) : 0;
+
+ case PR_SHADOW_STACK_ENABLE:
+ if (!shstk_was_enabled) {
+ rc = shstk_prctl(t, ARCH_SHSTK_ENABLE, ARCH_SHSTK_SHSTK);
+ if (rc)
+ return rc;
+ }
+
+ if (features_enabled(ARCH_SHSTK_WRSS)) {
+ rc = shstk_prctl(t, ARCH_SHSTK_DISABLE, ARCH_SHSTK_WRSS);
+ if (rc && !shstk_was_enabled)
+ shstk_prctl(t, ARCH_SHSTK_DISABLE, ARCH_SHSTK_SHSTK);
+ return rc;
+ }
+
+ return 0;
+
+ case PR_SHADOW_STACK_ENABLE|PR_SHADOW_STACK_WRITE:
+ if (!shstk_was_enabled) {
+ rc = shstk_prctl(t, ARCH_SHSTK_ENABLE, ARCH_SHSTK_SHSTK);
+ if (rc)
+ return rc;
+ }
+
+ if (!features_enabled(ARCH_SHSTK_WRSS)) {
+ rc = shstk_prctl(t, ARCH_SHSTK_ENABLE, ARCH_SHSTK_WRSS);
+ if (rc && !shstk_was_enabled)
+ shstk_prctl(t, ARCH_SHSTK_DISABLE, ARCH_SHSTK_SHSTK);
+
+ return rc;
+ }
+
+ return 0;
+
+ /*
+ * Unknown bit mapping, unsupported PR_SHADOW_STACK_PUSH or *only*
+ * PR_SHADOW_STACK_WRITE.
+ */
+ default:
+ return -EINVAL;
+ }
+}
+
+/* Handles the generic prctl interface for PR_LOCK_SHADOW_STACK_STATUS and its feature bits */
+int arch_lock_shadow_stack_status(struct task_struct *t, unsigned long status)
+{
+ return shstk_prctl(t, ARCH_SHSTK_LOCK, status);
+}
+
+/*
+ * We assume the prctl() feature bits line up with the arch_prctl() specific ones. If not,
+ * the flags returned via arch_get_shadow_stack_status will be mapped wrong.
+ */
+static_assert(PR_SHADOW_STACK_ENABLE == ARCH_SHSTK_SHSTK);
+static_assert(PR_SHADOW_STACK_WRITE == ARCH_SHSTK_WRSS);
+int arch_get_shadow_stack_status(struct task_struct *t, unsigned long __user *status)
+{
+ return shstk_prctl(t, ARCH_SHSTK_STATUS, (unsigned long)status);
+}
--
2.55.0