]> git.saurik.com Git - apple/xnu.git/blame - san/kasan-fakestack.c
xnu-7195.101.1.tar.gz
[apple/xnu.git] / san / kasan-fakestack.c
CommitLineData
5ba3f43e 1/*
f427ee49 2 * Copyright (c) 2016-2020 Apple Inc. All rights reserved.
5ba3f43e
A
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#include <stdint.h>
30#include <stdbool.h>
31#include <kern/assert.h>
32#include <kern/zalloc.h>
33#include <mach/mach_vm.h>
34#include <mach/vm_param.h>
35#include <libkern/libkern.h>
36#include <libkern/OSAtomic.h>
37#include <sys/queue.h>
38#include <kern/thread.h>
39#include <kern/debug.h>
40
41#include <kasan.h>
42#include <kasan_internal.h>
43
44int __asan_option_detect_stack_use_after_return = 0;
a39ff7e2 45int fakestack_enabled = 0;
5ba3f43e
A
46
47#define FAKESTACK_HEADER_SZ 64
48#define FAKESTACK_NUM_SZCLASS 7
49
d9a64523 50#define FAKESTACK_UNUSED 0 /* waiting to be collected at next gc - forced by clang */
5ba3f43e 51#define FAKESTACK_ALLOCATED 1
d9a64523 52#define FAKESTACK_FREED 2
5ba3f43e
A
53
54#if FAKESTACK
55
56struct fakestack_header {
57 LIST_ENTRY(fakestack_header) list;
58 void *site; /* allocation site */
59 struct {
60 uint8_t flag;
61 vm_size_t realsz : 52;
62 vm_size_t sz_class : 4;
63 };
64 uint64_t __pad0;
65};
66_Static_assert(sizeof(struct fakestack_header) <= FAKESTACK_HEADER_SZ, "fakestack_header size mismatch");
67
68static zone_t fakestack_zones[FAKESTACK_NUM_SZCLASS];
69static char fakestack_names[FAKESTACK_NUM_SZCLASS][16];
70static const unsigned long fakestack_min = 1 << 6;
71static const unsigned long __unused fakestack_max = 1 << 16;
72
73/*
a39ff7e2
A
74 * Enter a fakestack critical section in a reentrant-safe fashion. Returns true on
75 * success with the kasan lock held.
5ba3f43e 76 */
a39ff7e2
A
77static bool
78thread_enter_fakestack(boolean_t *flags)
5ba3f43e 79{
a39ff7e2
A
80 thread_t cur = current_thread();
81 if (cur && kasan_lock_held(cur)) {
82 /* current thread is already in kasan - fail */
83 return false;
5ba3f43e 84 }
a39ff7e2
A
85 kasan_lock(flags);
86 return true;
5ba3f43e
A
87}
88
a39ff7e2
A
89static volatile long suspend_count;
90static const long suspend_threshold = 20;
91
92void
93kasan_fakestack_suspend(void)
5ba3f43e 94{
a39ff7e2
A
95 if (OSIncrementAtomicLong(&suspend_count) == suspend_threshold) {
96 __asan_option_detect_stack_use_after_return = 0;
97 }
98}
99
100void
101kasan_fakestack_resume(void)
102{
103 long orig = OSDecrementAtomicLong(&suspend_count);
104 assert(orig >= 0);
105
106 if (fakestack_enabled && orig == suspend_threshold) {
107 __asan_option_detect_stack_use_after_return = 1;
5ba3f43e
A
108 }
109}
110
111static bool
112ptr_is_on_stack(uptr ptr)
113{
114 vm_offset_t base = dtrace_get_kernel_stack(current_thread());
115
116 if (ptr >= base && ptr < (base + kernel_stack_size)) {
117 return true;
118 } else {
119 return false;
120 }
121}
122
123/* free all unused fakestack entries */
d9a64523 124void
5ba3f43e
A
125kasan_fakestack_gc(thread_t thread)
126{
127 struct fakestack_header *cur, *tmp;
128 LIST_HEAD(, fakestack_header) tofree = LIST_HEAD_INITIALIZER(tofree);
129
d9a64523
A
130 boolean_t flags;
131 if (!thread_enter_fakestack(&flags)) {
132 panic("expected success entering fakestack\n");
133 }
134
135 /* move the unused objects off the per-thread list... */
5ba3f43e
A
136 struct fakestack_header_list *head = &kasan_get_thread_data(thread)->fakestack_head;
137 LIST_FOREACH_SAFE(cur, head, list, tmp) {
d9a64523 138 if (cur->flag == FAKESTACK_UNUSED) {
5ba3f43e
A
139 LIST_REMOVE(cur, list);
140 LIST_INSERT_HEAD(&tofree, cur, list);
d9a64523 141 cur->flag = FAKESTACK_FREED;
5ba3f43e
A
142 }
143 }
144
d9a64523
A
145 kasan_unlock(flags);
146
5ba3f43e
A
147 /* ... then actually free them */
148 LIST_FOREACH_SAFE(cur, &tofree, list, tmp) {
d9a64523
A
149 LIST_REMOVE(cur, list);
150
5ba3f43e
A
151 zone_t zone = fakestack_zones[cur->sz_class];
152 size_t sz = (fakestack_min << cur->sz_class) + FAKESTACK_HEADER_SZ;
5ba3f43e
A
153
154 void *ptr = (void *)cur;
d9a64523 155 kasan_free_internal(&ptr, &sz, KASAN_HEAP_FAKESTACK, &zone, cur->realsz, 0, FAKESTACK_QUARANTINE);
5ba3f43e
A
156 if (ptr) {
157 zfree(zone, ptr);
158 }
159 }
160}
161
162static uint8_t **
163fakestack_flag_ptr(vm_offset_t ptr, vm_size_t sz)
164{
165 uint8_t **x = (uint8_t **)ptr;
166 size_t idx = sz / 8;
167 return &x[idx - 1];
168}
169
170static uptr ALWAYS_INLINE
171kasan_fakestack_alloc(int sz_class, size_t realsz)
172{
173 if (!__asan_option_detect_stack_use_after_return) {
174 return 0;
175 }
176
177 if (sz_class >= FAKESTACK_NUM_SZCLASS) {
178 return 0;
179 }
180
5ba3f43e
A
181 uptr ret = 0;
182 size_t sz = fakestack_min << sz_class;
183 assert(realsz <= sz);
184 assert(sz <= fakestack_max);
185 zone_t zone = fakestack_zones[sz_class];
186
a39ff7e2
A
187 boolean_t flags;
188 if (!thread_enter_fakestack(&flags)) {
5ba3f43e
A
189 return 0;
190 }
191
f427ee49 192 ret = (uptr)zalloc_noblock(zone);
5ba3f43e 193
5ba3f43e
A
194 if (ret) {
195 size_t leftrz = 32 + FAKESTACK_HEADER_SZ;
196 size_t validsz = realsz - 32 - 16; /* remove redzones */
197 size_t rightrz = sz - validsz - 32; /* 16 bytes, plus whatever is left over */
198 struct fakestack_header *hdr = (struct fakestack_header *)ret;
199
200 kasan_poison(ret, validsz, leftrz, rightrz, ASAN_STACK_RZ);
201
202 hdr->site = __builtin_return_address(0);
203 hdr->realsz = realsz;
204 hdr->sz_class = sz_class;
205 hdr->flag = FAKESTACK_ALLOCATED;
206 ret += FAKESTACK_HEADER_SZ;
207
208 *fakestack_flag_ptr(ret, sz) = &hdr->flag; /* back ptr to the slot */
209 struct fakestack_header_list *head = &kasan_get_thread_data(current_thread())->fakestack_head;
210 LIST_INSERT_HEAD(head, hdr, list);
211 }
212
213 kasan_unlock(flags);
214 return ret;
215}
216
217static void NOINLINE
218kasan_fakestack_free(int sz_class, uptr dst, size_t realsz)
219{
220 if (ptr_is_on_stack(dst)) {
221 return;
222 }
223
224 assert(realsz <= (fakestack_min << sz_class));
5ba3f43e
A
225
226 vm_size_t sz = fakestack_min << sz_class;
227 zone_t zone = fakestack_zones[sz_class];
228 assert(zone);
229
230 /* TODO: check the magic? */
231
232 dst -= FAKESTACK_HEADER_SZ;
233 sz += FAKESTACK_HEADER_SZ;
234
235 struct fakestack_header *hdr = (struct fakestack_header *)dst;
236 assert(hdr->sz_class == sz_class);
237
238 boolean_t flags;
239 kasan_lock(&flags);
240
241 LIST_REMOVE(hdr, list);
242
243 kasan_free_internal((void **)&dst, &sz, KASAN_HEAP_FAKESTACK, &zone, realsz, 1, FAKESTACK_QUARANTINE);
244 if (dst) {
0a7de745 245 zfree(zone, dst);
5ba3f43e
A
246 }
247
248 kasan_unlock(flags);
249}
250
251void NOINLINE
d9a64523 252kasan_fakestack_drop(thread_t thread)
5ba3f43e 253{
5ba3f43e 254 boolean_t flags;
a39ff7e2
A
255 if (!thread_enter_fakestack(&flags)) {
256 panic("expected success entering fakestack\n");
257 }
5ba3f43e
A
258
259 struct fakestack_header_list *head = &kasan_get_thread_data(thread)->fakestack_head;
260 struct fakestack_header *cur;
261 LIST_FOREACH(cur, head, list) {
262 if (cur->flag == FAKESTACK_ALLOCATED) {
d9a64523 263 cur->flag = FAKESTACK_UNUSED;
5ba3f43e
A
264 }
265 }
266
5ba3f43e
A
267 kasan_unlock(flags);
268}
269
270void NOINLINE
271kasan_init_fakestack(void)
272{
273 /* allocate the fakestack zones */
274 for (int i = 0; i < FAKESTACK_NUM_SZCLASS; i++) {
5ba3f43e
A
275 unsigned long sz = (fakestack_min << i) + FAKESTACK_HEADER_SZ;
276 size_t maxsz = 256UL * 1024;
277
278 if (i <= 3) {
279 /* size classes 0..3 are much more common */
280 maxsz *= 4;
281 }
282
283 snprintf(fakestack_names[i], 16, "fakestack.%d", i);
f427ee49 284 fakestack_zones[i] = zone_create_ext(fakestack_names[i], sz,
c3c9b80d
A
285 ZC_NOCALLOUT | ZC_NOGC | ZC_NOCACHING |
286 ZC_KASAN_NOREDZONE | ZC_KASAN_NOQUARANTINE,
f427ee49 287 ZONE_ID_ANY, ^(zone_t z) {
c3c9b80d 288 zone_set_exhaustible(z, maxsz / sz);
f427ee49 289 });
c3c9b80d 290 zone_fill_initially(fakestack_zones[i], maxsz / sz);
5ba3f43e
A
291 }
292
293 /* globally enable */
a39ff7e2
A
294 if (fakestack_enabled) {
295 __asan_option_detect_stack_use_after_return = 1;
296 }
5ba3f43e
A
297}
298
299#else /* FAKESTACK */
300
301void
302kasan_init_fakestack(void)
303{
304 assert(__asan_option_detect_stack_use_after_return == 0);
305}
306
307void
308kasan_unpoison_fakestack(thread_t __unused thread)
309{
310 assert(__asan_option_detect_stack_use_after_return == 0);
311}
312
313static uptr
314kasan_fakestack_alloc(int __unused sz_class, size_t __unused realsz)
315{
316 assert(__asan_option_detect_stack_use_after_return == 0);
317 return 0;
318}
319
320static void
321kasan_fakestack_free(int __unused sz_class, uptr __unused dst, size_t __unused realsz)
322{
323 assert(__asan_option_detect_stack_use_after_return == 0);
324 panic("fakestack_free called on non-FAKESTACK config\n");
325}
326
327#endif
328
0a7de745
A
329void
330kasan_init_thread(struct kasan_thread_data *td)
5ba3f43e 331{
5ba3f43e
A
332 LIST_INIT(&td->fakestack_head);
333}
334
335#define FAKESTACK_DECLARE(szclass) \
336 uptr __asan_stack_malloc_##szclass(size_t sz) { return kasan_fakestack_alloc(szclass, sz); } \
337 void __asan_stack_free_##szclass(uptr dst, size_t sz) { kasan_fakestack_free(szclass, dst, sz); }
338
339FAKESTACK_DECLARE(0)
340FAKESTACK_DECLARE(1)
341FAKESTACK_DECLARE(2)
342FAKESTACK_DECLARE(3)
343FAKESTACK_DECLARE(4)
344FAKESTACK_DECLARE(5)
345FAKESTACK_DECLARE(6)
346FAKESTACK_DECLARE(7)
347FAKESTACK_DECLARE(8)
348FAKESTACK_DECLARE(9)
349FAKESTACK_DECLARE(10)