]> git.ipfire.org Git - thirdparty/linux.git/blob - arch/x86/kernel/cet.c
cc10d8be9d74cd91d09dc4a5376edc540c3fb736
[thirdparty/linux.git] / arch / x86 / kernel / cet.c
1 // SPDX-License-Identifier: GPL-2.0
2
3 #include <linux/ptrace.h>
4 #include <asm/bugs.h>
5 #include <asm/traps.h>
6
7 enum cp_error_code {
8 CP_EC = (1 << 15) - 1,
9
10 CP_RET = 1,
11 CP_IRET = 2,
12 CP_ENDBR = 3,
13 CP_RSTRORSSP = 4,
14 CP_SETSSBSY = 5,
15
16 CP_ENCL = 1 << 15,
17 };
18
19 static const char cp_err[][10] = {
20 [0] = "unknown",
21 [1] = "near ret",
22 [2] = "far/iret",
23 [3] = "endbranch",
24 [4] = "rstorssp",
25 [5] = "setssbsy",
26 };
27
28 static const char *cp_err_string(unsigned long error_code)
29 {
30 unsigned int cpec = error_code & CP_EC;
31
32 if (cpec >= ARRAY_SIZE(cp_err))
33 cpec = 0;
34 return cp_err[cpec];
35 }
36
37 static void do_unexpected_cp(struct pt_regs *regs, unsigned long error_code)
38 {
39 WARN_ONCE(1, "Unexpected %s #CP, error_code: %s\n",
40 user_mode(regs) ? "user mode" : "kernel mode",
41 cp_err_string(error_code));
42 }
43
44 static DEFINE_RATELIMIT_STATE(cpf_rate, DEFAULT_RATELIMIT_INTERVAL,
45 DEFAULT_RATELIMIT_BURST);
46
47 static void do_user_cp_fault(struct pt_regs *regs, unsigned long error_code)
48 {
49 struct task_struct *tsk;
50 unsigned long ssp;
51
52 /*
53 * An exception was just taken from userspace. Since interrupts are disabled
54 * here, no scheduling should have messed with the registers yet and they
55 * will be whatever is live in userspace. So read the SSP before enabling
56 * interrupts so locking the fpregs to do it later is not required.
57 */
58 rdmsrl(MSR_IA32_PL3_SSP, ssp);
59
60 cond_local_irq_enable(regs);
61
62 tsk = current;
63 tsk->thread.error_code = error_code;
64 tsk->thread.trap_nr = X86_TRAP_CP;
65
66 /* Ratelimit to prevent log spamming. */
67 if (show_unhandled_signals && unhandled_signal(tsk, SIGSEGV) &&
68 __ratelimit(&cpf_rate)) {
69 pr_emerg("%s[%d] control protection ip:%lx sp:%lx ssp:%lx error:%lx(%s)%s",
70 tsk->comm, task_pid_nr(tsk),
71 regs->ip, regs->sp, ssp, error_code,
72 cp_err_string(error_code),
73 error_code & CP_ENCL ? " in enclave" : "");
74 print_vma_addr(KERN_CONT " in ", regs->ip);
75 pr_cont("\n");
76 }
77
78 force_sig_fault(SIGSEGV, SEGV_CPERR, (void __user *)0);
79 cond_local_irq_disable(regs);
80 }
81
82 static __ro_after_init bool ibt_fatal = true;
83
84 /* code label defined in asm below */
85 extern void ibt_selftest_ip(void);
86
87 static void do_kernel_cp_fault(struct pt_regs *regs, unsigned long error_code)
88 {
89 if ((error_code & CP_EC) != CP_ENDBR) {
90 do_unexpected_cp(regs, error_code);
91 return;
92 }
93
94 if (unlikely(regs->ip == (unsigned long)&ibt_selftest_ip)) {
95 regs->ax = 0;
96 return;
97 }
98
99 pr_err("Missing ENDBR: %pS\n", (void *)instruction_pointer(regs));
100 if (!ibt_fatal) {
101 printk(KERN_DEFAULT CUT_HERE);
102 __warn(__FILE__, __LINE__, (void *)regs->ip, TAINT_WARN, regs, NULL);
103 return;
104 }
105 BUG();
106 }
107
108 /* Must be noinline to ensure uniqueness of ibt_selftest_ip. */
109 noinline bool ibt_selftest(void)
110 {
111 unsigned long ret;
112
113 asm (" lea ibt_selftest_ip(%%rip), %%rax\n\t"
114 ANNOTATE_RETPOLINE_SAFE
115 " jmp *%%rax\n\t"
116 "ibt_selftest_ip:\n\t"
117 UNWIND_HINT_FUNC
118 ANNOTATE_NOENDBR
119 " nop\n\t"
120
121 : "=a" (ret) : : "memory");
122
123 return !ret;
124 }
125
126 static int __init ibt_setup(char *str)
127 {
128 if (!strcmp(str, "off"))
129 setup_clear_cpu_cap(X86_FEATURE_IBT);
130
131 if (!strcmp(str, "warn"))
132 ibt_fatal = false;
133
134 return 1;
135 }
136
137 __setup("ibt=", ibt_setup);
138
139 DEFINE_IDTENTRY_ERRORCODE(exc_control_protection)
140 {
141 if (user_mode(regs)) {
142 if (cpu_feature_enabled(X86_FEATURE_USER_SHSTK))
143 do_user_cp_fault(regs, error_code);
144 else
145 do_unexpected_cp(regs, error_code);
146 } else {
147 if (cpu_feature_enabled(X86_FEATURE_IBT))
148 do_kernel_cp_fault(regs, error_code);
149 else
150 do_unexpected_cp(regs, error_code);
151 }
152 }