xref: /openbmc/linux/arch/s390/kernel/nospec-branch.c (revision 83a530e1)
1 // SPDX-License-Identifier: GPL-2.0
2 #include <linux/module.h>
3 #include <linux/device.h>
4 #include <linux/cpu.h>
5 #include <asm/nospec-branch.h>
6 
7 static int __init nobp_setup_early(char *str)
8 {
9 	bool enabled;
10 	int rc;
11 
12 	rc = kstrtobool(str, &enabled);
13 	if (rc)
14 		return rc;
15 	if (enabled && test_facility(82)) {
16 		/*
17 		 * The user explicitely requested nobp=1, enable it and
18 		 * disable the expoline support.
19 		 */
20 		__set_facility(82, S390_lowcore.alt_stfle_fac_list);
21 		if (IS_ENABLED(CONFIG_EXPOLINE))
22 			nospec_disable = 1;
23 	} else {
24 		__clear_facility(82, S390_lowcore.alt_stfle_fac_list);
25 	}
26 	return 0;
27 }
28 early_param("nobp", nobp_setup_early);
29 
30 static int __init nospec_setup_early(char *str)
31 {
32 	__clear_facility(82, S390_lowcore.alt_stfle_fac_list);
33 	return 0;
34 }
35 early_param("nospec", nospec_setup_early);
36 
37 static int __init nospec_report(void)
38 {
39 	if (IS_ENABLED(CC_USING_EXPOLINE) && !nospec_disable)
40 		pr_info("Spectre V2 mitigation: execute trampolines.\n");
41 	if (__test_facility(82, S390_lowcore.alt_stfle_fac_list))
42 		pr_info("Spectre V2 mitigation: limited branch prediction.\n");
43 	return 0;
44 }
45 arch_initcall(nospec_report);
46 
47 #ifdef CONFIG_SYSFS
48 ssize_t cpu_show_spectre_v1(struct device *dev,
49 			    struct device_attribute *attr, char *buf)
50 {
51 	return sprintf(buf, "Mitigation: __user pointer sanitization\n");
52 }
53 
54 ssize_t cpu_show_spectre_v2(struct device *dev,
55 			    struct device_attribute *attr, char *buf)
56 {
57 	if (IS_ENABLED(CC_USING_EXPOLINE) && !nospec_disable)
58 		return sprintf(buf, "Mitigation: execute trampolines\n");
59 	if (__test_facility(82, S390_lowcore.alt_stfle_fac_list))
60 		return sprintf(buf, "Mitigation: limited branch prediction.\n");
61 	return sprintf(buf, "Vulnerable\n");
62 }
63 #endif
64 
65 #ifdef CONFIG_EXPOLINE
66 
67 int nospec_disable = IS_ENABLED(CONFIG_EXPOLINE_OFF);
68 
69 static int __init nospectre_v2_setup_early(char *str)
70 {
71 	nospec_disable = 1;
72 	return 0;
73 }
74 early_param("nospectre_v2", nospectre_v2_setup_early);
75 
76 void __init nospec_auto_detect(void)
77 {
78 	if (IS_ENABLED(CC_USING_EXPOLINE)) {
79 		/*
80 		 * The kernel has been compiled with expolines.
81 		 * Keep expolines enabled and disable nobp.
82 		 */
83 		nospec_disable = 0;
84 		__clear_facility(82, S390_lowcore.alt_stfle_fac_list);
85 	}
86 	/*
87 	 * If the kernel has not been compiled with expolines the
88 	 * nobp setting decides what is done, this depends on the
89 	 * CONFIG_KERNEL_NP option and the nobp/nospec parameters.
90 	 */
91 }
92 
93 static int __init spectre_v2_setup_early(char *str)
94 {
95 	if (str && !strncmp(str, "on", 2)) {
96 		nospec_disable = 0;
97 		__clear_facility(82, S390_lowcore.alt_stfle_fac_list);
98 	}
99 	if (str && !strncmp(str, "off", 3))
100 		nospec_disable = 1;
101 	if (str && !strncmp(str, "auto", 4))
102 		nospec_auto_detect();
103 	return 0;
104 }
105 early_param("spectre_v2", spectre_v2_setup_early);
106 
107 static void __init_or_module __nospec_revert(s32 *start, s32 *end)
108 {
109 	enum { BRCL_EXPOLINE, BRASL_EXPOLINE } type;
110 	u8 *instr, *thunk, *br;
111 	u8 insnbuf[6];
112 	s32 *epo;
113 
114 	/* Second part of the instruction replace is always a nop */
115 	memcpy(insnbuf + 2, (char[]) { 0x47, 0x00, 0x00, 0x00 }, 4);
116 	for (epo = start; epo < end; epo++) {
117 		instr = (u8 *) epo + *epo;
118 		if (instr[0] == 0xc0 && (instr[1] & 0x0f) == 0x04)
119 			type = BRCL_EXPOLINE;	/* brcl instruction */
120 		else if (instr[0] == 0xc0 && (instr[1] & 0x0f) == 0x05)
121 			type = BRASL_EXPOLINE;	/* brasl instruction */
122 		else
123 			continue;
124 		thunk = instr + (*(int *)(instr + 2)) * 2;
125 		if (thunk[0] == 0xc6 && thunk[1] == 0x00)
126 			/* exrl %r0,<target-br> */
127 			br = thunk + (*(int *)(thunk + 2)) * 2;
128 		else if (thunk[0] == 0xc0 && (thunk[1] & 0x0f) == 0x00 &&
129 			 thunk[6] == 0x44 && thunk[7] == 0x00 &&
130 			 (thunk[8] & 0x0f) == 0x00 && thunk[9] == 0x00 &&
131 			 (thunk[1] & 0xf0) == (thunk[8] & 0xf0))
132 			/* larl %rx,<target br> + ex %r0,0(%rx) */
133 			br = thunk + (*(int *)(thunk + 2)) * 2;
134 		else
135 			continue;
136 		if (br[0] != 0x07 || (br[1] & 0xf0) != 0xf0)
137 			continue;
138 		switch (type) {
139 		case BRCL_EXPOLINE:
140 			/* brcl to thunk, replace with br + nop */
141 			insnbuf[0] = br[0];
142 			insnbuf[1] = (instr[1] & 0xf0) | (br[1] & 0x0f);
143 			break;
144 		case BRASL_EXPOLINE:
145 			/* brasl to thunk, replace with basr + nop */
146 			insnbuf[0] = 0x0d;
147 			insnbuf[1] = (instr[1] & 0xf0) | (br[1] & 0x0f);
148 			break;
149 		}
150 
151 		s390_kernel_write(instr, insnbuf, 6);
152 	}
153 }
154 
155 void __init_or_module nospec_revert(s32 *start, s32 *end)
156 {
157 	if (nospec_disable)
158 		__nospec_revert(start, end);
159 }
160 
161 extern s32 __nospec_call_start[], __nospec_call_end[];
162 extern s32 __nospec_return_start[], __nospec_return_end[];
163 void __init nospec_init_branches(void)
164 {
165 	nospec_revert(__nospec_call_start, __nospec_call_end);
166 	nospec_revert(__nospec_return_start, __nospec_return_end);
167 }
168 
169 #endif /* CONFIG_EXPOLINE */
170