* [PATCH v2 0/5] Usermode Indirect Branch Tracking
@ 2026-06-05 18:47 Richard Patel
2026-06-05 18:47 ` [PATCH v2 1/5] x86: add userspace IBT config option Richard Patel
` (6 more replies)
0 siblings, 7 replies; 12+ messages in thread
From: Richard Patel @ 2026-06-05 18:47 UTC (permalink / raw)
To: x86, H. Peter Anvin, Peter Zijlstra
Cc: Rick Edgecombe, Yu-cheng Yu, Dave Hansen, Thomas Gleixner,
Ingo Molnar, Borislav Petkov, David Laight, Andy Lutomirski,
Kees Cook, Shuah Khan, linux-kselftest, linux-kernel,
Florian Weimer, Richard Patel
Adds basic support for x86 userspace IBT.
IBT is part of Intel CET. It requires indirect call and jump targets
to start with an endbr{32,64} instruction, otherwise throwing #CP.
In summary, this patch does 3 things:
- Config wiring ensuring supervisor XSAVE contains IBT state
- Allow userspace to enable IBT via prctl(PR_CFI_*) for an entire thread
- Enable IBT support (ENDBR instructions) in VDSO
Unlike the arm64 BTI API:
- does not support mixed usermode (all or nothing)
- does not touch page table code
- not enabled automatically (no ELF GNU note parsing)
- temporarily disables IBT enforcement when handling signals
These can all be cleanly added later.
The main question is whether glibc is happy with this prctl syscall API.
Changes from v1:
- Removed all signal handler changes
- Removed all uapi changes
(v1 saved state via a redundant uc_flags bit, which was a complete
mess, and not compatible with sigframe_ia32)
- Don't allow unknown PR_CFI_* syscall flags
see RISC-V patch: https://lore.kernel.org/lkml/20260518183918.322545-1-ripatel@wii.dev/
- Added 32-bit support
The original usermode IBT patches were written by Yu Cheng years ago,
but are quite different (no syscall API to enable IBT, no FRED support)
https://lore.kernel.org/all/20210830182221.3535-1-yu-cheng.yu@intel.com/
There is one notable gap in this patch series, to do with signals:
000a: mov rax, 0x100a
000f: jmp rax
*** signal occurs ***
*** signal handler runs, does sigreturn ***
100a: nop
The above sequence does not crash.
With IBT, it should crash at the nop (because an endr64 is expected there).
The IBT state (WAIT_FOR_ENDBR in IA32_U_CET MSR) is not backed up to the
signal frame though. So, when userland does a sigreturn, the CPU has
forgotten that it was doing an indirect branch before the signal.
(This specifically only occurs with signal handlers that sigreturn.)
This is because IA32_U_CET is part of XSAVE 'supervisor' state, so
regular XSAVE/XRSTOR can't access it. Doing a manual backup is tricky.
A related problem is that the signal handler routine is not checked for
endbr preamble.
Basic IBT is better than no IBT, though.
Richard Patel (5):
x86: add userspace IBT config option
x86: shstk: don't clobber IBT bits in U_CET MSR
x86: expose user IBT via PR_CFI_BRANCH_LANDING_PADS
x86/entry/vdso: build with IBT support
selftests/x86: test usermode IBT
arch/x86/Kconfig | 18 ++
arch/x86/entry/vdso/common/Makefile.include | 3 +-
arch/x86/include/asm/cpufeatures.h | 1 +
arch/x86/include/asm/ibt.h | 14 ++
arch/x86/include/asm/processor.h | 5 +
arch/x86/kernel/Makefile | 1 +
arch/x86/kernel/cet.c | 3 +-
arch/x86/kernel/cpu/common.c | 14 +-
arch/x86/kernel/ibt.c | 98 ++++++++
arch/x86/kernel/process_64.c | 2 +
arch/x86/kernel/shstk.c | 12 +-
tools/arch/x86/include/asm/cpufeatures.h | 1 +
tools/testing/selftests/x86/Makefile | 5 +-
tools/testing/selftests/x86/user_ibt.c | 247 ++++++++++++++++++++
14 files changed, 416 insertions(+), 8 deletions(-)
create mode 100644 arch/x86/kernel/ibt.c
create mode 100644 tools/testing/selftests/x86/user_ibt.c
--
2.47.3
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH v2 1/5] x86: add userspace IBT config option
2026-06-05 18:47 [PATCH v2 0/5] Usermode Indirect Branch Tracking Richard Patel
@ 2026-06-05 18:47 ` Richard Patel
2026-06-05 18:47 ` [PATCH v2 2/5] x86: shstk: don't clobber IBT bits in U_CET MSR Richard Patel
` (5 subsequent siblings)
6 siblings, 0 replies; 12+ messages in thread
From: Richard Patel @ 2026-06-05 18:47 UTC (permalink / raw)
To: x86, H. Peter Anvin, Peter Zijlstra
Cc: Rick Edgecombe, Yu-cheng Yu, Dave Hansen, Thomas Gleixner,
Ingo Molnar, Borislav Petkov, David Laight, Andy Lutomirski,
Kees Cook, Shuah Khan, linux-kselftest, linux-kernel,
Florian Weimer, Richard Patel
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.
Depends on usermode shadow stack, which enables XSAVES for the
IA32_U_CET MSR, containing both SHSTK and IBT state.
Signed-off-by: Richard Patel <ripatel@wii.dev>
---
arch/x86/Kconfig | 18 ++++++++++++++++++
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, 34 insertions(+), 3 deletions(-)
diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig
index f3f7cb01d69d..90d5d14fd407 100644
--- a/arch/x86/Kconfig
+++ b/arch/x86/Kconfig
@@ -1901,6 +1901,24 @@ config X86_USER_SHADOW_STACK
If unsure, say N.
+config X86_USER_IBT
+ bool "X86 userspace indirect branch tracking"
+ depends on X86_64
+ depends on X86_USER_SHADOW_STACK
+ 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] 12+ messages in thread
* [PATCH v2 2/5] x86: shstk: don't clobber IBT bits in U_CET MSR
2026-06-05 18:47 [PATCH v2 0/5] Usermode Indirect Branch Tracking Richard Patel
2026-06-05 18:47 ` [PATCH v2 1/5] x86: add userspace IBT config option Richard Patel
@ 2026-06-05 18:47 ` Richard Patel
2026-06-05 18:47 ` [PATCH v2 3/5] x86: expose user IBT via PR_CFI_BRANCH_LANDING_PADS Richard Patel
` (4 subsequent siblings)
6 siblings, 0 replies; 12+ messages in thread
From: Richard Patel @ 2026-06-05 18:47 UTC (permalink / raw)
To: x86, H. Peter Anvin, Peter Zijlstra
Cc: Rick Edgecombe, Yu-cheng Yu, Dave Hansen, Thomas Gleixner,
Ingo Molnar, Borislav Petkov, David Laight, Andy Lutomirski,
Kees Cook, Shuah Khan, linux-kselftest, linux-kernel,
Florian Weimer, Richard Patel
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] 12+ messages in thread
* [PATCH v2 3/5] x86: expose user IBT via PR_CFI_BRANCH_LANDING_PADS
2026-06-05 18:47 [PATCH v2 0/5] Usermode Indirect Branch Tracking Richard Patel
2026-06-05 18:47 ` [PATCH v2 1/5] x86: add userspace IBT config option Richard Patel
2026-06-05 18:47 ` [PATCH v2 2/5] x86: shstk: don't clobber IBT bits in U_CET MSR Richard Patel
@ 2026-06-05 18:47 ` Richard Patel
2026-06-05 18:47 ` [PATCH v2 4/5] x86/entry/vdso: build with IBT support Richard Patel
` (3 subsequent siblings)
6 siblings, 0 replies; 12+ messages in thread
From: Richard Patel @ 2026-06-05 18:47 UTC (permalink / raw)
To: x86, H. Peter Anvin, Peter Zijlstra
Cc: Rick Edgecombe, Yu-cheng Yu, Dave Hansen, Thomas Gleixner,
Ingo Molnar, Borislav Petkov, David Laight, Andy Lutomirski,
Kees Cook, Shuah Khan, linux-kselftest, linux-kernel,
Florian Weimer, Richard Patel
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 | 14 +++++
arch/x86/include/asm/processor.h | 5 ++
arch/x86/kernel/Makefile | 1 +
arch/x86/kernel/ibt.c | 98 ++++++++++++++++++++++++++++++++
arch/x86/kernel/process_64.c | 2 +
5 files changed, 120 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..586e5fadf844 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__
+
+#include <linux/prctl.h>
+
+#define PR_CFI_SUPPORTED_STATUS_MASK (PR_CFI_ENABLE | PR_CFI_DISABLE | PR_CFI_LOCK)
+
+#ifdef CONFIG_X86_USER_IBT
+void reset_thread_ibt(void);
+#else
+static inline void reset_thread_ibt(void) {}
+#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 67dd932305db..7fbf10410973 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/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..682414fde5a4
--- /dev/null
+++ b/arch/x86/kernel/ibt.c
@@ -0,0 +1,98 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/types.h>
+#include <linux/cpu.h>
+#include <linux/prctl.h>
+#include <asm/msr.h>
+
+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))
+ 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))
+ return -EINVAL;
+
+ if (t != current)
+ return -EINVAL;
+
+ if (state & ~PR_CFI_SUPPORTED_STATUS_MASK)
+ 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))
+ return -EINVAL;
+
+ user_ibt_set_lock(task, true);
+
+ return 0;
+}
+
+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] 12+ messages in thread
* [PATCH v2 4/5] x86/entry/vdso: build with IBT support
2026-06-05 18:47 [PATCH v2 0/5] Usermode Indirect Branch Tracking Richard Patel
` (2 preceding siblings ...)
2026-06-05 18:47 ` [PATCH v2 3/5] x86: expose user IBT via PR_CFI_BRANCH_LANDING_PADS Richard Patel
@ 2026-06-05 18:47 ` Richard Patel
2026-06-05 19:14 ` Florian Weimer
2026-06-05 18:47 ` [PATCH v2 5/5] selftests/x86: test usermode IBT Richard Patel
` (2 subsequent siblings)
6 siblings, 1 reply; 12+ messages in thread
From: Richard Patel @ 2026-06-05 18:47 UTC (permalink / raw)
To: x86, H. Peter Anvin, Peter Zijlstra
Cc: Rick Edgecombe, Yu-cheng Yu, Dave Hansen, Thomas Gleixner,
Ingo Molnar, Borislav Petkov, David Laight, Andy Lutomirski,
Kees Cook, Shuah Khan, linux-kselftest, linux-kernel,
Florian Weimer, Richard Patel
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] 12+ messages in thread
* [PATCH v2 5/5] selftests/x86: test usermode IBT
2026-06-05 18:47 [PATCH v2 0/5] Usermode Indirect Branch Tracking Richard Patel
` (3 preceding siblings ...)
2026-06-05 18:47 ` [PATCH v2 4/5] x86/entry/vdso: build with IBT support Richard Patel
@ 2026-06-05 18:47 ` Richard Patel
2026-06-05 19:04 ` [PATCH v2 0/5] Usermode Indirect Branch Tracking Peter Zijlstra
2026-06-05 19:34 ` Florian Weimer
6 siblings, 0 replies; 12+ messages in thread
From: Richard Patel @ 2026-06-05 18:47 UTC (permalink / raw)
To: x86, H. Peter Anvin, Peter Zijlstra
Cc: Rick Edgecombe, Yu-cheng Yu, Dave Hansen, Thomas Gleixner,
Ingo Molnar, Borislav Petkov, David Laight, Andy Lutomirski,
Kees Cook, Shuah Khan, linux-kselftest, linux-kernel,
Florian Weimer, Richard Patel
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 | 247 +++++++++++++++++++++++++
2 files changed, 251 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..59f2ba3ec4ea 100644
--- a/tools/testing/selftests/x86/Makefile
+++ b/tools/testing/selftests/x86/Makefile
@@ -13,7 +13,7 @@ CAN_BUILD_WITH_NOPIE := $(shell ./check_cc.sh "$(CC)" trivial_program.c -no-pie)
TARGETS_C_BOTHBITS := single_step_syscall sysret_ss_attrs syscall_nt test_mremap_vdso \
check_initial_reg_state sigreturn iopl ioperm \
test_vsyscall mov_ss_trap sigtrap_loop \
- syscall_arg_fault fsgsbase_restore sigaltstack
+ syscall_arg_fault fsgsbase_restore sigaltstack user_ibt
TARGETS_C_BOTHBITS += nx_stack
TARGETS_C_32BIT_ONLY := entry_from_vm86 test_syscall_vdso unwind_vdso \
test_FCMOV test_FCOMI test_FISTTP \
@@ -138,3 +138,6 @@ $(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_32: CFLAGS += -fcf-protection=branch
+$(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..b1038e5e5e64
--- /dev/null
+++ b/tools/testing/selftests/x86/user_ibt.c
@@ -0,0 +1,247 @@
+// 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 <stdint.h>
+#include <sys/signal.h>
+#include <sys/syscall.h>
+#include <sys/mman.h>
+#include <time.h>
+#include <linux/const.h>
+#include <linux/prctl.h>
+#include <ucontext.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, aligned(4096))) valid_target(void)
+{
+#ifdef __x86_64__
+ asm volatile("endbr64\n");
+#else
+ asm volatile("endbr32\n");
+#endif
+ asm volatile(
+ "ret\n"
+ ".p2align 12\n"
+ );
+}
+
+void __attribute__((nocf_check, naked, aligned(4096))) invalid_target(void)
+{
+ asm volatile(
+ "ret\n"
+ ".p2align 12\n"
+ );
+}
+
+void __attribute__((naked)) user_ibt_basic_test(void)
+{
+#ifdef __x86_64__
+ asm volatile(
+ "leaq valid_target(%rip), %rax\n"
+ "call *%rax\n"
+ "ret\n"
+ );
+#else
+ asm volatile(
+ "movl $valid_target, %eax\n"
+ "call *%eax\n"
+ "ret\n"
+ );
+#endif
+}
+
+void __attribute__((naked)) user_ibt_notrack_test(void)
+{
+#ifdef __x86_64__
+ asm volatile (
+ "leaq invalid_target(%rip), %rax\n"
+ "notrack call *%rax\n"
+ "ret\n"
+ );
+#else
+ asm volatile (
+ "movl $invalid_target, %eax\n"
+ "notrack call *%eax\n"
+ "ret\n"
+ );
+#endif
+}
+
+static sigjmp_buf jmpbuf;
+static volatile sig_atomic_t num_segv;
+static volatile sig_atomic_t got_cperr;
+
+static void segv_handler(int signum, siginfo_t *si, void *uc)
+{
+ num_segv++;
+ got_cperr = si->si_code == SEGV_CPERR;
+ siglongjmp(jmpbuf, 1);
+}
+
+int user_ibt_violation_test(void)
+{
+ struct sigaction sa = {};
+ size_t volatile ptr;
+
+ num_segv = 0;
+ got_cperr = false;
+
+ sa.sa_sigaction = segv_handler;
+ sa.sa_flags = SA_SIGINFO;
+ if (sigaction(SIGSEGV, &sa, NULL))
+ return 0;
+
+ 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 */
+ }
+
+ signal(SIGSEGV, SIG_DFL);
+
+ return num_segv == 1 && got_cperr;
+}
+
+static int user_ibt_signal_handler(void (*handler)(int, siginfo_t *, void *), bool expect_segv)
+{
+ struct sigaction sa = {};
+ pid_t pid = getpid();
+
+ if (pid < 0)
+ return 0;
+ num_segv = 0;
+ got_cperr = false;
+
+ sa.sa_sigaction = handler;
+ sa.sa_flags = SA_SIGINFO;
+ if (sigaction(SIGPWR, &sa, NULL))
+ return 0;
+
+ sa.sa_sigaction = segv_handler;
+ sa.sa_flags = SA_SIGINFO;
+ if (sigaction(SIGSEGV, &sa, NULL))
+ return 0;
+
+ if (kill(getpid(), SIGPWR))
+ return 0;
+
+ signal(SIGSEGV, SIG_DFL);
+ signal(SIGPWR, SIG_DFL);
+
+ return 1;
+}
+
+int user_ibt_signal_handler_valid(void)
+{
+ user_ibt_signal_handler((void *)valid_target, false);
+ return num_segv == 0;
+}
+
+bool has_endbr_preamble(void *ptr)
+{
+ const unsigned char *p = ptr;
+
+ if (!(p[0] == 0xf3 && p[1] == 0x0f && p[2] == 0x1e))
+ return false;
+#ifdef __x86_64__
+ return p[3] == 0xfa;
+#else
+ return p[3] == 0xfb;
+#endif
+}
+
+int user_ibt_vdso(void)
+{
+ struct sigaction sa = {};
+ struct timespec ts;
+ int ret = 1;
+
+ sa.sa_sigaction = segv_handler;
+ sa.sa_flags = SA_SIGINFO;
+ if (sigaction(SIGSEGV, &sa, NULL))
+ return 0;
+
+ if (!sigsetjmp(jmpbuf, 1))
+ (void)clock_gettime(CLOCK_REALTIME, &ts);
+ else
+ ret = 0;
+
+ signal(SIGSEGV, SIG_DFL);
+ return ret;
+}
+
+int main(int argc, char *argv[])
+{
+ unsigned long lpad_status = PR_CFI_ENABLE;
+
+ ksft_print_header();
+
+ if (!has_endbr_preamble((void *)printf))
+ ksft_exit_skip("libc does not support IBT (needs -fcf-protection=branch)\n");
+
+ 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");
+ }
+
+ ksft_set_plan(5);
+
+ 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_test_result(user_ibt_signal_handler_valid(),
+ "Signal handler sanity check\n");
+
+ ksft_test_result(user_ibt_vdso(), "vDSO supports IBT\n");
+
+ ksft_finished();
+}
+#endif
\ No newline at end of file
--
2.47.3
^ permalink raw reply related [flat|nested] 12+ messages in thread
* Re: [PATCH v2 0/5] Usermode Indirect Branch Tracking
2026-06-05 18:47 [PATCH v2 0/5] Usermode Indirect Branch Tracking Richard Patel
` (4 preceding siblings ...)
2026-06-05 18:47 ` [PATCH v2 5/5] selftests/x86: test usermode IBT Richard Patel
@ 2026-06-05 19:04 ` Peter Zijlstra
2026-06-05 19:34 ` Florian Weimer
6 siblings, 0 replies; 12+ messages in thread
From: Peter Zijlstra @ 2026-06-05 19:04 UTC (permalink / raw)
To: Richard Patel
Cc: x86, H. Peter Anvin, Rick Edgecombe, Yu-cheng Yu, Dave Hansen,
Thomas Gleixner, Ingo Molnar, Borislav Petkov, David Laight,
Andy Lutomirski, Kees Cook, Shuah Khan, linux-kselftest,
linux-kernel, Florian Weimer
On Fri, Jun 05, 2026 at 06:47:11PM +0000, Richard Patel wrote:
> The above sequence does not crash.
>
> With IBT, it should crash at the nop (because an endr64 is expected there).
> The IBT state (WAIT_FOR_ENDBR in IA32_U_CET MSR) is not backed up to the
> signal frame though. So, when userland does a sigreturn, the CPU has
> forgotten that it was doing an indirect branch before the signal.
> (This specifically only occurs with signal handlers that sigreturn.)
>
> This is because IA32_U_CET is part of XSAVE 'supervisor' state, so
> regular XSAVE/XRSTOR can't access it. Doing a manual backup is tricky.
WAIT_FOR_ENDBR should be part of the exception frame with FRED, so if
you're on FRED hardware, this should be fixed IIRC.
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH v2 4/5] x86/entry/vdso: build with IBT support
2026-06-05 18:47 ` [PATCH v2 4/5] x86/entry/vdso: build with IBT support Richard Patel
@ 2026-06-05 19:14 ` Florian Weimer
0 siblings, 0 replies; 12+ messages in thread
From: Florian Weimer @ 2026-06-05 19:14 UTC (permalink / raw)
To: Richard Patel
Cc: x86, H. Peter Anvin, Peter Zijlstra, Rick Edgecombe, Yu-cheng Yu,
Dave Hansen, Thomas Gleixner, Ingo Molnar, Borislav Petkov,
David Laight, Andy Lutomirski, Kees Cook, Shuah Khan,
linux-kselftest, linux-kernel
* Richard Patel:
> 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)
Surprisingly, the option is cumulative in GCC. I didn't expect that.
So this in fact works with GCC.
However, it's not cumulative in Clang, and -fcf-protection=branch
overrides the earlier -fcf-protection=return.
Thanks,
Florian
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH v2 0/5] Usermode Indirect Branch Tracking
2026-06-05 18:47 [PATCH v2 0/5] Usermode Indirect Branch Tracking Richard Patel
` (5 preceding siblings ...)
2026-06-05 19:04 ` [PATCH v2 0/5] Usermode Indirect Branch Tracking Peter Zijlstra
@ 2026-06-05 19:34 ` Florian Weimer
2026-06-05 20:32 ` Richard Patel
6 siblings, 1 reply; 12+ messages in thread
From: Florian Weimer @ 2026-06-05 19:34 UTC (permalink / raw)
To: Richard Patel
Cc: x86, H. Peter Anvin, Peter Zijlstra, Rick Edgecombe, Yu-cheng Yu,
Dave Hansen, Thomas Gleixner, Ingo Molnar, Borislav Petkov,
David Laight, Andy Lutomirski, Kees Cook, Shuah Khan,
linux-kselftest, linux-kernel, libc-alpha, linux-api,
Arjun Shankar
* Richard Patel:
> Adds basic support for x86 userspace IBT.
>
> IBT is part of Intel CET. It requires indirect call and jump targets
> to start with an endbr{32,64} instruction, otherwise throwing #CP.
>
> In summary, this patch does 3 things:
> - Config wiring ensuring supervisor XSAVE contains IBT state
> - Allow userspace to enable IBT via prctl(PR_CFI_*) for an entire thread
> - Enable IBT support (ENDBR instructions) in VDSO
>
> Unlike the arm64 BTI API:
> - does not support mixed usermode (all or nothing)
> - does not touch page table code
> - not enabled automatically (no ELF GNU note parsing)
> - temporarily disables IBT enforcement when handling signals
> These can all be cleanly added later.
Adding the ELF GNU note parsing can be added later, but perhaps not
cleanly. I'm still a bit worried we might have to rev the markup
because too many binaries are in circulation that claim compatibility,
have never been tested, and are actually broken. If the kernel does not
look at the ELF bits, things a slightly simpler.
How do you detect that handling a signal is complete and IBT can be
re-enabled? Or is it re-enabled before entering the userspace signal
handler?
> The main question is whether glibc is happy with this prctl syscall API.
As far as I can tell, the prctl works for glibc. Re-use of an
arch_prctl constant might have been problematic, but the series is not
doing that.
> There is one notable gap in this patch series, to do with signals:
>
> 000a: mov rax, 0x100a
> 000f: jmp rax
> *** signal occurs ***
> *** signal handler runs, does sigreturn ***
> 100a: nop
>
> The above sequence does not crash.
>
> With IBT, it should crash at the nop (because an endr64 is expected there).
> The IBT state (WAIT_FOR_ENDBR in IA32_U_CET MSR) is not backed up to the
> signal frame though. So, when userland does a sigreturn, the CPU has
> forgotten that it was doing an indirect branch before the signal.
> (This specifically only occurs with signal handlers that sigreturn.)
>
> This is because IA32_U_CET is part of XSAVE 'supervisor' state, so
> regular XSAVE/XRSTOR can't access it. Doing a manual backup is tricky.
That's a bit annoying. Is this restricted to signal handlers, or does
it apply to page faults, too?
> A related problem is that the signal handler routine is not checked for
> endbr preamble.
That's not necessarily a problem because its address cannot be directly
overwritten in userspace. Not all indirect branches need to be checked,
only those that have tweakable targets. In fact, fewer ENDBR64 markers
are better (although we wouldn't drop the marker from a signal handler
specifically, of course).
Thanks,
Florian
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH v2 0/5] Usermode Indirect Branch Tracking
2026-06-05 19:34 ` Florian Weimer
@ 2026-06-05 20:32 ` Richard Patel
2026-06-06 13:40 ` Florian Weimer
0 siblings, 1 reply; 12+ messages in thread
From: Richard Patel @ 2026-06-05 20:32 UTC (permalink / raw)
To: Florian Weimer
Cc: x86, H. Peter Anvin, Peter Zijlstra, Rick Edgecombe, Yu-cheng Yu,
Dave Hansen, Thomas Gleixner, Ingo Molnar, Borislav Petkov,
David Laight, Andy Lutomirski, Kees Cook, Shuah Khan,
linux-kselftest, linux-kernel, libc-alpha, linux-api,
Arjun Shankar
On Fri, Jun 05, 2026 at 09:34:46PM +0200, Florian Weimer wrote:
> How do you detect that handling a signal is complete and IBT can be
> re-enabled? Or is it re-enabled before entering the userspace signal
> handler?
Hi Florian,
In v1, we backed up the IBT CPU state into the (user-accessible) signal
frame from FRED/XSAVE, then restored it:
https://lore.kernel.org/lkml/20260517183024.16292-4-ripatel@wii.dev/
In v2, when entering the signal handler, the kernel just context switches
to the new user rip, bypassing IBT checks (continues executing if the
signal handler does not begin with endbr).
IBT stays enabled in both designs, just the IBT state is preserved in v1,
and lost in v2.
The same thing happens when doing a sigreturn in v2 (e.g. via trampoline),
again IBT is not enforced. IBT stays enabled when doing a siglongjmp,
though.
Some time in the future, ideally:
- signal handler is *required* to start with endbr (this is easy)
- sigreturn as in my asm example enforces endbr after returning from a
signal handler to a in-progres indirect branc
- libc (sig)longjmp is made IBT-compatible
Btw, I had self-tests for the v1 design, and {signal handle,rt_sigreturn,
siglongjmp} with {success case,violation} works flawlessly with Fedora 44
glibc amd64. With glibc i686 I ran into PLT issues, probably my fault.
It is quite surprised that siglongjmp was working, btw, since the glibc
longjmp code uses 'jmp *reg' (without notrack prefix). I guess you do an
endbr64 at the setjmp side?
> > The main question is whether glibc is happy with this prctl syscall API.
>
> As far as I can tell, the prctl works for glibc. Re-use of an
> arch_prctl constant might have been problematic, but the series is not
> doing that.
Nice :-)
The alternative would have been to bolt on stuff to ARCH_SHSTK, or create
an entirely new arch_prctl. Open to any API.
> Adding the ELF GNU note parsing can be added later, but perhaps not
> cleanly. I'm still a bit worried we might have to rev the markup
> because too many binaries are in circulation that claim compatibility,
> have never been tested, and are actually broken. If the kernel does not
> look at the ELF bits, things a slightly simpler.
Phew, I was hoping you'd say that.
If you want, I can sketch out glibc IBT enabling and test it on Debian
and Fedora, which IIRC already emit compile with -fcf-protection=branch
for all OS packages.
> > There is one notable gap in this patch series, to do with signals:
> >
> > 000a: mov rax, 0x100a
> > 000f: jmp rax
> > *** signal occurs ***
> > *** signal handler runs, does sigreturn ***
> > 100a: nop
> >
> > The above sequence does not crash.
> >
> > With IBT, it should crash at the nop (because an endr64 is expected there).
> > The IBT state (WAIT_FOR_ENDBR in IA32_U_CET MSR) is not backed up to the
> > signal frame though. So, when userland does a sigreturn, the CPU has
> > forgotten that it was doing an indirect branch before the signal.
> > (This specifically only occurs with signal handlers that sigreturn.)
> >
> > This is because IA32_U_CET is part of XSAVE 'supervisor' state, so
> > regular XSAVE/XRSTOR can't access it. Doing a manual backup is tricky.
>
> That's a bit annoying. Is this restricted to signal handlers, or does
> it apply to page faults, too?
Only signal handlers, page faults don't reset IBT.
> > A related problem is that the signal handler routine is not checked for
> > endbr preamble.
>
> That's not necessarily a problem because its address cannot be directly
> overwritten in userspace. Not all indirect branches need to be checked,
> only those that have tweakable targets. In fact, fewer ENDBR64 markers
> are better (although we wouldn't drop the marker from a signal handler
> specifically, of course).
Just one concern I have is that people start relying on signal handlers
not requiring endbr64, and then a future kernel version breaking them once
we enforce it.
Really appreciate your review,
-Richard
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH v2 0/5] Usermode Indirect Branch Tracking
2026-06-05 20:32 ` Richard Patel
@ 2026-06-06 13:40 ` Florian Weimer
2026-06-06 23:05 ` Richard Patel
0 siblings, 1 reply; 12+ messages in thread
From: Florian Weimer @ 2026-06-06 13:40 UTC (permalink / raw)
To: Richard Patel
Cc: x86, H. Peter Anvin, Peter Zijlstra, Rick Edgecombe, Yu-cheng Yu,
Dave Hansen, Thomas Gleixner, Ingo Molnar, Borislav Petkov,
David Laight, Andy Lutomirski, Kees Cook, Shuah Khan,
linux-kselftest, linux-kernel, libc-alpha, linux-api,
Arjun Shankar
* Richard Patel:
> On Fri, Jun 05, 2026 at 09:34:46PM +0200, Florian Weimer wrote:
>
>> How do you detect that handling a signal is complete and IBT can be
>> re-enabled? Or is it re-enabled before entering the userspace signal
>> handler?
>
> Hi Florian,
>
> In v1, we backed up the IBT CPU state into the (user-accessible) signal
> frame from FRED/XSAVE, then restored it:
> https://lore.kernel.org/lkml/20260517183024.16292-4-ripatel@wii.dev/
>
> In v2, when entering the signal handler, the kernel just context switches
> to the new user rip, bypassing IBT checks (continues executing if the
> signal handler does not begin with endbr).
What's the reason for this?
> Some time in the future, ideally:
> - signal handler is *required* to start with endbr (this is easy)
> - sigreturn as in my asm example enforces endbr after returning from a
> signal handler to a in-progres indirect branc
> - libc (sig)longjmp is made IBT-compatible
I think the compiler already emits ENDBR markers for returns-twice
functions, which is why longjmp does not use a no-track jump. Other
architectures require such a proliferation of markers because they do
not support no-track jumps at all. However, longjmp is arguable a
corner case. It's not completely safe, like loading a function address
from a RELRO GOT and jumping to it.
> Btw, I had self-tests for the v1 design, and {signal handle,rt_sigreturn,
> siglongjmp} with {success case,violation} works flawlessly with Fedora 44
> glibc amd64. With glibc i686 I ran into PLT issues, probably my fault.
There's no IBT support planned for i686, that's why we dropped all
marker instructions in Fedora.
> It is quite surprised that siglongjmp was working, btw, since the glibc
> longjmp code uses 'jmp *reg' (without notrack prefix). I guess you do an
> endbr64 at the setjmp side?
Yes, compilers generate landing pads for returns-twice functions. Not
ideal, but it's the only way to get setjmp working on targets without
NOTRACK.
>> Adding the ELF GNU note parsing can be added later, but perhaps not
>> cleanly. I'm still a bit worried we might have to rev the markup
>> because too many binaries are in circulation that claim compatibility,
>> have never been tested, and are actually broken. If the kernel does not
>> look at the ELF bits, things a slightly simpler.
>
> Phew, I was hoping you'd say that.
>
> If you want, I can sketch out glibc IBT enabling and test it on Debian
> and Fedora, which IIRC already emit compile with -fcf-protection=branch
> for all OS packages.
For Fedora, please coordinate with Arjun (Cc:ed), who is going through
the motions of enabling SHSTK for real.
>> That's not necessarily a problem because its address cannot be directly
>> overwritten in userspace. Not all indirect branches need to be checked,
>> only those that have tweakable targets. In fact, fewer ENDBR64 markers
>> are better (although we wouldn't drop the marker from a signal handler
>> specifically, of course).
>
> Just one concern I have is that people start relying on signal handlers
> not requiring endbr64, and then a future kernel version breaking them once
> we enforce it.
Would software enforcement be a possibility? The kernel could check if
the landing pad is there.
Thanks,
Florian
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH v2 0/5] Usermode Indirect Branch Tracking
2026-06-06 13:40 ` Florian Weimer
@ 2026-06-06 23:05 ` Richard Patel
0 siblings, 0 replies; 12+ messages in thread
From: Richard Patel @ 2026-06-06 23:05 UTC (permalink / raw)
To: Florian Weimer
Cc: x86, H. Peter Anvin, Peter Zijlstra, Rick Edgecombe, Yu-cheng Yu,
Dave Hansen, Thomas Gleixner, Ingo Molnar, Borislav Petkov,
David Laight, Andy Lutomirski, Kees Cook, Shuah Khan,
linux-kselftest, linux-kernel, libc-alpha, linux-api,
Arjun Shankar
On Sat, Jun 06, 2026 at 03:40:10PM +0200, Florian Weimer wrote:
> * Richard Patel:
>
> > On Fri, Jun 05, 2026 at 09:34:46PM +0200, Florian Weimer wrote:
> >
> >> How do you detect that handling a signal is complete and IBT can be
> >> re-enabled? Or is it re-enabled before entering the userspace signal
> >> handler?
> >
> > Hi Florian,
> >
> > In v1, we backed up the IBT CPU state into the (user-accessible) signal
> > frame from FRED/XSAVE, then restored it:
> > https://lore.kernel.org/lkml/20260517183024.16292-4-ripatel@wii.dev/
> >
> > In v2, when entering the signal handler, the kernel just context switches
> > to the new user rip, bypassing IBT checks (continues executing if the
> > signal handler does not begin with endbr).
>
> What's the reason for this?
Hi Florian,
We just don't have a nice way to include IBT state in the signal frame
right now. v1 had an uabi change (adding a new bit in ucontext_t uc_flags),
which was originally proposed by Intel years ago. My preferred way to add
IBT state is to carve out an XSAVE area in fpstate, which works well with all
the existing signal frame code.
But I figured it's better to just keep the first pass at user IBT super
simple, in the hopes upstream is more inclined to accept that.
BTW, OpenBSD uses the v2 approach (don't preserve IBT state across signal
handlers), presumably because it's also hard for them to restore IBT state
on sigreturn.
> >> That's not necessarily a problem because its address cannot be directly
> >> overwritten in userspace. Not all indirect branches need to be checked,
> >> only those that have tweakable targets. In fact, fewer ENDBR64 markers
> >> are better (although we wouldn't drop the marker from a signal handler
> >> specifically, of course).
> >
> > Just one concern I have is that people start relying on signal handlers
> > not requiring endbr64, and then a future kernel version breaking them once
> > we enforce it.
>
> Would software enforcement be a possibility? The kernel could check if
> the landing pad is there.
Enforcement is the easy part. I can trivially add back 'check if signal
handler starts with endbr64'. Just the backup/restore of the pre-signal
handler state ('do I expect an endbr64 after returning') is the tricky part.
Thank you,
-Richard
^ permalink raw reply [flat|nested] 12+ messages in thread
end of thread, other threads:[~2026-06-06 23:11 UTC | newest]
Thread overview: 12+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-06-05 18:47 [PATCH v2 0/5] Usermode Indirect Branch Tracking Richard Patel
2026-06-05 18:47 ` [PATCH v2 1/5] x86: add userspace IBT config option Richard Patel
2026-06-05 18:47 ` [PATCH v2 2/5] x86: shstk: don't clobber IBT bits in U_CET MSR Richard Patel
2026-06-05 18:47 ` [PATCH v2 3/5] x86: expose user IBT via PR_CFI_BRANCH_LANDING_PADS Richard Patel
2026-06-05 18:47 ` [PATCH v2 4/5] x86/entry/vdso: build with IBT support Richard Patel
2026-06-05 19:14 ` Florian Weimer
2026-06-05 18:47 ` [PATCH v2 5/5] selftests/x86: test usermode IBT Richard Patel
2026-06-05 19:04 ` [PATCH v2 0/5] Usermode Indirect Branch Tracking Peter Zijlstra
2026-06-05 19:34 ` Florian Weimer
2026-06-05 20:32 ` Richard Patel
2026-06-06 13:40 ` Florian Weimer
2026-06-06 23:05 ` Richard Patel
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.