xref: /xnu-12377.81.4/osfmk/arm64/mte.c (revision 043036a2b3718f7f0be807e2870f8f47d3fa0796)
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