xref: /xnu-8019.80.24/san/memory/ubsan_minimal.c (revision a325d9c4a84054e40bbe985afedcb50ab80993ea)
1 /*
2  * Copyright (c) 2021 Apple 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 
29 /*
30  * UBSan minimal runtime for production environments
31  * This runtime emulates an inlined trap model, but gated through the
32  * logging functions, so that fix and continue is always possible just by
33  * advancing the program counter.
34  * NOTE: this file is regularly instrumented, since all helpers must inline
35  * correctly to a trap.
36  */
37 #include <sys/sysctl.h>
38 #include <libkern/libkern.h>
39 #include <libkern/coreanalytics/coreanalytics.h>
40 #include <kern/backtrace.h>
41 #include <kern/kalloc.h>
42 #include <kern/thread_call.h>
43 #include <kern/percpu.h>
44 #include <machine/machine_routines.h>
45 #include <san/ubsan_minimal.h>
46 
47 #define UBSAN_M_PANIC           (0x0001)
48 #define UBSAN_M_TELEMETRY       (0x0002)
49 
50 struct ubsan_minimal_trap_desc {
51 	uint16_t        id;
52 	uint32_t        flags;
53 	char            str[16];
54 };
55 
56 #if RELEASE
57 static __security_const_late
58 #endif /* RELEASE */
59 struct ubsan_minimal_trap_desc ubsan_traps[] = {
60 	{ UBSAN_MINIMAL_SIGNED_OVERFLOW, UBSAN_M_TELEMETRY, "signed-overflow" },
61 };
62 
63 static SECURITY_READ_ONLY_LATE(bool) ubsan_minimal_enabled = false;
64 static SECURITY_READ_ONLY_LATE(bool) ubsan_minimal_reporting_enabled = false;
65 
66 #define UBSAN_M_BT_FRAMES                       (5)
67 #define UBSAN_M_FRAMES_BUF                      (((UBSAN_M_BT_FRAMES) * 17) + 1)
68 
69 _Static_assert(CA_UBSANBUF_LEN == UBSAN_M_FRAMES_BUF, "Mismatching size between CA_UBSANBUF_LEN and UBSAN internal expected size");
70 
71 /*
72  * Telemetry reporting is unsafe in interrupt context, since the CA framework
73  * relies on being able to successfully zalloc some memory for the event.
74  * Therefore we maintain a small buffer that is then flushed by an helper thread.
75  */
76 #define UBSAN_MINIMAL_RB_SIZE                    2
77 
78 struct ubsan_minimal_rb_entry {
79 	uint32_t        num_frames;
80 	uintptr_t       faulting_address;
81 	uintptr_t       frames[UBSAN_M_BT_FRAMES];
82 };
83 
84 LCK_GRP_DECLARE(ubsan_minimal_lock_grp, "ubsan_minimal_rb_lock");
85 LCK_SPIN_DECLARE(ubsan_minimal_lck, &ubsan_minimal_lock_grp);
86 
87 static struct ubsan_minimal_rb_entry ubsan_minimal_rb[UBSAN_MINIMAL_RB_SIZE];
88 static uint8_t ubsan_minimal_rb_index;
89 static struct thread_call *ubsan_minimal_callout;
90 
91 /* Telemetry: report back the faulting address and the associated backtrace */
92 CA_EVENT(ubsan_minimal_trap,
93     CA_INT, faulting_address,
94     CA_STATIC_STRING(CA_UBSANBUF_LEN), backtrace,
95     CA_STATIC_STRING(CA_UUID_LEN), uuid);
96 
97 /* Rate-limit telemetry on last seen faulting address */
98 static uintptr_t PERCPU_DATA(ubsan_minimal_cache_address);
99 /* Get out from the brk handler if the CPU is already servicing one */
100 static bool PERCPU_DATA(ubsan_minimal_in_handler);
101 
102 /* Helper counters for fixup/drop operations */
103 static uint32_t ubsan_minimal_fixup_events;
104 static uint32_t ubsan_minimal_drop_events;
105 
106 static char *
ubsan_minimal_trap_to_str(uint16_t trap)107 ubsan_minimal_trap_to_str(uint16_t trap)
108 {
109 	return ubsan_traps[trap - UBSAN_MINIMAL_TRAPS_START].str;
110 }
111 
112 static void
ubsan_minimal_backtrace_to_string(char * buf,size_t buflen,uint32_t tot,uintptr_t * frames)113 ubsan_minimal_backtrace_to_string(char *buf, size_t buflen, uint32_t tot, uintptr_t *frames)
114 {
115 	size_t l = 0;
116 
117 	for (uint32_t i = 0; i < tot; i++) {
118 		l += scnprintf(buf + l, buflen - l, "%lx\n", VM_KERNEL_UNSLIDE(frames[i]));
119 	}
120 }
121 
122 static void
ubsan_minimal_send_telemetry(struct ubsan_minimal_rb_entry * list,uint8_t num_entry)123 ubsan_minimal_send_telemetry(struct ubsan_minimal_rb_entry *list, uint8_t num_entry)
124 {
125 	assert(num_entry <= UBSAN_MINIMAL_RB_SIZE);
126 
127 	while (num_entry-- > 0) {
128 		ca_event_t ca_event = CA_EVENT_ALLOCATE(ubsan_minimal_trap);
129 		CA_EVENT_TYPE(ubsan_minimal_trap) * event = ca_event->data;
130 
131 		event->faulting_address = list[num_entry].faulting_address;
132 		ubsan_minimal_backtrace_to_string(event->backtrace, UBSAN_M_FRAMES_BUF,
133 		    list[num_entry].num_frames, list[num_entry].frames);
134 		strlcpy(event->uuid, kernel_uuid_string, CA_UUID_LEN);
135 
136 		CA_EVENT_SEND(ca_event);
137 	}
138 }
139 
140 static void
ubsan_minimal_stash_telemetry(uint32_t total_frames,uintptr_t * backtrace,uintptr_t faulting_address)141 ubsan_minimal_stash_telemetry(uint32_t total_frames, uintptr_t *backtrace,
142     uintptr_t faulting_address)
143 {
144 	if (__improbable(!ubsan_minimal_reporting_enabled)) {
145 		return;
146 	}
147 
148 	/* Skip telemetry if we accidentally took a fault while handling telemetry */
149 	bool *in_handler = PERCPU_GET(ubsan_minimal_in_handler);
150 	if (*in_handler) {
151 		ubsan_minimal_drop_events++;
152 #if DEVELOPMENT
153 		panic("UBSan minimal runtime re-entered from within a spinlock");
154 #endif /* DEVELOPMENT */
155 		return;
156 	}
157 
158 	/* Rate limit on repeatedly seeing the same address */
159 	uintptr_t *cache_address = PERCPU_GET(ubsan_minimal_cache_address);
160 	if (*cache_address == faulting_address) {
161 		ubsan_minimal_drop_events++;
162 		return;
163 	}
164 
165 	*cache_address = faulting_address;
166 
167 	lck_spin_lock(&ubsan_minimal_lck);
168 	*in_handler = true;
169 
170 	if (__improbable(ubsan_minimal_rb_index > UBSAN_MINIMAL_RB_SIZE)) {
171 		panic("Invalid ubsan interrupt buffer index %d >= %d",
172 		    ubsan_minimal_rb_index, UBSAN_MINIMAL_RB_SIZE);
173 	}
174 
175 	/* We're full, just drop the event */
176 	if (ubsan_minimal_rb_index == UBSAN_MINIMAL_RB_SIZE) {
177 		ubsan_minimal_drop_events++;
178 		*in_handler = false;
179 		lck_spin_unlock(&ubsan_minimal_lck);
180 		return;
181 	}
182 
183 	ubsan_minimal_rb[ubsan_minimal_rb_index].faulting_address = faulting_address;
184 	ubsan_minimal_rb[ubsan_minimal_rb_index].num_frames = total_frames;
185 	memcpy(ubsan_minimal_rb[ubsan_minimal_rb_index].frames, backtrace,
186 	    UBSAN_M_BT_FRAMES * sizeof(uintptr_t *));
187 	ubsan_minimal_rb_index++;
188 
189 	*in_handler = false;
190 	lck_spin_unlock(&ubsan_minimal_lck);
191 
192 	thread_call_enter(ubsan_minimal_callout);
193 }
194 
195 static void
ubsan_minimal_flush_entries(__unused thread_call_param_t p0,__unused thread_call_param_t p1)196 ubsan_minimal_flush_entries(__unused thread_call_param_t p0, __unused thread_call_param_t p1)
197 {
198 	struct ubsan_minimal_rb_entry local_rb[UBSAN_MINIMAL_RB_SIZE] = {0};
199 	uint8_t local_index = 0;
200 	bool *in_handler = PERCPU_GET(ubsan_minimal_in_handler);
201 
202 	lck_spin_lock(&ubsan_minimal_lck);
203 	*in_handler = true;
204 
205 	if (__improbable(ubsan_minimal_rb_index > UBSAN_MINIMAL_RB_SIZE)) {
206 		panic("Invalid ubsan interrupt buffer index %d > %d", ubsan_minimal_rb_index,
207 		    UBSAN_MINIMAL_RB_SIZE);
208 	}
209 
210 	if (ubsan_minimal_rb_index == 0) {
211 		*in_handler = false;
212 		lck_spin_unlock(&ubsan_minimal_lck);
213 		return;
214 	}
215 
216 	while (ubsan_minimal_rb_index > 0) {
217 		local_rb[local_index++] = ubsan_minimal_rb[--ubsan_minimal_rb_index];
218 	}
219 
220 	*in_handler = false;
221 	lck_spin_unlock(&ubsan_minimal_lck);
222 
223 	ubsan_minimal_send_telemetry(local_rb, local_index);
224 }
225 
226 void
ubsan_handle_brk_trap(uint16_t trap,uintptr_t faulting_address,uintptr_t saved_fp)227 ubsan_handle_brk_trap(uint16_t trap, uintptr_t faulting_address, uintptr_t saved_fp)
228 {
229 	if (!ubsan_minimal_enabled) {
230 #if DEVELOPMENT
231 		/* We want to know about those sooner than later... */
232 		panic("UBSAN trap taken in early startup code");
233 #endif /* DEVELOPMENT */
234 		return;
235 	}
236 
237 	uintptr_t frames[UBSAN_M_BT_FRAMES];
238 
239 	struct backtrace_control ctl = {
240 		.btc_frame_addr = (uintptr_t)saved_fp,
241 	};
242 
243 	uint32_t total_frames = backtrace(frames, UBSAN_M_BT_FRAMES, &ctl, NULL);
244 	uint32_t trap_idx = trap - UBSAN_MINIMAL_TRAPS_START;
245 
246 	if (ubsan_traps[trap_idx].flags & UBSAN_M_TELEMETRY) {
247 		ubsan_minimal_stash_telemetry(total_frames, frames, VM_KERNEL_UNSLIDE(faulting_address));
248 	}
249 
250 	if (ubsan_traps[trap_idx].flags & UBSAN_M_PANIC) {
251 		panic("UBSAN trap for %s detected\n", ubsan_minimal_trap_to_str(trap));
252 		__builtin_unreachable();
253 	}
254 
255 	ubsan_minimal_fixup_events++;
256 }
257 
258 __startup_func
259 void
ubsan_minimal_init(void)260 ubsan_minimal_init(void)
261 {
262 	ubsan_minimal_callout = thread_call_allocate_with_options(
263 		ubsan_minimal_flush_entries, NULL, THREAD_CALL_PRIORITY_KERNEL,
264 		THREAD_CALL_OPTIONS_ONCE);
265 
266 	if (ubsan_minimal_callout == NULL) {
267 		ubsan_minimal_reporting_enabled = false;
268 	}
269 
270 #if DEVELOPMENT || DEBUG
271 	bool force_panic = false;
272 	PE_parse_boot_argn("-ubsan_force_panic", &force_panic, sizeof(force_panic));
273 	if (force_panic) {
274 		for (int i = UBSAN_MINIMAL_TRAPS_START; i < UBSAN_MINIMAL_TRAPS_END; i++) {
275 			size_t idx = i - UBSAN_MINIMAL_TRAPS_START;
276 			ubsan_traps[idx].flags |= UBSAN_M_PANIC;
277 		}
278 	}
279 #endif /* DEVELOPMENT || DEBUG */
280 
281 	ubsan_minimal_reporting_enabled = true;
282 	ubsan_minimal_enabled = true;
283 }
284 
285 #if DEVELOPMENT || DEBUG
286 
287 /* Add a simple testing path that explicitly triggers a signed int overflow */
288 static int
289 sysctl_ubsan_test SYSCTL_HANDLER_ARGS
290 {
291 #pragma unused(oidp, arg1, arg2)
292 	int err, incr = 0;
293 	err = sysctl_io_number(req, 0, sizeof(int), &incr, NULL);
294 
295 	int k = 0x7fffffff;
296 
297 	for (int i = 0; i < UBSAN_MINIMAL_RB_SIZE + 1; i++) {
298 		int a = k;
299 		if (incr != 0) {
300 			k += incr;
301 		}
302 
303 		k = a;
304 	}
305 
306 	return err;
307 }
308 
309 SYSCTL_DECL(kern_ubsan);
310 SYSCTL_NODE(_kern, OID_AUTO, ubsan, CTLFLAG_RW | CTLFLAG_LOCKED, 0, "ubsan");
311 SYSCTL_NODE(_kern_ubsan, OID_AUTO, minimal, CTLFLAG_RW | CTLFLAG_LOCKED, 0, "minimal runtime");
312 SYSCTL_INT(_kern_ubsan_minimal, OID_AUTO, fixups, CTLFLAG_RW | CTLFLAG_LOCKED, &ubsan_minimal_fixup_events, 0, "");
313 SYSCTL_INT(_kern_ubsan_minimal, OID_AUTO, drops, CTLFLAG_RW | CTLFLAG_LOCKED, &ubsan_minimal_drop_events, 0, "");
314 SYSCTL_INT(_kern_ubsan_minimal, OID_AUTO, signed_ovf_flags, CTLFLAG_RW | CTLFLAG_LOCKED, &(ubsan_traps[0].flags), 0, "");
315 SYSCTL_PROC(_kern_ubsan_minimal, OID_AUTO, test, CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_LOCKED,
316     0, 0, sysctl_ubsan_test, "I", "Test signed overflow detection");
317 
318 #endif /* DEVELOPMENT || DEBUG */
319 
320 #define UBSAN_M_ATTR    __attribute__((always_inline, cold))
321 
322 UBSAN_M_ATTR void
__ubsan_handle_divrem_overflow_minimal(void)323 __ubsan_handle_divrem_overflow_minimal(void)
324 {
325 	asm volatile ("brk #%0" : : "i"(UBSAN_MINIMAL_SIGNED_OVERFLOW));
326 }
327 
328 UBSAN_M_ATTR void
__ubsan_handle_negate_overflow_minimal(void)329 __ubsan_handle_negate_overflow_minimal(void)
330 {
331 	asm volatile ("brk #%0" : : "i"(UBSAN_MINIMAL_SIGNED_OVERFLOW));
332 }
333 
334 UBSAN_M_ATTR void
__ubsan_handle_mul_overflow_minimal(void)335 __ubsan_handle_mul_overflow_minimal(void)
336 {
337 	asm volatile ("brk #%0" : : "i"(UBSAN_MINIMAL_SIGNED_OVERFLOW));
338 }
339 
340 UBSAN_M_ATTR void
__ubsan_handle_sub_overflow_minimal(void)341 __ubsan_handle_sub_overflow_minimal(void)
342 {
343 	asm volatile ("brk #%0" : : "i"(UBSAN_MINIMAL_SIGNED_OVERFLOW));
344 }
345 
346 UBSAN_M_ATTR void
__ubsan_handle_add_overflow_minimal(void)347 __ubsan_handle_add_overflow_minimal(void)
348 {
349 	asm volatile ("brk #%0" : : "i"(UBSAN_MINIMAL_SIGNED_OVERFLOW));
350 }
351