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 <[email protected]>
---
 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


Reply via email to