* [PATCH 1/7] x86: add userspace IBT config option
2026-05-17 18:30 [PATCH 0/7] Usermode Indirect Branch Tracking Richard Patel
@ 2026-05-17 18:30 ` Richard Patel
2026-05-17 18:30 ` [PATCH 2/7] x86: shstk: don't clobber IBT bits in U_CET MSR Richard Patel
` (7 subsequent siblings)
8 siblings, 0 replies; 20+ messages in thread
From: Richard Patel @ 2026-05-17 18:30 UTC (permalink / raw)
To: x86
Cc: Rick Edgecombe, Yu-cheng Yu, Dave Hansen, Thomas Gleixner,
Ingo Molnar, Borislav Petkov, H. Peter Anvin, Andy Lutomirski,
Kees Cook, Peter Zijlstra, Shuah Khan, linux-kselftest,
linux-kernel
Adds a X86_USER_IBT Kconfig option and "nouseribt" command-line
option. Default disabled for now.
These prepare for userspace support for IBT (forward-edge control
flow integrity protection).
User IBT works even if kernel IBT is disabled. However, ibt=off
also disables user IBT.
Signed-off-by: Richard Patel <ripatel@wii.dev>
---
arch/x86/Kconfig | 17 +++++++++++++++++
arch/x86/include/asm/cpufeatures.h | 1 +
arch/x86/kernel/cet.c | 3 ++-
arch/x86/kernel/cpu/common.c | 14 ++++++++++++--
tools/arch/x86/include/asm/cpufeatures.h | 1 +
5 files changed, 33 insertions(+), 3 deletions(-)
diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig
index f3f7cb01d69d..12cc944b63c7 100644
--- a/arch/x86/Kconfig
+++ b/arch/x86/Kconfig
@@ -1901,6 +1901,23 @@ config X86_USER_SHADOW_STACK
If unsure, say N.
+config X86_USER_IBT
+ bool "X86 userspace indirect branch tracking"
+ depends on X86_64
+ select X86_CET
+ help
+ Support Indirect Branch Tracking protection for userspace
+ applications. IBT is a hardware-supported coarse-grained
+ forward-edge Control Flow Integrity protection feature.
+ It enforces that all indirect calls must land on an ENDBR
+ instruction. Applications must be enabled to use it, and old
+ userspace does not get protection "for free". Enables the
+ PR_CFI_BRANCH_LANDING_PADS prctl CFI option.
+
+ CPUs supporting IBT were first released in 2021.
+
+ If unsure, say N.
+
config INTEL_TDX_HOST
bool "Intel Trust Domain Extensions (TDX) host support"
depends on CPU_SUP_INTEL
diff --git a/arch/x86/include/asm/cpufeatures.h b/arch/x86/include/asm/cpufeatures.h
index 1d506e5d6f46..1825cbf864c0 100644
--- a/arch/x86/include/asm/cpufeatures.h
+++ b/arch/x86/include/asm/cpufeatures.h
@@ -516,6 +516,7 @@
* and purposes if CLEAR_CPU_BUF_VM is set).
*/
#define X86_FEATURE_X2AVIC_EXT (21*32+20) /* AMD SVM x2AVIC support for 4k vCPUs */
+#define X86_FEATURE_USER_IBT (21*32+21) /* Indirect Branch Tracking for user mode applications */
/*
* BUG word(s)
diff --git a/arch/x86/kernel/cet.c b/arch/x86/kernel/cet.c
index 99444409c026..3ccf47e82da1 100644
--- a/arch/x86/kernel/cet.c
+++ b/arch/x86/kernel/cet.c
@@ -149,7 +149,8 @@ __setup("ibt=", ibt_setup);
DEFINE_IDTENTRY_ERRORCODE(exc_control_protection)
{
if (user_mode(regs)) {
- if (cpu_feature_enabled(X86_FEATURE_USER_SHSTK))
+ if (cpu_feature_enabled(X86_FEATURE_USER_SHSTK) ||
+ cpu_feature_enabled(X86_FEATURE_USER_IBT))
do_user_cp_fault(regs, error_code);
else
do_unexpected_cp(regs, error_code);
diff --git a/arch/x86/kernel/cpu/common.c b/arch/x86/kernel/cpu/common.c
index a4268c47f2bc..2839edd92331 100644
--- a/arch/x86/kernel/cpu/common.c
+++ b/arch/x86/kernel/cpu/common.c
@@ -634,7 +634,7 @@ __noendbr void ibt_restore(u64 save)
static __always_inline void setup_cet(struct cpuinfo_x86 *c)
{
- bool user_shstk, kernel_ibt;
+ bool user_shstk, kernel_ibt, user_ibt;
if (!IS_ENABLED(CONFIG_X86_CET))
return;
@@ -642,13 +642,19 @@ static __always_inline void setup_cet(struct cpuinfo_x86 *c)
kernel_ibt = HAS_KERNEL_IBT && cpu_feature_enabled(X86_FEATURE_IBT);
user_shstk = cpu_feature_enabled(X86_FEATURE_SHSTK) &&
IS_ENABLED(CONFIG_X86_USER_SHADOW_STACK);
+ /* User IBT only needs hardware IBT, not kernel-enabled IBT. */
+ user_ibt = cpu_has(c, X86_FEATURE_IBT) &&
+ IS_ENABLED(CONFIG_X86_USER_IBT);
- if (!kernel_ibt && !user_shstk)
+ if (!kernel_ibt && !user_shstk && !user_ibt)
return;
if (user_shstk)
set_cpu_cap(c, X86_FEATURE_USER_SHSTK);
+ if (user_ibt)
+ set_cpu_cap(c, X86_FEATURE_USER_IBT);
+
if (kernel_ibt)
wrmsrq(MSR_IA32_S_CET, CET_ENDBR_EN);
else
@@ -666,6 +672,7 @@ static __always_inline void setup_cet(struct cpuinfo_x86 *c)
__noendbr void cet_disable(void)
{
if (!(cpu_feature_enabled(X86_FEATURE_IBT) ||
+ cpu_feature_enabled(X86_FEATURE_USER_IBT) ||
cpu_feature_enabled(X86_FEATURE_SHSTK)))
return;
@@ -1760,6 +1767,9 @@ static void __init cpu_parse_early_param(void)
if (cmdline_find_option_bool(boot_command_line, "nousershstk"))
setup_clear_cpu_cap(X86_FEATURE_USER_SHSTK);
+ if (cmdline_find_option_bool(boot_command_line, "nouseribt"))
+ setup_clear_cpu_cap(X86_FEATURE_USER_IBT);
+
/* Minimize the gap between FRED is available and available but disabled. */
arglen = cmdline_find_option(boot_command_line, "fred", arg, sizeof(arg));
if (arglen == 3 && !strncmp(arg, "off", 3))
diff --git a/tools/arch/x86/include/asm/cpufeatures.h b/tools/arch/x86/include/asm/cpufeatures.h
index 86d17b195e79..1cf22d27c7a1 100644
--- a/tools/arch/x86/include/asm/cpufeatures.h
+++ b/tools/arch/x86/include/asm/cpufeatures.h
@@ -515,6 +515,7 @@
* and purposes if CLEAR_CPU_BUF_VM is set).
*/
#define X86_FEATURE_X2AVIC_EXT (21*32+20) /* AMD SVM x2AVIC support for 4k vCPUs */
+#define X86_FEATURE_USER_IBT (21*32+21) /* Indirect Branch Tracking for user mode applications */
/*
* BUG word(s)
--
2.47.3
^ permalink raw reply related [flat|nested] 20+ messages in thread* [PATCH 2/7] x86: shstk: don't clobber IBT bits in U_CET MSR
2026-05-17 18:30 [PATCH 0/7] Usermode Indirect Branch Tracking Richard Patel
2026-05-17 18:30 ` [PATCH 1/7] x86: add userspace IBT config option Richard Patel
@ 2026-05-17 18:30 ` Richard Patel
2026-05-17 18:30 ` [PATCH 3/7] x86: signal handler support for IBT Richard Patel
` (6 subsequent siblings)
8 siblings, 0 replies; 20+ messages in thread
From: Richard Patel @ 2026-05-17 18:30 UTC (permalink / raw)
To: x86
Cc: Rick Edgecombe, Yu-cheng Yu, Dave Hansen, Thomas Gleixner,
Ingo Molnar, Borislav Petkov, H. Peter Anvin, Andy Lutomirski,
Kees Cook, Peter Zijlstra, Shuah Khan, linux-kselftest,
linux-kernel
Updates usermode shadow stack code to not set IBT-related bits in
the U_CET MSR.
Signed-off-by: Richard Patel <ripatel@wii.dev>
---
arch/x86/kernel/shstk.c | 12 ++++++++++--
1 file changed, 10 insertions(+), 2 deletions(-)
diff --git a/arch/x86/kernel/shstk.c b/arch/x86/kernel/shstk.c
index 0ca64900192f..ff4106dcfec4 100644
--- a/arch/x86/kernel/shstk.c
+++ b/arch/x86/kernel/shstk.c
@@ -150,6 +150,7 @@ static int shstk_setup(void)
{
struct thread_shstk *shstk = ¤t->thread.shstk;
unsigned long addr, size;
+ u64 msrval;
/* Already enabled */
if (features_enabled(ARCH_SHSTK_SHSTK))
@@ -166,7 +167,10 @@ static int shstk_setup(void)
fpregs_lock_and_load();
wrmsrq(MSR_IA32_PL3_SSP, addr + size);
- wrmsrq(MSR_IA32_U_CET, CET_SHSTK_EN);
+ rdmsrq(MSR_IA32_U_CET, msrval);
+ msrval &= ~CET_WRSS_EN;
+ msrval |= CET_SHSTK_EN;
+ wrmsrq(MSR_IA32_U_CET, msrval);
fpregs_unlock();
shstk->base = addr;
@@ -520,6 +524,8 @@ static int wrss_control(bool enable)
static int shstk_disable(void)
{
+ u64 msrval;
+
if (!cpu_feature_enabled(X86_FEATURE_USER_SHSTK))
return -EOPNOTSUPP;
@@ -528,8 +534,10 @@ static int shstk_disable(void)
return 0;
fpregs_lock_and_load();
+ rdmsrq(MSR_IA32_U_CET, msrval);
/* Disable WRSS too when disabling shadow stack */
- wrmsrq(MSR_IA32_U_CET, 0);
+ msrval &= ~(CET_SHSTK_EN | CET_WRSS_EN);
+ wrmsrq(MSR_IA32_U_CET, msrval);
wrmsrq(MSR_IA32_PL3_SSP, 0);
fpregs_unlock();
--
2.47.3
^ permalink raw reply related [flat|nested] 20+ messages in thread* [PATCH 3/7] x86: signal handler support for IBT
2026-05-17 18:30 [PATCH 0/7] Usermode Indirect Branch Tracking Richard Patel
2026-05-17 18:30 ` [PATCH 1/7] x86: add userspace IBT config option Richard Patel
2026-05-17 18:30 ` [PATCH 2/7] x86: shstk: don't clobber IBT bits in U_CET MSR Richard Patel
@ 2026-05-17 18:30 ` Richard Patel
2026-05-17 18:30 ` [PATCH 4/7] x86: ban 32-bit sigreturn when user IBT enabled Richard Patel
` (5 subsequent siblings)
8 siblings, 0 replies; 20+ messages in thread
From: Richard Patel @ 2026-05-17 18:30 UTC (permalink / raw)
To: x86
Cc: Rick Edgecombe, Yu-cheng Yu, Dave Hansen, Thomas Gleixner,
Ingo Molnar, Borislav Petkov, H. Peter Anvin, Andy Lutomirski,
Kees Cook, Peter Zijlstra, Shuah Khan, linux-kselftest,
linux-kernel
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 <yu-cheng.yu@intel.com>
Link: https://lore.kernel.org/lkml/20210820182245.1188-4-yu-cheng.yu@intel.com/
Signed-off-by: Richard Patel <ripatel@wii.dev>
---
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
^ permalink raw reply related [flat|nested] 20+ messages in thread* [PATCH 4/7] x86: ban 32-bit sigreturn when user IBT enabled
2026-05-17 18:30 [PATCH 0/7] Usermode Indirect Branch Tracking Richard Patel
` (2 preceding siblings ...)
2026-05-17 18:30 ` [PATCH 3/7] x86: signal handler support for IBT Richard Patel
@ 2026-05-17 18:30 ` Richard Patel
2026-05-18 20:22 ` H. Peter Anvin
2026-05-17 18:30 ` [PATCH 5/7] x86: expose user IBT via PR_CFI_BRANCH_LANDING_PADS Richard Patel
` (4 subsequent siblings)
8 siblings, 1 reply; 20+ messages in thread
From: Richard Patel @ 2026-05-17 18:30 UTC (permalink / raw)
To: x86
Cc: Rick Edgecombe, Yu-cheng Yu, Dave Hansen, Thomas Gleixner,
Ingo Molnar, Borislav Petkov, H. Peter Anvin, Andy Lutomirski,
Kees Cook, Peter Zijlstra, Shuah Khan, linux-kselftest,
linux-kernel
IBT enforces that indirect branch targets land on an endbr
instruction. The CPU enforces this by setting the 'WAIT_FOR_ENDBR'
bit after executing an indirect branch/jump.
The only relevant edge case with user IBT is signal handling:
When entering/leaving a signal handler, the WAIT_FOR_ENDBR bit must
be backed up/restored.
IBT is not implemented for 32-bit and cannot be enabled using a
32-bit syscall. However, a 64-bit thread could far jump into 32-bit.
Therefore, 32-bit sigreturn must be banned until IBT supports that
environment.
Signed-off-by: Richard Patel <ripatel@wii.dev>
Based-on-patch-by: Yu-cheng Yu <yu-cheng.yu@intel.com>
Link: https://lwn.net/ml/linux-kernel/20210830182221.3535-5-yu-cheng.yu@intel.com/
---
arch/x86/kernel/signal_32.c | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/arch/x86/kernel/signal_32.c b/arch/x86/kernel/signal_32.c
index e55cf19e68fe..7cb76d794366 100644
--- a/arch/x86/kernel/signal_32.c
+++ b/arch/x86/kernel/signal_32.c
@@ -143,6 +143,11 @@ static bool ia32_restore_sigcontext(struct pt_regs *regs,
regs->ds = fixup_rpl(sc.ds);
#endif
+#ifdef CONFIG_X86_USER_IBT
+ if (current->thread.ibt)
+ return false;
+#endif
+
return fpu__restore_sig(compat_ptr(sc.fpstate), 1);
}
--
2.47.3
^ permalink raw reply related [flat|nested] 20+ messages in thread* Re: [PATCH 4/7] x86: ban 32-bit sigreturn when user IBT enabled
2026-05-17 18:30 ` [PATCH 4/7] x86: ban 32-bit sigreturn when user IBT enabled Richard Patel
@ 2026-05-18 20:22 ` H. Peter Anvin
2026-05-19 0:14 ` Richard Patel
0 siblings, 1 reply; 20+ messages in thread
From: H. Peter Anvin @ 2026-05-18 20:22 UTC (permalink / raw)
To: Richard Patel, x86
Cc: Rick Edgecombe, Yu-cheng Yu, Dave Hansen, Thomas Gleixner,
Ingo Molnar, Borislav Petkov, Andy Lutomirski, Kees Cook,
Peter Zijlstra, Shuah Khan, linux-kselftest, linux-kernel
On May 17, 2026 11:30:21 AM PDT, Richard Patel <ripatel@wii.dev> wrote:
>IBT enforces that indirect branch targets land on an endbr
>instruction. The CPU enforces this by setting the 'WAIT_FOR_ENDBR'
>bit after executing an indirect branch/jump.
>
>The only relevant edge case with user IBT is signal handling:
>When entering/leaving a signal handler, the WAIT_FOR_ENDBR bit must
>be backed up/restored.
>
>IBT is not implemented for 32-bit and cannot be enabled using a
>32-bit syscall. However, a 64-bit thread could far jump into 32-bit.
>Therefore, 32-bit sigreturn must be banned until IBT supports that
>environment.
>
>Signed-off-by: Richard Patel <ripatel@wii.dev>
>Based-on-patch-by: Yu-cheng Yu <yu-cheng.yu@intel.com>
>Link: https://lwn.net/ml/linux-kernel/20210830182221.3535-5-yu-cheng.yu@intel.com/
>---
> arch/x86/kernel/signal_32.c | 5 +++++
> 1 file changed, 5 insertions(+)
>
>diff --git a/arch/x86/kernel/signal_32.c b/arch/x86/kernel/signal_32.c
>index e55cf19e68fe..7cb76d794366 100644
>--- a/arch/x86/kernel/signal_32.c
>+++ b/arch/x86/kernel/signal_32.c
>@@ -143,6 +143,11 @@ static bool ia32_restore_sigcontext(struct pt_regs *regs,
> regs->ds = fixup_rpl(sc.ds);
> #endif
>
>+#ifdef CONFIG_X86_USER_IBT
>+ if (current->thread.ibt)
>+ return false;
>+#endif
>+
> return fpu__restore_sig(compat_ptr(sc.fpstate), 1);
> }
>
Dumb question: is there any reason not to just enable it for 32 bits? It doesn't seem that it would be that big of a delta to Just Do It.™
That being said, I suspect the number of users will be very small if any.
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH 4/7] x86: ban 32-bit sigreturn when user IBT enabled
2026-05-18 20:22 ` H. Peter Anvin
@ 2026-05-19 0:14 ` Richard Patel
0 siblings, 0 replies; 20+ messages in thread
From: Richard Patel @ 2026-05-19 0:14 UTC (permalink / raw)
To: H. Peter Anvin
Cc: x86, Rick Edgecombe, Yu-cheng Yu, Dave Hansen, Thomas Gleixner,
Ingo Molnar, Borislav Petkov, Andy Lutomirski, Kees Cook,
Peter Zijlstra, Shuah Khan, linux-kselftest, linux-kernel
On Mon, May 18, 2026 at 01:22:19PM -0700, H. Peter Anvin wrote:
> On May 17, 2026 11:30:21 AM PDT, Richard Patel <ripatel@wii.dev> wrote:
> >IBT is not implemented for 32-bit and cannot be enabled using a
> >32-bit syscall. However, a 64-bit thread could far jump into 32-bit.
> >Therefore, 32-bit sigreturn must be banned until IBT supports that
> >environment.
> Dumb question: is there any reason not to just enable it for 32 bits? It doesn't seem that it would be that big of a delta to Just Do It.™
>
> That being said, I suspect the number of users will be very small if any.
Yes, partially, good call. It'd be tricky for legacy IA32 signals.
sigframe_ia32 would need to be modified I think. For rt_sigframe_ia32
(rt_sigreturn) we can put it in ucontext_ia32::uc_flags.
I'll fix it in v2 later this week and add tests.
^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH 5/7] x86: expose user IBT via PR_CFI_BRANCH_LANDING_PADS
2026-05-17 18:30 [PATCH 0/7] Usermode Indirect Branch Tracking Richard Patel
` (3 preceding siblings ...)
2026-05-17 18:30 ` [PATCH 4/7] x86: ban 32-bit sigreturn when user IBT enabled Richard Patel
@ 2026-05-17 18:30 ` Richard Patel
2026-05-18 6:46 ` Richard Patel
2026-05-17 18:30 ` [PATCH 6/7] x86/entry/vdso: build with IBT support Richard Patel
` (3 subsequent siblings)
8 siblings, 1 reply; 20+ messages in thread
From: Richard Patel @ 2026-05-17 18:30 UTC (permalink / raw)
To: x86
Cc: Rick Edgecombe, Yu-cheng Yu, Dave Hansen, Thomas Gleixner,
Ingo Molnar, Borislav Petkov, H. Peter Anvin, Andy Lutomirski,
Kees Cook, Peter Zijlstra, Shuah Khan, linux-kselftest,
linux-kernel
Allows userspace applications to enable IBT (forward-edge control
flow integrity protection) using the portable PR_CFI prctl API.
The name 'branch landing pads' is RISC-V specific, but the mechanism
is nearly identical in x86.
This setting enables the following MSR_IA32_U_CET bits:
- CET_ENDBR_EN (enforce endbr as indirect branch target)
- CET_NOTRACK_EN (jump modifier to opt-out of IBT checking)
Kernel-mode IBT (as part of CFI) bans notrack. A future prctl flag
could be introduced to ban notrack in usermode too.
Signed-off-by: Richard Patel <ripatel@wii.dev>
---
arch/x86/include/asm/ibt.h | 2 +
arch/x86/kernel/ibt.c | 87 ++++++++++++++++++++++++++++++++++++
arch/x86/kernel/process_64.c | 2 +
3 files changed, 91 insertions(+)
diff --git a/arch/x86/include/asm/ibt.h b/arch/x86/include/asm/ibt.h
index 3fe464bf83e7..a67c9ceaaf9e 100644
--- a/arch/x86/include/asm/ibt.h
+++ b/arch/x86/include/asm/ibt.h
@@ -121,9 +121,11 @@ 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);
+void reset_thread_ibt(void);
#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) {}
+static inline void reset_thread_ibt(void) {}
#endif /* CONFIG_X86_USER_IBT */
#endif /* __ASSEMBLER__ */
diff --git a/arch/x86/kernel/ibt.c b/arch/x86/kernel/ibt.c
index 596b0629106d..343e6fd5dab0 100644
--- a/arch/x86/kernel/ibt.c
+++ b/arch/x86/kernel/ibt.c
@@ -1,6 +1,8 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/types.h>
+#include <linux/cpu.h>
+#include <linux/prctl.h>
#include <asm/msr.h>
#include <asm/fpu/xstate.h>
@@ -9,6 +11,85 @@ static bool user_ibt_enabled(struct task_struct *task)
return task->thread.ibt;
}
+static bool user_ibt_locked(struct task_struct *task)
+{
+ return task->thread.ibt_locked;
+}
+
+static void user_ibt_set_lock(struct task_struct *task, bool lock)
+{
+ task->thread.ibt_locked = lock;
+}
+
+static void user_ibt_set_enable(bool enable)
+{
+ u64 msrval;
+
+ /* Already enabled */
+ if (user_ibt_enabled(current) == enable)
+ return;
+
+ current->thread.ibt = !!enable;
+
+ fpregs_lock_and_load();
+ rdmsrq(MSR_IA32_U_CET, msrval);
+ if (enable)
+ msrval |= CET_ENDBR_EN | CET_NO_TRACK_EN;
+ else
+ msrval &= ~(CET_ENDBR_EN | CET_NO_TRACK_EN);
+ msrval &= ~CET_WAIT_ENDBR;
+ wrmsrq(MSR_IA32_U_CET, msrval);
+ fpregs_unlock();
+}
+
+int arch_prctl_get_branch_landing_pad_state(struct task_struct *t,
+ unsigned long __user *state)
+{
+ unsigned long status = 0;
+
+ if (!cpu_feature_enabled(X86_FEATURE_USER_IBT) || in_ia32_syscall())
+ return -EINVAL;
+
+ status = (user_ibt_enabled(t) ? PR_CFI_ENABLE : PR_CFI_DISABLE);
+ status |= (user_ibt_locked(t) ? PR_CFI_LOCK : 0);
+
+ return copy_to_user(state, &status, sizeof(status)) ? -EFAULT : 0;
+}
+
+int arch_prctl_set_branch_landing_pad_state(struct task_struct *t, unsigned long state)
+{
+ if (!cpu_feature_enabled(X86_FEATURE_USER_IBT) || in_ia32_syscall())
+ return -EINVAL;
+
+ if (t != current)
+ return -EINVAL;
+
+ if (user_ibt_locked(t))
+ return -EINVAL;
+
+ if (!(state & (PR_CFI_ENABLE | PR_CFI_DISABLE)))
+ return -EINVAL;
+
+ if (state & PR_CFI_ENABLE && state & PR_CFI_DISABLE)
+ return -EINVAL;
+
+ user_ibt_set_enable(!!(state & PR_CFI_ENABLE));
+
+ return 0;
+}
+
+int arch_prctl_lock_branch_landing_pad_state(struct task_struct *task)
+{
+ if (!cpu_feature_enabled(X86_FEATURE_USER_IBT) ||
+ !user_ibt_enabled(task) ||
+ in_ia32_syscall())
+ return -EINVAL;
+
+ user_ibt_set_lock(task, true);
+
+ return 0;
+}
+
bool user_ibt_pop_wait_endbr(struct pt_regs *regs)
{
struct fpu *fpu = x86_task_fpu(current);
@@ -86,3 +167,9 @@ user_ibt_restore_wait_endbr(struct pt_regs *regs, bool wait_endbr)
fpregs_unlock();
}
+
+void reset_thread_ibt(void)
+{
+ current->thread.ibt = false;
+ current->thread.ibt_locked = false;
+}
diff --git a/arch/x86/kernel/process_64.c b/arch/x86/kernel/process_64.c
index b85e715ebb30..4b727cc7bccb 100644
--- a/arch/x86/kernel/process_64.c
+++ b/arch/x86/kernel/process_64.c
@@ -59,6 +59,7 @@
#include <asm/fsgsbase.h>
#include <asm/fred.h>
#include <asm/msr.h>
+#include <asm/ibt.h>
#ifdef CONFIG_IA32_EMULATION
/* Not included via unistd.h */
#include <asm/unistd_32_ia32.h>
@@ -540,6 +541,7 @@ start_thread_common(struct pt_regs *regs, unsigned long new_ip,
}
reset_thread_features();
+ reset_thread_ibt();
loadsegment(fs, 0);
loadsegment(es, _ds);
--
2.47.3
^ permalink raw reply related [flat|nested] 20+ messages in thread* Re: [PATCH 5/7] x86: expose user IBT via PR_CFI_BRANCH_LANDING_PADS
2026-05-17 18:30 ` [PATCH 5/7] x86: expose user IBT via PR_CFI_BRANCH_LANDING_PADS Richard Patel
@ 2026-05-18 6:46 ` Richard Patel
0 siblings, 0 replies; 20+ messages in thread
From: Richard Patel @ 2026-05-18 6:46 UTC (permalink / raw)
To: x86
Cc: Rick Edgecombe, Yu-cheng Yu, Dave Hansen, Thomas Gleixner,
Ingo Molnar, Borislav Petkov, H. Peter Anvin, Andy Lutomirski,
Kees Cook, Peter Zijlstra, Shuah Khan, linux-kselftest,
linux-kernel
Thinking out loud ... sorry for the noise
On Sun, May 17, 2026 at 01:30:22PM -0500, Richard Patel wrote:
> +int arch_prctl_set_branch_landing_pad_state(struct task_struct *t, unsigned long state)
> +{
> + if (!cpu_feature_enabled(X86_FEATURE_USER_IBT) || in_ia32_syscall())
> + return -EINVAL;
> +
> + if (t != current)
> + return -EINVAL;
> +
> + if (user_ibt_locked(t))
> + return -EINVAL;
> +
> + if (!(state & (PR_CFI_ENABLE | PR_CFI_DISABLE)))
> + return -EINVAL;
> +
> + if (state & PR_CFI_ENABLE && state & PR_CFI_DISABLE)
> + return -EINVAL;
I noticed the caller doesn't filter input arguments, so uapi could pass
garbage flags, and we need to filter here. Will fix and add a selftest
for this in v2.
I think the RISC-V code from which I ported this has the same problem.
> +void reset_thread_ibt(void)
> +{
> + current->thread.ibt = false;
> + current->thread.ibt_locked = false;
> +}
Should I add a comment here explaining why setting U_CET MSR is not
necessary here?
> reset_thread_features();
> + reset_thread_ibt();
There is quite obvious overlap with ARCH_SHSTK here. The problem is that
'features' is gated by USER_SHADOW_STACK, and I didn't want to cause a
mess there before asking. x86 maintainers, what is your preference?
^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH 6/7] x86/entry/vdso: build with IBT support
2026-05-17 18:30 [PATCH 0/7] Usermode Indirect Branch Tracking Richard Patel
` (4 preceding siblings ...)
2026-05-17 18:30 ` [PATCH 5/7] x86: expose user IBT via PR_CFI_BRANCH_LANDING_PADS Richard Patel
@ 2026-05-17 18:30 ` Richard Patel
2026-05-17 18:30 ` [PATCH 7/7] selftests/x86: test usermode IBT Richard Patel
` (2 subsequent siblings)
8 siblings, 0 replies; 20+ messages in thread
From: Richard Patel @ 2026-05-17 18:30 UTC (permalink / raw)
To: x86
Cc: Rick Edgecombe, Yu-cheng Yu, Dave Hansen, Thomas Gleixner,
Ingo Molnar, Borislav Petkov, H. Peter Anvin, Andy Lutomirski,
Kees Cook, Peter Zijlstra, Shuah Khan, linux-kselftest,
linux-kernel
VDSO should expose ENDBR instructions now that usermode IBT is
available.
Signed-off-by: Richard Patel <ripatel@wii.dev>
---
arch/x86/entry/vdso/common/Makefile.include | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/arch/x86/entry/vdso/common/Makefile.include b/arch/x86/entry/vdso/common/Makefile.include
index 687b3d89b40d..a0dc69b7a330 100644
--- a/arch/x86/entry/vdso/common/Makefile.include
+++ b/arch/x86/entry/vdso/common/Makefile.include
@@ -46,8 +46,7 @@ flags-y += -fasynchronous-unwind-tables
# Reset cf protections enabled by compiler default
flags-y += $(call cc-option, -fcf-protection=none)
flags-$(X86_USER_SHADOW_STACK) += $(call cc-option, -fcf-protection=return)
-# When user space IBT is supported, enable this.
-# flags-$(CONFIG_USER_IBT) += $(call cc-option, -fcf-protection=branch)
+flags-$(CONFIG_X86_USER_IBT) += $(call cc-option, -fcf-protection=branch)
flags-$(CONFIG_MITIGATION_RETPOLINE) += $(RETPOLINE_VDSO_CFLAGS)
--
2.47.3
^ permalink raw reply related [flat|nested] 20+ messages in thread* [PATCH 7/7] selftests/x86: test usermode IBT
2026-05-17 18:30 [PATCH 0/7] Usermode Indirect Branch Tracking Richard Patel
` (5 preceding siblings ...)
2026-05-17 18:30 ` [PATCH 6/7] x86/entry/vdso: build with IBT support Richard Patel
@ 2026-05-17 18:30 ` Richard Patel
2026-05-18 7:36 ` [PATCH 0/7] Usermode Indirect Branch Tracking Peter Zijlstra
2026-05-19 9:33 ` David Laight
8 siblings, 0 replies; 20+ messages in thread
From: Richard Patel @ 2026-05-17 18:30 UTC (permalink / raw)
To: x86
Cc: Rick Edgecombe, Yu-cheng Yu, Dave Hansen, Thomas Gleixner,
Ingo Molnar, Borislav Petkov, H. Peter Anvin, Andy Lutomirski,
Kees Cook, Peter Zijlstra, Shuah Khan, linux-kselftest,
linux-kernel
Adds a basic selftest exercising a usermode IBT violation.
Signed-off-by: Richard Patel <ripatel@wii.dev>
---
tools/testing/selftests/x86/Makefile | 5 +-
tools/testing/selftests/x86/user_ibt.c | 157 +++++++++++++++++++++++++
2 files changed, 161 insertions(+), 1 deletion(-)
create mode 100644 tools/testing/selftests/x86/user_ibt.c
diff --git a/tools/testing/selftests/x86/Makefile b/tools/testing/selftests/x86/Makefile
index 434065215d12..203c3d9073f2 100644
--- a/tools/testing/selftests/x86/Makefile
+++ b/tools/testing/selftests/x86/Makefile
@@ -19,7 +19,8 @@ TARGETS_C_32BIT_ONLY := entry_from_vm86 test_syscall_vdso unwind_vdso \
test_FCMOV test_FCOMI test_FISTTP \
vdso_restorer
TARGETS_C_64BIT_ONLY := fsgsbase sysret_rip syscall_numbering \
- corrupt_xstate_header amx lam test_shadow_stack avx apx
+ corrupt_xstate_header amx lam test_shadow_stack avx apx \
+ user_ibt
# Some selftests require 32bit support enabled also on 64bit systems
TARGETS_C_32BIT_NEEDED := ldt_gdt ptrace_syscall
@@ -138,3 +139,5 @@ $(OUTPUT)/avx_64: CFLAGS += -mno-avx -mno-avx512f
$(OUTPUT)/amx_64: EXTRA_FILES += xstate.c
$(OUTPUT)/avx_64: EXTRA_FILES += xstate.c
$(OUTPUT)/apx_64: EXTRA_FILES += xstate.c
+
+$(OUTPUT)/user_ibt_64: CFLAGS += -fcf-protection=branch
diff --git a/tools/testing/selftests/x86/user_ibt.c b/tools/testing/selftests/x86/user_ibt.c
new file mode 100644
index 000000000000..795011503335
--- /dev/null
+++ b/tools/testing/selftests/x86/user_ibt.c
@@ -0,0 +1,157 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Test kernel support for userspace Indirect Branch Tracking (IBT).
+ * Enables IBT manually via prctl(). Must be compiled with
+ * -fcf-protection=branch to enable ENDBR64 instrumentation.
+ */
+
+#define _GNU_SOURCE
+
+#include <errno.h>
+#include <setjmp.h>
+#include <signal.h>
+#include <sys/signal.h>
+#include <sys/syscall.h>
+#include <linux/const.h>
+#include <linux/prctl.h>
+#include <unistd.h>
+#include "../kselftest.h"
+
+/*
+ * Allow building this test with old kernel headers.
+ */
+#ifndef PR_SET_CFI
+#define PR_GET_CFI 80
+#define PR_SET_CFI 81
+#endif
+#ifndef PR_CFI_ENABLE
+#define PR_CFI_ENABLE (1UL << 0)
+#define PR_CFI_DISABLE (1UL << 1)
+#endif
+#ifndef PR_CFI_BRANCH_LANDING_PADS
+#define PR_CFI_BRANCH_LANDING_PADS 0
+#endif
+#ifndef SEGV_CPERR
+#define SEGV_CPERR 10
+#endif
+
+#if !defined(__CET__) || (__CET__ & 1) != 1
+int main(int argc, char *argv[])
+{
+ ksft_print_header();
+ ksft_exit_skip("Compiler does not support CET.\n");
+ return 0;
+}
+#else
+void __attribute__((naked)) valid_target(void)
+{
+ asm volatile (
+ "endbr64\n"
+ "ret\n"
+ );
+}
+
+void __attribute__((nocf_check, naked)) invalid_target(void)
+{
+ asm volatile ("ret\n");
+}
+
+void __attribute__((naked)) user_ibt_basic_test(void)
+{
+ asm volatile (
+ "leaq valid_target(%rip), %rax\n"
+ "call *%rax\n"
+ "ret\n"
+ );
+}
+
+void __attribute__((naked)) user_ibt_notrack_test(void)
+{
+ asm volatile (
+ "leaq invalid_target(%rip), %rax\n"
+ "notrack call *%rax\n"
+ "ret\n"
+ );
+}
+
+static sigjmp_buf jmpbuf;
+static sig_atomic_t got_signal;
+static sig_atomic_t got_cperr;
+
+static void segv_handler(int signum, siginfo_t *si, void *uc)
+{
+ got_signal = true;
+ got_cperr = si->si_code == SEGV_CPERR;
+ siglongjmp(jmpbuf, 1);
+}
+
+int user_ibt_violation_test(void)
+{
+ struct sigaction sa = {};
+ struct sigaction oldsa;
+ size_t volatile ptr;
+
+ got_signal = false;
+ got_cperr = false;
+
+ sa.sa_sigaction = segv_handler;
+ sa.sa_flags = SA_SIGINFO;
+ if (sigaction(SIGSEGV, &sa, &oldsa)) {
+ ksft_perror("SIGSEGV handler setup failed");
+ return 1;
+ }
+
+ if (!sigsetjmp(jmpbuf, 1)) {
+ /*
+ * Force an indirect call and drop 'nocf_check' attribute.
+ * Obfuscate cast through a temp local to suppress
+ * -Wincompatible-pointer-types (which correctly detects
+ * the nocf_check attribute mismatch)
+ */
+ ptr = (size_t)invalid_target;
+ ((void (* volatile)(void))ptr)();
+ /* Fall through in case this didn't SIGSEGV */
+ }
+
+ if (sigaction(SIGSEGV, &oldsa, NULL)) {
+ ksft_perror("SIGSEGV handler restore failed");
+ return 1;
+ }
+
+ if (!got_signal) {
+ ksft_print_msg("IBT violation did not generate SIGSEGV\n");
+ return 1;
+ }
+ if (!got_cperr) {
+ ksft_print_msg("IBT violation generated unknown segfault\n");
+ return 1;
+ }
+
+ return 0;
+}
+
+int main(int argc, char *argv[])
+{
+ unsigned long lpad_status = PR_CFI_ENABLE;
+
+ ksft_print_header();
+ ksft_set_plan(3);
+
+ if (syscall(__NR_prctl, PR_SET_CFI, PR_CFI_BRANCH_LANDING_PADS, lpad_status, 0, 0)) {
+ if (errno == EINVAL || errno == EOPNOTSUPP)
+ ksft_exit_skip("User IBT is not supported.\n");
+ ksft_exit_fail_perror("Failed to enable user IBT");
+ }
+
+ user_ibt_basic_test();
+ ksft_test_result_pass("valid indirect call with endbr64\n");
+
+ user_ibt_notrack_test();
+ ksft_test_result_pass("notrack indirect call to non-endbr target\n");
+
+ ksft_test_result(!user_ibt_violation_test(),
+ "indirect call to non-endbr target raises SIGSEGV\n");
+
+ ksft_finished();
+}
+#endif
--
2.47.3
^ permalink raw reply related [flat|nested] 20+ messages in thread* Re: [PATCH 0/7] Usermode Indirect Branch Tracking
2026-05-17 18:30 [PATCH 0/7] Usermode Indirect Branch Tracking Richard Patel
` (6 preceding siblings ...)
2026-05-17 18:30 ` [PATCH 7/7] selftests/x86: test usermode IBT Richard Patel
@ 2026-05-18 7:36 ` Peter Zijlstra
2026-05-18 16:25 ` Richard Patel
2026-05-19 9:33 ` David Laight
8 siblings, 1 reply; 20+ messages in thread
From: Peter Zijlstra @ 2026-05-18 7:36 UTC (permalink / raw)
To: Richard Patel
Cc: x86, Rick Edgecombe, Yu-cheng Yu, Dave Hansen, Thomas Gleixner,
Ingo Molnar, Borislav Petkov, H. Peter Anvin, Andy Lutomirski,
Kees Cook, Shuah Khan, linux-kselftest, linux-kernel
On Sun, May 17, 2026 at 01:30:17PM -0500, Richard Patel wrote:
> I was quite surprised that the Linux kernel still does not allow
> userspace to enable x86 IBT (indirect jmp/call integrity).
>
> Compilers and linkers have been emitting 'endbr64' IBT markers and ELF
> support notes for a while now.
>
> The hard work was done years ago by Intel:
> https://lore.kernel.org/all/20210830182221.3535-1-yu-cheng.yu@intel.com/
>
> In summary, usermode IBT requires 3 things:
> 1. Set the CET_ENDBR_EN bit in MSR_IA32_U_CET for each IBT-enabled thread
> (PATCH 2,5)
> 2. Back up the WAIT_FOR_ENDBR bit across signal handling (PATCH 3,4)
> 3. Provide a way for usermode to enable it (PATCH 5)
>
> This builds on top of Yu Cheng's work, with some adaptations:
> - FRED support
> - Implemented the existing prctl(PR_CFI_*) API
> - Removed ELF parsing (can be added later)
>
> Unresolved questions:
> - Is there a cleaner way to do the WAIT_FOR_ENDBR XSAVE fallback?
> - What to do about 'notrack jmp *rax'?
> I leave CET_NO_TRACK_EN enabled, which weakens IBT, by enabling a jump
> prefix that skips the ENDBR check. GCC emits it for jump tables
> (-mcet-switch). We could introduce a PR_CFI_IBT_STRICT bit.
> - There's some obvious overlap with arch_prctl(ARCH_SHSTK_*).
> Happy to use that API instead.
Right, I think the problem was mostly one of ABI. Various distributions
shipped with 'early' patches using an ABI that hadn't seen upstream
review much, and when it hit we went uhhh, no!
And then glibc and distros were like, but uhmm, it has to be.
So here we are :-(
Anyway, the most contentious part was the whole backwards compat bitmap
crap. When the dynamic linker composes a process of parts that support
IBT and parts that do not, you get to deal with fallout.
The IBT spec has this horrid bitmap thing to try and deal with this, and
those early patches exposed that piece of shit to userspace. Then later
patches (suggested by me) used the ARM64/BTI approach of using PROT_BTI.
We'd use a (software) page-table bit, and upon #CP consult that to see
if we should eat the trap or produce a warn/signal whatever.
I think we were near something workable there when Rick got pulled from
this and put onto something more 'important' and things just haven't
moved ever since.
Anyway, glad to see someone has time to poke at this.
^ permalink raw reply [flat|nested] 20+ messages in thread* Re: [PATCH 0/7] Usermode Indirect Branch Tracking
2026-05-18 7:36 ` [PATCH 0/7] Usermode Indirect Branch Tracking Peter Zijlstra
@ 2026-05-18 16:25 ` Richard Patel
2026-05-18 19:31 ` Peter Zijlstra
0 siblings, 1 reply; 20+ messages in thread
From: Richard Patel @ 2026-05-18 16:25 UTC (permalink / raw)
To: Peter Zijlstra
Cc: x86, Rick Edgecombe, Yu-cheng Yu, Dave Hansen, Thomas Gleixner,
Ingo Molnar, Borislav Petkov, H. Peter Anvin, Andy Lutomirski,
Kees Cook, Shuah Khan, linux-kselftest, linux-kernel
On Mon, May 18, 2026 at 09:36:16AM +0200, Peter Zijlstra wrote:
> Anyway, the most contentious part was the whole backwards compat bitmap
> crap. When the dynamic linker composes a process of parts that support
> IBT and parts that do not, you get to deal with fallout.
Is it acceptable to do all-or-nothing IBT first? And then do a second
round of patches with legacy support?
Until then, ld.so could:
- start with IBT, disable it upon loading incompatible DSO
- allow users to manually lock IBT
I thought this weak form of IBT is better than nothing at all, if
there's a risk that legacy support derails things.
Btw, apparently OpenBSD enforces kernel+user IBT. I think the end goal
is a user_ibt=force command-line param that locks IBT for all processes
on startup.
> The IBT spec has this horrid bitmap thing to try and deal with this, and
> those early patches exposed that piece of shit to userspace. Then later
> patches (suggested by me) used the ARM64/BTI approach of using PROT_BTI.
> We'd use a (software) page-table bit, and upon #CP consult that to see
> if we should eat the trap or produce a warn/signal whatever.
Nice, I'm happy to revive/rebase/test any of this if there's interest.
> I think we were near something workable there when Rick got pulled from
> this and put onto something more 'important' and things just haven't
> moved ever since.
>
> Anyway, glad to see someone has time to poke at this.
Happy to spend whatever time is needed to land IBT. I'm very glad the
first reaction wasn't "absolutely no way" :-)
Thank you,
-Richard
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH 0/7] Usermode Indirect Branch Tracking
2026-05-18 16:25 ` Richard Patel
@ 2026-05-18 19:31 ` Peter Zijlstra
0 siblings, 0 replies; 20+ messages in thread
From: Peter Zijlstra @ 2026-05-18 19:31 UTC (permalink / raw)
To: Richard Patel
Cc: x86, Rick Edgecombe, Yu-cheng Yu, Dave Hansen, Thomas Gleixner,
Ingo Molnar, Borislav Petkov, H. Peter Anvin, Andy Lutomirski,
Kees Cook, Shuah Khan, linux-kselftest, linux-kernel
+ Florian
On Mon, May 18, 2026 at 04:25:40PM +0000, Richard Patel wrote:
> On Mon, May 18, 2026 at 09:36:16AM +0200, Peter Zijlstra wrote:
> > Anyway, the most contentious part was the whole backwards compat bitmap
> > crap. When the dynamic linker composes a process of parts that support
> > IBT and parts that do not, you get to deal with fallout.
>
> Is it acceptable to do all-or-nothing IBT first? And then do a second
> round of patches with legacy support?
>
> Until then, ld.so could:
> - start with IBT, disable it upon loading incompatible DSO
> - allow users to manually lock IBT
>
> I thought this weak form of IBT is better than nothing at all, if
> there's a risk that legacy support derails things.
That wholly depends on how much churn glibc people are willing to put up
with. Added Florian to answer some of that.
> Btw, apparently OpenBSD enforces kernel+user IBT. I think the end goal
> is a user_ibt=force command-line param that locks IBT for all processes
> on startup.
>
> > The IBT spec has this horrid bitmap thing to try and deal with this, and
> > those early patches exposed that piece of shit to userspace. Then later
> > patches (suggested by me) used the ARM64/BTI approach of using PROT_BTI.
> > We'd use a (software) page-table bit, and upon #CP consult that to see
> > if we should eat the trap or produce a warn/signal whatever.
>
> Nice, I'm happy to revive/rebase/test any of this if there's interest.
>
> > I think we were near something workable there when Rick got pulled from
> > this and put onto something more 'important' and things just haven't
> > moved ever since.
> >
> > Anyway, glad to see someone has time to poke at this.
>
> Happy to spend whatever time is needed to land IBT. I'm very glad the
> first reaction wasn't "absolutely no way" :-)
Yeah, its just something that fell between the cracks :/
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH 0/7] Usermode Indirect Branch Tracking
2026-05-17 18:30 [PATCH 0/7] Usermode Indirect Branch Tracking Richard Patel
` (7 preceding siblings ...)
2026-05-18 7:36 ` [PATCH 0/7] Usermode Indirect Branch Tracking Peter Zijlstra
@ 2026-05-19 9:33 ` David Laight
2026-05-19 9:40 ` Peter Zijlstra
2026-05-19 13:14 ` Richard Patel
8 siblings, 2 replies; 20+ messages in thread
From: David Laight @ 2026-05-19 9:33 UTC (permalink / raw)
To: Richard Patel
Cc: x86, Rick Edgecombe, Yu-cheng Yu, Dave Hansen, Thomas Gleixner,
Ingo Molnar, Borislav Petkov, H. Peter Anvin, Andy Lutomirski,
Kees Cook, Peter Zijlstra, Shuah Khan, linux-kselftest,
linux-kernel
On Sun, 17 May 2026 13:30:17 -0500
Richard Patel <ripatel@wii.dev> wrote:
> I was quite surprised that the Linux kernel still does not allow
> userspace to enable x86 IBT (indirect jmp/call integrity).
>
> Compilers and linkers have been emitting 'endbr64' IBT markers and ELF
> support notes for a while now.
>
> The hard work was done years ago by Intel:
> https://lore.kernel.org/all/20210830182221.3535-1-yu-cheng.yu@intel.com/
>
> In summary, usermode IBT requires 3 things:
> 1. Set the CET_ENDBR_EN bit in MSR_IA32_U_CET for each IBT-enabled thread
> (PATCH 2,5)
> 2. Back up the WAIT_FOR_ENDBR bit across signal handling (PATCH 3,4)
> 3. Provide a way for usermode to enable it (PATCH 5)
>
> This builds on top of Yu Cheng's work, with some adaptations:
> - FRED support
> - Implemented the existing prctl(PR_CFI_*) API
> - Removed ELF parsing (can be added later)
>
> Unresolved questions:
> - Is there a cleaner way to do the WAIT_FOR_ENDBR XSAVE fallback?
> - What to do about 'notrack jmp *rax'?
> I leave CET_NO_TRACK_EN enabled, which weakens IBT, by enabling a jump
> prefix that skips the ENDBR check. GCC emits it for jump tables
> (-mcet-switch). We could introduce a PR_CFI_IBT_STRICT bit.
Isn't using 'notrack jmp *reg' for jump tables actually more secure?
If an attacker can write code it doesn't matter.
The jump table in is RO memory so can't be written.
But if there are ENDBR on all the jump table targets they become
possibly useful code addresses to arrange to write into some RW
function pointer table - which might be useful.
-- David
> - There's some obvious overlap with arch_prctl(ARCH_SHSTK_*).
> Happy to use that API instead.
...
^ permalink raw reply [flat|nested] 20+ messages in thread* Re: [PATCH 0/7] Usermode Indirect Branch Tracking
2026-05-19 9:33 ` David Laight
@ 2026-05-19 9:40 ` Peter Zijlstra
2026-05-19 13:14 ` Richard Patel
1 sibling, 0 replies; 20+ messages in thread
From: Peter Zijlstra @ 2026-05-19 9:40 UTC (permalink / raw)
To: David Laight
Cc: Richard Patel, x86, Rick Edgecombe, Yu-cheng Yu, Dave Hansen,
Thomas Gleixner, Ingo Molnar, Borislav Petkov, H. Peter Anvin,
Andy Lutomirski, Kees Cook, Shuah Khan, linux-kselftest,
linux-kernel
On Tue, May 19, 2026 at 10:33:45AM +0100, David Laight wrote:
> On Sun, 17 May 2026 13:30:17 -0500
> Richard Patel <ripatel@wii.dev> wrote:
>
> > I was quite surprised that the Linux kernel still does not allow
> > userspace to enable x86 IBT (indirect jmp/call integrity).
> >
> > Compilers and linkers have been emitting 'endbr64' IBT markers and ELF
> > support notes for a while now.
> >
> > The hard work was done years ago by Intel:
> > https://lore.kernel.org/all/20210830182221.3535-1-yu-cheng.yu@intel.com/
> >
> > In summary, usermode IBT requires 3 things:
> > 1. Set the CET_ENDBR_EN bit in MSR_IA32_U_CET for each IBT-enabled thread
> > (PATCH 2,5)
> > 2. Back up the WAIT_FOR_ENDBR bit across signal handling (PATCH 3,4)
> > 3. Provide a way for usermode to enable it (PATCH 5)
> >
> > This builds on top of Yu Cheng's work, with some adaptations:
> > - FRED support
> > - Implemented the existing prctl(PR_CFI_*) API
> > - Removed ELF parsing (can be added later)
> >
> > Unresolved questions:
> > - Is there a cleaner way to do the WAIT_FOR_ENDBR XSAVE fallback?
> > - What to do about 'notrack jmp *rax'?
> > I leave CET_NO_TRACK_EN enabled, which weakens IBT, by enabling a jump
> > prefix that skips the ENDBR check. GCC emits it for jump tables
> > (-mcet-switch). We could introduce a PR_CFI_IBT_STRICT bit.
>
> Isn't using 'notrack jmp *reg' for jump tables actually more secure?
> If an attacker can write code it doesn't matter.
> The jump table in is RO memory so can't be written.
> But if there are ENDBR on all the jump table targets they become
> possibly useful code addresses to arrange to write into some RW
> function pointer table - which might be useful.
One of the many reasons the kernel is built without jump-tables.
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH 0/7] Usermode Indirect Branch Tracking
2026-05-19 9:33 ` David Laight
2026-05-19 9:40 ` Peter Zijlstra
@ 2026-05-19 13:14 ` Richard Patel
2026-05-19 13:28 ` David Laight
1 sibling, 1 reply; 20+ messages in thread
From: Richard Patel @ 2026-05-19 13:14 UTC (permalink / raw)
To: David Laight
Cc: x86, Rick Edgecombe, Yu-cheng Yu, Dave Hansen, Thomas Gleixner,
Ingo Molnar, Borislav Petkov, H. Peter Anvin, Andy Lutomirski,
Kees Cook, Peter Zijlstra, Shuah Khan, linux-kselftest,
linux-kernel
On Tue, May 19, 2026 at 10:33:45AM +0100, David Laight wrote:
> Isn't using 'notrack jmp *reg' for jump tables actually more secure?
> If an attacker can write code it doesn't matter.
> The jump table in is RO memory so can't be written.
> But if there are ENDBR on all the jump table targets they become
> possibly useful code addresses to arrange to write into some RW
> function pointer table - which might be useful.
You're right. I was worried about an invalid jump table index at first.
Clang 22 happily optimizes away jump table index bounds checks. GCC 16
seems to be more careful. We should probably patch LLVM to never
optimize it away, e.g.:
// funny.c
// clang -c -fcf-protection=branch -O2 -o funny.o funny.c
// objdump -d funny.o -M intel
int t0(void), t1(void), t2(void), t3(void);
int funny(unsigned long target) {
__builtin_assume(target < 4);
switch (target) {
case 0: return t0();
case 1: return t1();
case 2: return t2();
case 3: return t3();
}
}
// Clang 22
0000000000000000 <funny>:
0: f3 0f 1e fa endbr64
4: 55 push rbp
5: 48 89 e5 mov rbp, rsp
8: 3e ff 24 fd 00 00 00 00 notrack jmp qword ptr [rdi*8+0x0] // vulnerable
10: 5d pop rbp
11: e9 00 00 00 00 jmp 0x16 <funny+0x16>
16: 5d pop rbp
17: e9 00 00 00 00 jmp 0x1c <funny+0x1c>
1c: 5d pop rbp
1d: e9 00 00 00 00 jmp 0x22 <funny+0x22>
22: 5d pop rbp
23: e9 00 00 00 00 jmp 0x28 <funny+0x28>
-Richard
^ permalink raw reply [flat|nested] 20+ messages in thread* Re: [PATCH 0/7] Usermode Indirect Branch Tracking
2026-05-19 13:14 ` Richard Patel
@ 2026-05-19 13:28 ` David Laight
2026-05-19 14:18 ` Richard Patel
0 siblings, 1 reply; 20+ messages in thread
From: David Laight @ 2026-05-19 13:28 UTC (permalink / raw)
To: Richard Patel
Cc: x86, Rick Edgecombe, Yu-cheng Yu, Dave Hansen, Thomas Gleixner,
Ingo Molnar, Borislav Petkov, H. Peter Anvin, Andy Lutomirski,
Kees Cook, Peter Zijlstra, Shuah Khan, linux-kselftest,
linux-kernel
On Tue, 19 May 2026 13:14:33 +0000
Richard Patel <ripatel@wii.dev> wrote:
> On Tue, May 19, 2026 at 10:33:45AM +0100, David Laight wrote:
> > Isn't using 'notrack jmp *reg' for jump tables actually more secure?
> > If an attacker can write code it doesn't matter.
> > The jump table in is RO memory so can't be written.
> > But if there are ENDBR on all the jump table targets they become
> > possibly useful code addresses to arrange to write into some RW
> > function pointer table - which might be useful.
>
> You're right. I was worried about an invalid jump table index at first.
> Clang 22 happily optimizes away jump table index bounds checks. GCC 16
> seems to be more careful. We should probably patch LLVM to never
> optimize it away, e.g.:
>
> // funny.c
> // clang -c -fcf-protection=branch -O2 -o funny.o funny.c
> // objdump -d funny.o -M intel
> int t0(void), t1(void), t2(void), t3(void);
> int funny(unsigned long target) {
> __builtin_assume(target < 4);
If you use __builtin_assume() you get to clear up the mess.
I don't know if userspace ever cares about speculative array access.
If it does you need one of the mitigration - eg using cmp+cmov
to generate a jump table index that references the 'default'.
-- David
> switch (target) {
> case 0: return t0();
> case 1: return t1();
> case 2: return t2();
> case 3: return t3();
> }
> }
>
> // Clang 22
> 0000000000000000 <funny>:
> 0: f3 0f 1e fa endbr64
> 4: 55 push rbp
> 5: 48 89 e5 mov rbp, rsp
> 8: 3e ff 24 fd 00 00 00 00 notrack jmp qword ptr [rdi*8+0x0] // vulnerable
> 10: 5d pop rbp
> 11: e9 00 00 00 00 jmp 0x16 <funny+0x16>
> 16: 5d pop rbp
> 17: e9 00 00 00 00 jmp 0x1c <funny+0x1c>
> 1c: 5d pop rbp
> 1d: e9 00 00 00 00 jmp 0x22 <funny+0x22>
> 22: 5d pop rbp
> 23: e9 00 00 00 00 jmp 0x28 <funny+0x28>
>
> -Richard
^ permalink raw reply [flat|nested] 20+ messages in thread* Re: [PATCH 0/7] Usermode Indirect Branch Tracking
2026-05-19 13:28 ` David Laight
@ 2026-05-19 14:18 ` Richard Patel
2026-05-19 14:42 ` Peter Zijlstra
0 siblings, 1 reply; 20+ messages in thread
From: Richard Patel @ 2026-05-19 14:18 UTC (permalink / raw)
To: David Laight
Cc: x86, Rick Edgecombe, Yu-cheng Yu, Dave Hansen, Thomas Gleixner,
Ingo Molnar, Borislav Petkov, H. Peter Anvin, Andy Lutomirski,
Kees Cook, Peter Zijlstra, Shuah Khan, linux-kselftest,
linux-kernel
On Tue, May 19, 2026 at 02:28:08PM +0100, David Laight wrote:
> On Tue, 19 May 2026 13:14:33 +0000
> Richard Patel <ripatel@wii.dev> wrote:
>
> > On Tue, May 19, 2026 at 10:33:45AM +0100, David Laight wrote:
> > > Isn't using 'notrack jmp *reg' for jump tables actually more secure?
> > > If an attacker can write code it doesn't matter.
> > > The jump table in is RO memory so can't be written.
> > > But if there are ENDBR on all the jump table targets they become
> > > possibly useful code addresses to arrange to write into some RW
> > > function pointer table - which might be useful.
> >
> > You're right. I was worried about an invalid jump table index at first.
> > Clang 22 happily optimizes away jump table index bounds checks. GCC 16
> > seems to be more careful. We should probably patch LLVM to never
> > optimize it away, e.g.:
> >
> > // funny.c
> > // clang -c -fcf-protection=branch -O2 -o funny.o funny.c
> > // objdump -d funny.o -M intel
> > int t0(void), t1(void), t2(void), t3(void);
> > int funny(unsigned long target) {
> > __builtin_assume(target < 4);
>
> If you use __builtin_assume() you get to clear up the mess.
I'm pretty sure you'd get the same result with cross-function
optimization across a bunch of static functions or LTO. Compiler goes
"oh, this internal function is only reachable from these 3 callers in
the same unit, which all already bound their input params. Guess I will
skip the bounds check".
It is a compiler bug that Clang is at all able to generate unbounded
'notrack jmp' with -fcf-protection=branch, it blows a gap in IBT.
Anyways, I don't think we need kernel support for banning notrack in
userland? There is no ABI (GNU note) standard for 'notrack-free'
binaries AFAIK, and as you point out notrack is a secure way to do
jump tables (if done properly).
> I don't know if userspace ever cares about speculative array access.
> If it does you need one of the mitigration - eg using cmp+cmov
> to generate a jump table index that references the 'default'.
Intel docs say that "CET-IBT limits speculative execution at indirect
branch targets that do not start with ENDBRANCH", with heavy emphasis
on "limits" not "prevents" ... Is it too unreliable in practice?
https://www.intel.com/content/www/us/en/developer/articles/technical/software-security-guidance/technical-documentation/branch-history-injection.html#inpage-nav-4-3
-- Richard
^ permalink raw reply [flat|nested] 20+ messages in thread* Re: [PATCH 0/7] Usermode Indirect Branch Tracking
2026-05-19 14:18 ` Richard Patel
@ 2026-05-19 14:42 ` Peter Zijlstra
0 siblings, 0 replies; 20+ messages in thread
From: Peter Zijlstra @ 2026-05-19 14:42 UTC (permalink / raw)
To: Richard Patel
Cc: David Laight, x86, Rick Edgecombe, Yu-cheng Yu, Dave Hansen,
Thomas Gleixner, Ingo Molnar, Borislav Petkov, H. Peter Anvin,
Andy Lutomirski, Kees Cook, Shuah Khan, linux-kselftest,
linux-kernel
On Tue, May 19, 2026 at 02:18:04PM +0000, Richard Patel wrote:
> > I don't know if userspace ever cares about speculative array access.
> > If it does you need one of the mitigration - eg using cmp+cmov
> > to generate a jump table index that references the 'default'.
>
> Intel docs say that "CET-IBT limits speculative execution at indirect
> branch targets that do not start with ENDBRANCH", with heavy emphasis
> on "limits" not "prevents" ... Is it too unreliable in practice?
>
> https://www.intel.com/content/www/us/en/developer/articles/technical/software-security-guidance/technical-documentation/branch-history-injection.html#inpage-nav-4-3
You might find the commit log of: d8122c428076 ("x86/ibt: Implement
FineIBT-BHI mitigation") instructive.
^ permalink raw reply [flat|nested] 20+ messages in thread