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