Addresses an edge case in usermode IBT with signal handlers. When entering and exiting signals, the WAIT_FOR_ENDBR CPU state must be restored. WAIT_FOR_ENDBR is a flag that raises a CET violation if the next instruction is not endbr64.
If a thread enters a signal handler immediately after executing an indirect jump (before reaching an endbr64), the WAIT_FOR_ENDBR would otherwise mistakenly cause a CET violation on the signal handler's first instruction. Worse, an attacker could circumvent IBT by triggering a signal before a vulnerable jump, call rt_sigreturn in that signal, and then return to the original indirect jump target without endbr64 checking. Unless FRED is enabled, the WAIT_FOR_ENDBR flag is backed up from the U_CET MSR. Due to XSAVE, this MSR might be stale in kernel-mode. A gap in 32-bit signal handling is resolved in a follow-up commit. Based-on-patch-by: Yu-cheng Yu <[email protected]> Link: https://lore.kernel.org/lkml/[email protected]/ Signed-off-by: Richard Patel <[email protected]> --- arch/x86/include/asm/ibt.h | 14 +++++ arch/x86/include/asm/processor.h | 5 ++ arch/x86/include/uapi/asm/ucontext.h | 5 ++ arch/x86/kernel/Makefile | 1 + arch/x86/kernel/ibt.c | 88 ++++++++++++++++++++++++++++ arch/x86/kernel/signal_64.c | 6 ++ 6 files changed, 119 insertions(+) create mode 100644 arch/x86/kernel/ibt.c diff --git a/arch/x86/include/asm/ibt.h b/arch/x86/include/asm/ibt.h index 5e45d6424722..3fe464bf83e7 100644 --- a/arch/x86/include/asm/ibt.h +++ b/arch/x86/include/asm/ibt.h @@ -114,4 +114,18 @@ static inline void ibt_restore(u64 save) { } #define ENDBR_INSN_SIZE (4*HAS_KERNEL_IBT) +#ifndef __ASSEMBLER__ + +struct pt_regs; + +#ifdef CONFIG_X86_USER_IBT +bool user_ibt_pop_wait_endbr(struct pt_regs *regs); +void user_ibt_restore_wait_endbr(struct pt_regs *regs, bool wait_endbr); +#else +static inline bool user_ibt_pop_wait_endbr(struct pt_regs *regs) { return false; } +static inline void user_ibt_restore_wait_endbr(struct pt_regs *regs, bool wait_endbr) {} +#endif /* CONFIG_X86_USER_IBT */ + +#endif /* __ASSEMBLER__ */ + #endif /* _ASM_X86_IBT_H */ diff --git a/arch/x86/include/asm/processor.h b/arch/x86/include/asm/processor.h index 10b5355b323e..6ce8f7b6607c 100644 --- a/arch/x86/include/asm/processor.h +++ b/arch/x86/include/asm/processor.h @@ -504,6 +504,11 @@ struct thread_struct { unsigned int iopl_warn:1; +#ifdef CONFIG_X86_USER_IBT + unsigned int ibt:1; + unsigned int ibt_locked:1; +#endif + /* * Protection Keys Register for Userspace. Loaded immediately on * context switch. Store it in thread_struct to avoid a lookup in diff --git a/arch/x86/include/uapi/asm/ucontext.h b/arch/x86/include/uapi/asm/ucontext.h index 5657b7a49f03..0271f5e8aa14 100644 --- a/arch/x86/include/uapi/asm/ucontext.h +++ b/arch/x86/include/uapi/asm/ucontext.h @@ -51,6 +51,11 @@ #define UC_STRICT_RESTORE_SS 0x4 #endif +/* + * Indicates IBT status WAIT_FOR_ENDBR. + */ +#define UC_WAIT_ENDBR 0x8 + #include <asm-generic/ucontext.h> #endif /* _ASM_X86_UCONTEXT_H */ diff --git a/arch/x86/kernel/Makefile b/arch/x86/kernel/Makefile index 47a32f583930..05c87f014552 100644 --- a/arch/x86/kernel/Makefile +++ b/arch/x86/kernel/Makefile @@ -169,6 +169,7 @@ obj-$(CONFIG_CALL_THUNKS) += callthunks.o obj-$(CONFIG_X86_CET) += cet.o obj-$(CONFIG_X86_USER_SHADOW_STACK) += shstk.o +obj-$(CONFIG_X86_USER_IBT) += ibt.o ### # 64 bit specific files diff --git a/arch/x86/kernel/ibt.c b/arch/x86/kernel/ibt.c new file mode 100644 index 000000000000..596b0629106d --- /dev/null +++ b/arch/x86/kernel/ibt.c @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include <linux/types.h> +#include <asm/msr.h> +#include <asm/fpu/xstate.h> + +static bool user_ibt_enabled(struct task_struct *task) +{ + return task->thread.ibt; +} + +bool user_ibt_pop_wait_endbr(struct pt_regs *regs) +{ + struct fpu *fpu = x86_task_fpu(current); + u64 msrval = 0; + + if (!user_ibt_enabled(current)) + return 0; + +#ifdef CONFIG_X86_FRED + if (cpu_feature_enabled(X86_FEATURE_FRED)) { + msrval = regs->fred_cs.wfe; + regs->fred_cs.wfe = 0; + return !!msrval; + } +#endif + + fpregs_lock(); + + if (!test_thread_flag(TIF_NEED_FPU_LOAD)) { + if (!rdmsrq_safe(MSR_IA32_U_CET, &msrval)) + wrmsrq(MSR_IA32_U_CET, msrval & ~CET_WAIT_ENDBR); + } else { + struct cet_user_state *cet; + + /* + * If TIF_NEED_FPU_LOAD and get_xsave_addr() returns zero, + * XFEATURE_CET_USER is in init state (cet is not active). + * Return zero status. + */ + cet = get_xsave_addr(&fpu->fpstate->regs.xsave, + XFEATURE_CET_USER); + if (cet) { + msrval = cet->user_cet; + cet->user_cet = msrval & ~CET_WAIT_ENDBR; + } + } + + fpregs_unlock(); + + return !!(msrval & CET_WAIT_ENDBR); +} + +void +user_ibt_restore_wait_endbr(struct pt_regs *regs, bool wait_endbr) +{ + struct fpu *fpu = x86_task_fpu(current); + u64 msrval = 0; + + if (!user_ibt_enabled(current)) + return; + +#ifdef CONFIG_X86_FRED + if (cpu_feature_enabled(X86_FEATURE_FRED)) { + regs->fred_cs.wfe = wait_endbr; + return; + } +#endif + + if (!wait_endbr) + return; + + fpregs_lock(); + + if (!test_thread_flag(TIF_NEED_FPU_LOAD)) { + if (!rdmsrq_safe(MSR_IA32_U_CET, &msrval)) + wrmsrq(MSR_IA32_U_CET, msrval | CET_WAIT_ENDBR); + } else { + struct cet_user_state *cet; + + cet = get_xsave_addr(&fpu->fpstate->regs.xsave, + XFEATURE_CET_USER); + if (cet) + cet->user_cet |= CET_WAIT_ENDBR; + } + + fpregs_unlock(); +} diff --git a/arch/x86/kernel/signal_64.c b/arch/x86/kernel/signal_64.c index d483b585c6c6..9f1861540d27 100644 --- a/arch/x86/kernel/signal_64.c +++ b/arch/x86/kernel/signal_64.c @@ -14,6 +14,7 @@ #include <asm/ucontext.h> #include <asm/fpu/signal.h> +#include <asm/ibt.h> #include <asm/sighandling.h> #include <asm/syscall.h> @@ -92,6 +93,8 @@ static bool restore_sigcontext(struct pt_regs *regs, if (unlikely(!(uc_flags & UC_STRICT_RESTORE_SS) && user_64bit_mode(regs))) force_valid_ss(regs); + user_ibt_restore_wait_endbr(regs, uc_flags & UC_WAIT_ENDBR); + return fpu__restore_sig((void __user *)sc.fpstate, 0); } @@ -158,6 +161,9 @@ static unsigned long frame_uc_flags(struct pt_regs *regs) if (likely(user_64bit_mode(regs))) flags |= UC_STRICT_RESTORE_SS; + if (user_ibt_pop_wait_endbr(regs)) + flags |= UC_WAIT_ENDBR; + return flags; } -- 2.47.3

