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 --- 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 #include #include +#include #include #include #include @@ -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