1 /* * Copyright (c) 2021 Apple Inc. All rights reserved.
2 *
3 * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
4 *
5 * This file contains Original Code and/or Modifications of Original Code
6 * as defined in and that are subject to the Apple Public Source License
7 * Version 2.0 (the 'License'). You may not use this file except in
8 * compliance with the License. The rights granted to you under the License
9 * may not be used to create, or enable the creation or redistribution of,
10 * unlawful or unlicensed copies of an Apple operating system, or to
11 * circumvent, violate, or enable the circumvention or violation of, any
12 * terms of an Apple operating system software license agreement.
13 *
14 * Please obtain a copy of the License at
15 * http://www.opensource.apple.com/apsl/ and read it before using this file.
16 *
17 * The Original Code and all software distributed under the License are
18 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
19 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
20 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
21 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
22 * Please see the License for the specific language governing rights and
23 * limitations under the License.
24 *
25 * @APPLE_OSREFERENCE_LICENSE_HEADER_END@
26 */
27
28 #ifndef STATIC_IF_TEST
29 #include <kern/assert.h>
30 #include <kern/locks.h>
31 #include <kern/startup.h>
32 #include <machine/static_if.h>
33 #include <machine/machine_routines.h>
34
35
36 extern struct static_if_entry __static_if_entries[]
37 __SECTION_START_SYM(STATIC_IF_SEGMENT, STATIC_IF_SECTION);
38
39 extern struct static_if_entry __static_if_entries_end[]
40 __SECTION_END_SYM(STATIC_IF_SEGMENT, STATIC_IF_SECTION);
41
42 extern static_if_initializer __static_if_initializer_entries[]
43 __SECTION_START_SYM(STATIC_IF_SEGMENT, STATIC_IFINIT_SECTION);
44
45 extern static_if_initializer __static_if_initializer_entries_end[]
46 __SECTION_END_SYM(STATIC_IF_SEGMENT, STATIC_IFINIT_SECTION);
47
48 /* libhwtrace knows about this contract */
49 SECURITY_READ_ONLY_LATE(static_if_key_t) static_if_modified_keys;
50 SECURITY_READ_ONLY_LATE(uint32_t) static_if_abi = STATIC_IF_ABI_CURRENT;
51
52 #endif /* STATIC_IF_TEST */
53 #pragma mark boot-arg parsing
54
55 /*
56 * On SPTM targets we can't use PE_parse_boot_argn() because it isn't part
57 * of __BOOT_TEXT, so we need to roll our own.
58 *
59 * We can't use TUNABLES() yet either because they won't have been initialized.
60 */
61
62 __attribute__((always_inline))
63 static inline bool
isargsep(char c)64 isargsep(char c)
65 {
66 return c == ' ' || c == '\0' || c == '\t';
67 }
68
69 __attribute__((always_inline))
70 static const char *
skip_seps(const char * s)71 skip_seps(const char *s)
72 {
73 while (*s && isargsep(*s)) {
74 s++;
75 }
76 return s;
77 }
78
79 __attribute__((always_inline))
80 static const char *
skip_to_sep(const char * s)81 skip_to_sep(const char *s)
82 {
83 while (!isargsep(*s)) {
84 s++;
85 }
86 return s;
87 }
88
89 __attribute__((always_inline))
90 static inline bool
skip_prefix(const char * args,const char * key,const char ** argsout)91 skip_prefix(const char *args, const char *key, const char **argsout)
92 {
93 while (*key) {
94 if (*args != *key) {
95 return false;
96 }
97 args++;
98 key++;
99 }
100
101 *argsout = args;
102 return true;
103 }
104
105 __attribute__((always_inline))
106 static inline const char *
get_val(const char * s,uint64_t * val)107 get_val(const char *s, uint64_t *val)
108 {
109 uint64_t radix = 10;
110 uint64_t v;
111 int sign = 1;
112
113 if (isargsep(*s)) {
114 /* "... key ..." is the same as "... key=1 ..." */
115 *val = 1;
116 return s;
117 }
118
119 if (*s != '=') {
120 /* if not followed by a = then this is garbage */
121 return s;
122 }
123 s++;
124
125 if (*s == '-') {
126 sign = -1;
127 s++;
128 }
129
130 if (isargsep(*s)) {
131 /* "... key=- ..." is malfomed */
132 return s;
133 }
134
135 v = (*s++ - '0');
136 if (v == 0) {
137 switch (*s) {
138 case 'x':
139 radix = 16;
140 s++;
141 break;
142
143 case 'b':
144 radix = 2;
145 s++;
146 break;
147
148 case '0' ... '7':
149 radix = 8;
150 break;
151
152 default:
153 if (!isargsep(*s)) {
154 return s;
155 }
156 break;
157 }
158 } else if (v > radix) {
159 return s;
160 }
161
162 for (;;) {
163 if (*s >= '0' && *s <= '9' - (10 - radix)) {
164 v = v * radix + *s - '0';
165 } else if (radix == 16 && *s >= 'a' && *s <= 'f') {
166 v = v * radix + 10 + *s - 'a';
167 } else if (radix == 16 && *s >= 'A' && *s <= 'F') {
168 v = v * radix + 10 + *s - 'A';
169 } else {
170 if (isargsep(*s)) {
171 *val = v * sign;
172 }
173 return s;
174 }
175
176 s++;
177 }
178 }
179
180 MARK_AS_FIXUP_TEXT uint64_t
static_if_boot_arg_uint64(const char * args,const char * key,uint64_t defval)181 static_if_boot_arg_uint64(const char *args, const char *key, uint64_t defval)
182 {
183 uint64_t ret = defval;
184
185 args = skip_seps(args);
186
187 while (*args) {
188 if (*args == '-' && skip_prefix(args + 1, key, &args)) {
189 if (isargsep(*args)) {
190 ret = TRUE;
191 }
192 } else if (skip_prefix(args, key, &args)) {
193 args = get_val(args, &ret);
194 }
195
196 args = skip_to_sep(args);
197 args = skip_seps(args);
198 }
199
200 return ret;
201 }
202
203
204 #pragma mark patching
205 #ifndef STATIC_IF_TEST
206
207 /*
208 * static_if() is implemented using keys, which are data structures
209 * of type `struct static_if_key`.
210 *
211 * These come in two concrete variants:
212 * - struct static_if_key_true, for which the key starts enabled/true,
213 * - struct static_if_key_false, for which the key starts disabled/false.
214 *
215 * Usage of static_if() and its variants use the following pattern:
216 * (a) a result variable is initialized to 0 (resp 1),
217 * (b) an asm goto() statement might jump to a label or fall through depending on
218 * the state of the key (implemented with STATIC_IF_{NOP,JUMP}),
219 * (c) in the fall through code, the variable is set to 1 (resp 0).
220 *
221 * As a result these macros implement a boolean return that depend on whether
222 * their assembly is currently a nop (in which case it will return the value
223 * from (c)) or a branch (in which case it will return the value from (a)).
224 *
225 * STATIC_IF_NOP() and STATIC_IF_ENTRY() are machine dependent macros that emit
226 * either a nop or a branch instruction, and generate a `struct static_if_entry`
227 * which denote where this patchable instruction lives, and for which key.
228 *
229 * static_if_init() will run early and chain all these entries onto their key,
230 * in order to enable static_if_key_{enable,disable} to be able to quickly patch
231 * these instructions between nops and jumps.
232 */
233
234 __attribute__((always_inline))
235 static inline void *
__static_if_entry_next(static_if_entry_t sie)236 __static_if_entry_next(static_if_entry_t sie)
237 {
238 return (void *)(sie->sie_link & ~3ul);
239 }
240
241 __attribute__((always_inline))
242 static inline bool
__static_if_is_jump(static_if_entry_t sie)243 __static_if_is_jump(static_if_entry_t sie)
244 {
245 return sie->sie_link & 1;
246 }
247
248
249 MARK_AS_FIXUP_TEXT void
static_if_init(const char * args)250 static_if_init(const char *args)
251 {
252 struct static_if_entry *sie = __static_if_entries_end;
253 unsigned long sie_flags;
254 static_if_key_t sie_key;
255
256 while (--sie >= __static_if_entries) {
257 sie_flags = sie->sie_link & 3ul;
258 sie_key = __static_if_entry_next(sie);
259 sie->sie_link = (vm_offset_t)sie_key->sik_entries_head | sie_flags;
260
261 sie_key->sik_entries_head = sie;
262 sie_key->sik_entries_count++;
263 }
264
265 for (static_if_initializer *f = __static_if_initializer_entries;
266 f < __static_if_initializer_entries_end; f++) {
267 (*f)(args);
268 }
269
270 ml_static_if_flush_icache();
271 }
272
273 MARK_AS_FIXUP_TEXT void
__static_if_key_delta(static_if_key_t key,int delta)274 __static_if_key_delta(static_if_key_t key, int delta)
275 {
276 /*
277 * On SPTM configuration, static_if_init() is called by
278 * arm_static_if_init() during the XNU fixup phase,
279 * before the XNU kernel text is retyped to SPTM_XNU_CODE
280 * and can't be modified anymore.
281 *
282 * For other platforms, this is called from kernel_startup_bootstrap()
283 */
284 if (startup_phase >= STARTUP_SUB_TUNABLES) {
285 panic("static_if_key_{enable,disable} called too late");
286 }
287
288 bool was_enabled = (key->sik_enable_count >= 0);
289
290 /*
291 * Remember modified keys.
292 */
293 if (!key->sik_modified) {
294 key->sik_modified_next = static_if_modified_keys;
295 static_if_modified_keys = key;
296 key->sik_modified = true;
297 }
298
299 key->sik_enable_count += delta;
300
301 if (was_enabled != (key->sik_enable_count >= 0)) {
302 static_if_entry_t sie = key->sik_entries_head;
303 bool init_enabled = key->sik_init_value;
304
305 while (sie) {
306 ml_static_if_entry_patch(sie,
307 (was_enabled == init_enabled) ^
308 __static_if_is_jump(sie));
309 sie = __static_if_entry_next(sie);
310 }
311 }
312 }
313
314 #endif /* STATIC_IF_TEST */
315