1 /*
2 * Copyright (c) 2024 Apple Computer, Inc. All rights reserved.
3 *
4 * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. The rights granted to you under the License
10 * may not be used to create, or enable the creation or redistribution of,
11 * unlawful or unlicensed copies of an Apple operating system, or to
12 * circumvent, violate, or enable the circumvention or violation of, any
13 * terms of an Apple operating system software license agreement.
14 *
15 * Please obtain a copy of the License at
16 * http://www.opensource.apple.com/apsl/ and read it before using this file.
17 *
18 * The Original Code and all software distributed under the License are
19 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
20 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
21 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
22 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
23 * Please see the License for the specific language governing rights and
24 * limitations under the License.
25 *
26 * @APPLE_OSREFERENCE_LICENSE_HEADER_END@
27 */
28 #include <pexpert/arm64/board_config.h>
29
30 /*
31 * MTE implements the memtag interface that also KASAN-TBI uses. This file
32 * leverages the APIs provided by mte.h to implement memtag interfaces.
33 *
34 * On top of memtag interfaces a few dedicated helpers for kernel specific
35 * MTE consumers (e.g. memory compressor) are implemented here.
36 */
37
38 #if HAS_MTE
39
40 #include <mach/arm/thread_status.h>
41 #include <mach/machine/vm_types.h>
42 #include <kern/kern_types.h>
43 #include <kern/thread.h>
44 #include <kern/exc_guard.h>
45
46 #include <vm/vm_kern.h>
47 #include <vm/vm_page.h>
48 #include <vm/vm_memtag.h>
49 #include <vm/vm_map_xnu.h>
50 #include <vm/pmap.h>
51
52 #include <sys/reason.h>
53
54 #include <arm64/mte_xnu.h>
55
56 #if DEVELOPMENT || DEBUG
57 /*
58 * PSTATE.TCO sounds mellow, but can be evil. Tag Check Override is meant to be
59 * a way to briefly disable tag checking during a trusted path. It must not extend
60 * past the path, into unexpected callee/callers. For this reason, we should not incentivize
61 * its use. This function adds some enforcing that TCO is at the state we expect
62 * whenever we try to manipulate it. We intentionally do not implement any form of
63 * save and restore of its state. We don't enable this on release as reading TCO is
64 * a slow operation and would impact performance.
65 */
66 void
mte_validate_tco_state(void)67 mte_validate_tco_state(void)
68 {
69 if (!mte_debug_tco_state()) {
70 return;
71 }
72
73 uint64_t tco_state = 0;
74
75 __asm__ __volatile__ ("mrs %0, TCO" : "=r" (tco_state) ::);
76
77 if (tco_state != 0) {
78 panic("[ERROR] Unexpected non-zero PSTATE.TCO state entering a manipulation function");
79 }
80 }
81 #endif /* DEVELOPMENT || DEBUG */
82
83 __attribute__((noinline))
84 void
mte_report_non_canonical_address(__unused caddr_t address,__unused vm_map_t map,__unused const char * location)85 mte_report_non_canonical_address(__unused caddr_t address, __unused vm_map_t map, __unused const char *location)
86 {
87 #if DEVELOPMENT || DEBUG
88 if (mte_panic_on_non_canonical()) {
89 panic("MTE: detected canonical address (%p), map(%p) in function %s\n",
90 address, map, location);
91 }
92 #endif /* DEVELOPMENT || DEBUG */
93 }
94
95
96 /*
97 * Generate an exclude mask out of a best effort to hold
98 * MTE linear overflow detection guarantees. We don't know anything
99 * about the memory layout and organization of who's calling here,
100 * so we can just make assumptions on neighbours.
101 */
102 static mte_exclude_mask_t
mte_generate_default_exclude_mask(caddr_t target,size_t size)103 mte_generate_default_exclude_mask(caddr_t target, size_t size)
104 {
105 mte_exclude_mask_t mask = 0;
106 /* Exclude the current pointer from the selection mask */
107 mask = mte_update_exclude_mask(target, mask);
108
109 /* kernel canonical tag is covered by GCR_EL1, but doesn't hurt to add it here. */
110 mask = mte_update_exclude_mask((caddr_t)-1ULL, mask);
111
112 #define mte_page_align(x) ((uintptr_t)(x) & -(PAGE_SIZE))
113
114 /* Best effort to incorporate boundary objects tags. */
115 if (mte_page_align(target) == mte_page_align(target - 16)) {
116 mask = mte_update_exclude_mask(mte_load_tag(target - 16), mask);
117 }
118
119 if (mte_page_align(target + size - 1) == mte_page_align(target + size)) {
120 mask = mte_update_exclude_mask(mte_load_tag(target + size), mask);
121 }
122
123 return mask;
124 }
125
126 /*
127 * Generate a random tag out of either a default exclude mask or a caller provided
128 * exclude mask.
129 */
130 __attribute__((overloadable))
131 caddr_t
mte_generate_and_store_tag(caddr_t target,size_t size)132 mte_generate_and_store_tag(caddr_t target, size_t size)
133 {
134 mte_exclude_mask_t mask = mte_generate_default_exclude_mask(target, size);
135
136 return mte_generate_and_store_tag(target, size, mask);
137 }
138
139 __attribute__((overloadable))
140 caddr_t
mte_generate_and_store_tag(caddr_t target,size_t size,mte_exclude_mask_t mask)141 mte_generate_and_store_tag(caddr_t target, size_t size, mte_exclude_mask_t mask)
142 {
143 target = mte_generate_random_tag(target, mask);
144 mte_store_tag(target, size);
145
146
147 return target;
148 }
149
150 void
mte_bulk_read_tags(caddr_t va,size_t va_size,mte_bulk_taglist_t * buffer,size_t buf_size)151 mte_bulk_read_tags(caddr_t va, size_t va_size, mte_bulk_taglist_t * buffer, size_t buf_size)
152 {
153 assert((uintptr_t)va % 256 == 0);
154 assert((va_size / 256) == (buf_size / sizeof(mte_bulk_taglist_t)));
155
156 if ((va_size / 256) != (buf_size / sizeof(mte_bulk_taglist_t))) {
157 panic("Buffer size doesn't match MTE bulk size request\n");
158 }
159
160 size_t buf_count = buf_size / sizeof(mte_bulk_taglist_t);
161
162 for (size_t counter = 0; counter < buf_count; counter++, va += 256) {
163 buffer[counter] = mte_load_tag_256(va);
164 }
165 }
166
167 void
mte_bulk_write_tags(caddr_t va,size_t __unused va_size,mte_bulk_taglist_t * buffer,size_t buf_size)168 mte_bulk_write_tags(caddr_t va, size_t __unused va_size, mte_bulk_taglist_t * buffer, size_t buf_size)
169 {
170 assert((uintptr_t)va % 256 == 0);
171 assert((va_size / 256) == (buf_size / sizeof(mte_bulk_taglist_t)));
172
173 if ((va_size / 256) != (buf_size / sizeof(mte_bulk_taglist_t))) {
174 panic("Buffer size doesn't match MTE bulk size request\n");
175 }
176
177 size_t buf_count = buf_size / sizeof(mte_bulk_taglist_t);
178
179 for (size_t counter = 0; counter < buf_count; counter++, va += 256) {
180 mte_store_tag_256(va, buffer[counter]);
181 }
182 }
183
184 void
mte_copy_tags(caddr_t dest,caddr_t source,vm_size_t size)185 mte_copy_tags(caddr_t dest, caddr_t source, vm_size_t size)
186 {
187 for (vm_size_t i = 0; i < size; i += 256) {
188 mte_bulk_taglist_t tags = mte_load_tag_256(source + i);
189 mte_store_tag_256(dest + i, tags);
190 }
191 }
192
193 /*
194 * During initial adoption we want to detect MTE violations and just
195 * fix and continue. This is achieved by clearing SCTLR_ELx.TCF0 for the
196 * user thread.
197 */
198 void
mte_disable_user_checking(task_t task)199 mte_disable_user_checking(task_t task)
200 {
201 task_set_sec_never_check(task);
202 vm_map_set_sec_disabled(get_task_map(task));
203 }
204
205 #if !KASAN
206 /* memtag APIs on KASAN are currently provided by KASAN-TBI also on Hidra. */
207
208 /*
209 * MTE implements the vm_memtag interface.
210 */
211 void
vm_memtag_bzero_fast_checked(void * tagged_buf,vm_size_t n)212 vm_memtag_bzero_fast_checked(void *tagged_buf, vm_size_t n)
213 {
214 if (__probable(is_mte_enabled)) {
215 mte_bzero_fast_checked(tagged_buf, n);
216 } else {
217 bzero(tagged_buf, n);
218 }
219 }
220
221 void
vm_memtag_bzero_unchecked(void * tagged_buf,vm_size_t n)222 vm_memtag_bzero_unchecked(void *tagged_buf, vm_size_t n)
223 {
224 if (__probable(is_mte_enabled)) {
225 mte_bzero_unchecked(tagged_buf, n);
226 } else {
227 bzero(tagged_buf, n);
228 }
229 }
230
231 vm_map_address_t
vm_memtag_load_tag(vm_map_address_t naked_address)232 vm_memtag_load_tag(vm_map_address_t naked_address)
233 {
234 if (__probable(is_mte_enabled)) {
235 return (vm_map_address_t)mte_load_tag((caddr_t)naked_address);
236 } else {
237 return naked_address;
238 }
239 }
240
241 void
vm_memtag_store_tag(caddr_t tagged_address,vm_size_t size)242 vm_memtag_store_tag(caddr_t tagged_address, vm_size_t size)
243 {
244 if (__probable(is_mte_enabled)) {
245 mte_store_tag(tagged_address, size);
246 }
247 }
248
249 caddr_t
vm_memtag_generate_and_store_tag(caddr_t address,vm_size_t size)250 vm_memtag_generate_and_store_tag(caddr_t address, vm_size_t size)
251 {
252 if (__probable(is_mte_enabled)) {
253 return mte_generate_and_store_tag(address, size);
254 }
255
256 return address;
257 }
258
259 void
vm_memtag_verify_tag(vm_map_address_t tagged_address)260 vm_memtag_verify_tag(vm_map_address_t tagged_address)
261 {
262 if (__probable(is_mte_enabled)) {
263 asm volatile ("ldrb wzr, [%0]" : : "r"(tagged_address) : "memory");
264 }
265 }
266
267 void
vm_memtag_relocate_tags(vm_address_t new_address,vm_address_t old_address,vm_size_t size)268 vm_memtag_relocate_tags(vm_address_t new_address, vm_address_t old_address, vm_size_t size)
269 {
270 if (__improbable(!is_mte_enabled)) {
271 return;
272 }
273
274 mte_copy_tags((caddr_t)new_address, (caddr_t)old_address, size);
275 }
276
277 void
vm_memtag_disable_checking()278 vm_memtag_disable_checking()
279 {
280 mte_disable_tag_checking();
281 }
282
283 __attribute__((always_inline))
284 void
vm_memtag_enable_checking()285 vm_memtag_enable_checking()
286 {
287 mte_enable_tag_checking();
288 }
289 #endif /* KASAN */
290 /*
291 * MTE exceptions are always synchronous in hardware, but can be conceptually synchronous or asynchronous
292 * in software. An asynchronous MTE software exception happens every time the kernel operates on behalf of
293 * a user supplied pointer but not directly in the context of the user thread supplying it. We deliver
294 * such asynchronous exceptions as GUARD_VM exception, although we'll transition to a dedicated exception
295 * type with rdar://150503373 (MTE Exceptions should not overload VM_GUARD exceptions and instead have their own).
296 *
297 * Both synchronous and asynchronous exceptions can happen on a task that is running in soft-mode. In
298 * this case, they are not fatal and a simulated crash is generated.
299 */
300 void
mte_guard_ast(thread_t thread,mach_exception_data_type_t code,mach_exception_data_type_t subcode)301 mte_guard_ast(
302 thread_t thread,
303 mach_exception_data_type_t code,
304 mach_exception_data_type_t subcode)
305 {
306 task_t task = get_threadtask(thread);
307 assert(task != kernel_task);
308
309 /* Downstream code currently only supports operating on current task/thread/proc. */
310 assert(task == current_task());
311 assert(thread == current_thread());
312 assert(vm_guard_is_mte_fault(EXC_GUARD_DECODE_GUARD_FLAVOR(code)));
313
314 /* For soft-mode we always generate simulated crashes. */
315 if (task_has_sec_soft_mode(task)) {
316 /* Only softmode guards should be sent down here. */
317 assert(EXC_GUARD_DECODE_GUARD_TARGET(code) & kGUARD_EXC_MTE_SOFT_MODE);
318 task_violated_guard(code, subcode, NULL, FALSE);
319 return;
320 }
321
322 /* All exceptions past this point are fatal. */
323 kern_return_t sync_exception_result = task_exception_notify(EXC_GUARD, code, subcode, /* fatal */ true);
324
325 int flags = 0;
326 exception_info_t info = {
327 .os_reason = OS_REASON_GUARD,
328 .exception_type = EXC_GUARD,
329 .mx_code = code,
330 .mx_subcode = subcode
331 };
332
333 if (sync_exception_result == KERN_SUCCESS) {
334 flags |= PX_PSIGNAL;
335 }
336 exit_with_mach_exception(current_proc(), info, flags);
337 }
338
339 /*
340 * Special MTE AST handler for asyncronous traps raised while in a kernel thread
341 * context. For these traps we have to synthesize from thin air the exception, as
342 * the only thing we saved at the time of the fault was the faulting address.
343 *
344 * There's also no notion of a thread to blame, due to the disjoint nature of
345 * registering for some work (e.g. IOMD) and later performing it. This leads to
346 * the special AST_SYNTHESIZE_MACH ast to be flagged on all threads of the
347 * victim task. We use magic sentinel values to avoid delivering to more than
348 * a single target, but the picked one is completely random and likely entirely
349 * unrelated at its execution point.
350 */
351 void
mte_synthesize_async_tag_check_fault(thread_t thread,vm_map_t map)352 mte_synthesize_async_tag_check_fault(thread_t thread, vm_map_t map)
353 {
354 task_t task = get_threadtask(thread);
355 assert(task == current_task_early());
356
357 /* Bail out if we have a wrong task. */
358 if (task == NULL || task == kernel_task) {
359 return;
360 }
361
362 vm_map_offset_t address = os_atomic_load(&map->async_tag_fault_address, relaxed);
363
364 /* Report only once, so that we don't send this for more than a single thread in the task. */
365 if ((address != 0) && (address != VM_ASYNC_TAG_FAULT_ALREADY_REPORTED)) {
366 if (os_atomic_cmpxchg(&map->async_tag_fault_address, address, VM_ASYNC_TAG_FAULT_ALREADY_REPORTED, relaxed)) {
367 /*
368 * Some kernel thread doing work on behalf of our task asynchronously
369 * reported an MTE tag check fault.
370 */
371 mach_exception_code_t code = 0;
372 mach_exception_subcode_t subcode = address;
373 EXC_GUARD_ENCODE_TYPE(code, GUARD_TYPE_VIRT_MEMORY);
374 EXC_GUARD_ENCODE_FLAVOR(code, kGUARD_EXC_MTE_ASYNC_KERN_FAULT);
375 /* Hardcoded as we should never be in a canonical tag check fault scenario. */
376 EXC_GUARD_ENCODE_TARGET(code, EXC_ARM_MTE_TAGCHECK_FAIL);
377
378 if (task_has_sec_soft_mode(task)) {
379 code |= kGUARD_EXC_MTE_SOFT_MODE;
380 }
381 mte_guard_ast(thread, code, subcode);
382 }
383 }
384 }
385
386
387 #endif /* HAS_MTE */
388