xref: /xnu-12377.1.9/osfmk/kern/static_if_common.c (revision f6217f891ac0bb64f3d375211650a4c1ff8ca1ea)
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