xref: /xnu-11215/osfmk/kern/task_ref.c (revision 1031c584)
1 /*
2  * C (c) 2000-2020 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 #include <kern/task.h>
30 #include <kern/task_ref.h>
31 #include <libkern/OSKextLibPrivate.h>
32 
33 #include <os/refcnt.h>
34 
35 /*
36  * Task references.
37  *
38  * Each task reference/deallocate pair has an associated reference group:
39  *     TASK_GRP_INTERNAL This group is used exclusively to track long-term
40  *                       references which are almost always present.
41  *                       Specifically, the importance task reference, the owning
42  *                       task reference and the thread references.
43  *     TASK_GRP_EXTERNAL For kext references
44  *     TASK_KERNEL       For at-large kernel references other than those tracked
45  *                       by task_internal.
46  *     TASK_GRP_MIG      For references from the MIG layer
47  *
48  * Depending on configuration (see task_refgrp_config) os_refgrps are used to
49  * keep track of the context of the reference/deallocation.
50  *
51  * TASK_REF_CONFIG_OFF
52  * No refgrps are used other than the single 'task' reference group.
53  *
54  * TASK_REF_CONFIG_DEFAULT
55  * Global refgrps are used for 'kernel' and 'external' references. The
56  * primary 'task' reference group is set as their parent. Each kext also gets
57  * its own refgrp parented to the 'external' group.
58  * Each task gets two reference groups - one for 'kernel' references parented to
59  * the global 'kernel' group and as second which is dynamically assigned. All
60  * references tagged with TASK_GRP_INTERNAL, TASK_GRP_KERNEL and TASK_GRP_MIG
61  * use the task 'kernel' group. The dynamic group is initialized for the first
62  * 'external' reference to a kext specific group parented to the matching global
63  * kext group. For 'external' references not matching that group, the global
64  * 'external' group is used.
65  * This is the default configuration.
66  *
67  * TASK_REF_CONFIG_FULL
68  * Global refgrps are used for 'kernel', 'external', 'internal' and 'mig'
69  * references. The primary 'task' reference group is set as their parent. Each
70  * kext also gets is own refgrp parented to the 'external' group.
71  * Each task gets eight reference groups - one each mirroring the four global
72  * reference groups and four dynamic groups which are assigned to kexts. For
73  * 'external' references not matching any of the four dynamic groups, the global
74  * 'external' group is used.
75  *
76  * Kext callers have the calls which take or release task references mapped
77  * to '_external' equivalents  via the .exports file.
78  *
79  * At-large kernel callers see calls  redefined to call the '_kernel' variants
80  * (see task_ref.h).
81  *
82  * The mig layer generates code which uses the '_mig' variants.
83  *
84  * Other groups are selected explicitly.
85  *
86  * Reference groups support recording of back traces via the rlog boot arg.
87  * For example: rlog=task_external would keep a backtrace log of all external
88  * references.
89  */
90 
91 #define TASK_REF_COUNT_INITIAL  (2u)
92 
93 extern void task_deallocate_internal(task_t task, os_ref_count_t refs);
94 
95 #if DEVELOPMENT || DEBUG
96 
97 #include <stdbool.h>
98 
99 #define DYNAMIC_COUNT 4
100 
101 /*
102  * Controlled by the boot arg 'task_refgrp=X'.
103  *
104  * Unspecified/default
105  * There are two task reference groups. One kext specific reference group, the
106  * other used for kernel/internal and mig references.
107  *
108  * "off"
109  * No task specific reference groups are used.
110  *
111  * "full"
112  * Each task gets its own set of kernel/internal/mig and external groups.
113  * Additionally four dynamic reference groups are made available to identify kext
114  * references.
115  */
116 __attribute__((used))
117 static enum {
118 	TASK_REF_CONFIG_DEFAULT,
119 	TASK_REF_CONFIG_FULL,
120 	TASK_REF_CONFIG_OFF,
121 } task_refgrp_config = TASK_REF_CONFIG_DEFAULT;
122 
123 /* Global reference groups. */
124 os_refgrp_decl_flags(static, task_primary_refgrp, "task", NULL, OS_REFGRP_F_ALWAYS_ENABLED);
125 os_refgrp_decl_flags(static, task_kernel_refgrp, "task_kernel", &task_primary_refgrp, OS_REFGRP_F_ALWAYS_ENABLED);
126 os_refgrp_decl_flags(static, task_internal_refgrp, "task_internal", &task_primary_refgrp, OS_REFGRP_F_ALWAYS_ENABLED);
127 os_refgrp_decl_flags(static, task_mig_refgrp, "task_mig", &task_primary_refgrp, OS_REFGRP_F_ALWAYS_ENABLED);
128 os_refgrp_decl_flags(, task_external_refgrp, "task_external", &task_primary_refgrp, OS_REFGRP_F_ALWAYS_ENABLED);
129 
130 
131 /* 'task_refgrp' is used by lldb macros. */
132 __attribute__((used))
133 static struct os_refgrp * const task_refgrp[TASK_GRP_COUNT] = {
134 	[TASK_GRP_KERNEL]   = &task_kernel_refgrp,
135 	[TASK_GRP_INTERNAL] = &task_internal_refgrp,
136 	[TASK_GRP_MIG]      = &task_mig_refgrp,
137 	[TASK_GRP_EXTERNAL] = &task_external_refgrp,
138 };
139 
140 /* Names used by local reference groups. */
141 static const char * const local_name[TASK_GRP_COUNT] = {
142 	[TASK_GRP_KERNEL]   = "task_local_kernel",
143 	[TASK_GRP_INTERNAL] = "task_local_internal",
144 	[TASK_GRP_MIG]      = "task_local_mig",
145 	[TASK_GRP_EXTERNAL] = "task_local_external",
146 };
147 
148 /* Walk back the callstack calling cb for each address. */
149 static inline void
150 walk_kext_callstack(int (^cb)(uintptr_t))
151 {
152 	uintptr_t* frameptr;
153 	uintptr_t* frameptr_next;
154 	uintptr_t retaddr;
155 	uintptr_t kstackb, kstackt;
156 	thread_t cthread;
157 
158 	cthread = current_thread();
159 	assert3p(cthread, !=, NULL);
160 
161 	kstackb = thread_get_kernel_stack(cthread);
162 	kstackt = kstackb + kernel_stack_size;
163 
164 	/* Load stack frame pointer (EBP on x86) into frameptr */
165 	frameptr = __builtin_frame_address(0);
166 
167 	while (frameptr != NULL) {
168 		/* Verify thread stack bounds */
169 		if (((uintptr_t)(frameptr + 2) > kstackt) ||
170 		    ((uintptr_t)frameptr < kstackb)) {
171 			break;
172 		}
173 
174 		/* Next frame pointer is pointed to by the previous one */
175 		frameptr_next = (uintptr_t*) *frameptr;
176 #if defined(HAS_APPLE_PAC)
177 		frameptr_next = ptrauth_strip(frameptr_next,
178 		    ptrauth_key_frame_pointer);
179 #endif
180 
181 		/* Pull return address from one spot above the frame pointer */
182 		retaddr = *(frameptr + 1);
183 
184 #if defined(HAS_APPLE_PAC)
185 		retaddr = (uintptr_t) ptrauth_strip((void *)retaddr,
186 		    ptrauth_key_return_address);
187 #endif
188 
189 		if (((retaddr < vm_kernel_builtinkmod_text_end) &&
190 		    (retaddr >= vm_kernel_builtinkmod_text)) ||
191 		    (retaddr < vm_kernel_stext) || (retaddr > vm_kernel_top)) {
192 			if (cb(retaddr) != 0) {
193 				return;
194 			}
195 		}
196 		frameptr = frameptr_next;
197 	}
198 
199 	return;
200 }
201 
202 /* Return the reference group associated with the 'closest' kext. */
203 static struct os_refgrp *
lookup_kext_refgrp(void)204 lookup_kext_refgrp(void)
205 {
206 	__block struct os_refgrp *refgrp = NULL;
207 
208 	/* Get the kext specific group based on the current stack. */
209 	walk_kext_callstack(^(uintptr_t retaddr) {
210 		OSKextGetRefGrpForCaller(retaddr, ^(struct os_refgrp *kext_grp) {
211 			assert(kext_grp != NULL);
212 			refgrp = kext_grp;
213 		});
214 		return 1;
215 	});
216 	return refgrp;
217 }
218 
219 
220 /*
221  * Given an array of reference groups, find one that matches the specified kext
222  * group. If there is no match and there is a empty slot, initialize a new
223  * refgrp with the kext group as the parent (only when `can_allocate` is true).
224  */
225 static struct os_refgrp *
lookup_dynamic_refgrp(struct os_refgrp * kext,struct os_refgrp * dynamic,int dynamic_count,bool can_allocate)226 lookup_dynamic_refgrp(struct os_refgrp *kext,
227     struct os_refgrp *dynamic, int dynamic_count, bool can_allocate)
228 {
229 	/* First see if it exists. */
230 	for (int i = 0; i < dynamic_count; i++) {
231 		if (dynamic[i].grp_parent == kext) {
232 			return &dynamic[i];
233 		}
234 	}
235 
236 	if (!can_allocate) {
237 		return NULL;
238 	}
239 
240 	/* Grab an empty one, if available. */
241 	for (int i = 0; i < dynamic_count; i++) {
242 		if (dynamic[i].grp_name == NULL) {
243 			dynamic[i] = (struct os_refgrp)
244 			    os_refgrp_initializer(kext->grp_name, kext,
245 			    OS_REFGRP_F_ALWAYS_ENABLED);
246 			return &dynamic[i];
247 		}
248 	}
249 
250 	return NULL;
251 }
252 
253 /*
254  * Find the best external reference group.
255  * - Task specific kext ref group
256  *   else
257  * - Kext ref group
258  *   else
259  * - Global external ref group
260  */
261 static struct os_refgrp *
find_external_refgrp(struct os_refgrp * dynamic,int dynamic_count,bool can_allocate)262 find_external_refgrp(struct os_refgrp *dynamic, int dynamic_count,
263     bool can_allocate)
264 {
265 	struct os_refgrp *kext_refgrp = lookup_kext_refgrp();
266 	if (kext_refgrp == NULL) {
267 		return task_refgrp[TASK_GRP_EXTERNAL];
268 	}
269 
270 	struct os_refgrp *refgrp = lookup_dynamic_refgrp(kext_refgrp, dynamic,
271 	    dynamic_count, can_allocate);
272 	if (refgrp == NULL) {
273 		return kext_refgrp;
274 	}
275 
276 	return refgrp;
277 }
278 
279 void
task_reference_grp(task_t task,task_grp_t grp)280 task_reference_grp(task_t task, task_grp_t grp)
281 {
282 	assert3u(grp, <, TASK_GRP_COUNT);
283 	assert(
284 		task_refgrp_config == TASK_REF_CONFIG_OFF ||
285 		task_refgrp_config == TASK_REF_CONFIG_DEFAULT ||
286 		task_refgrp_config == TASK_REF_CONFIG_FULL);
287 
288 	struct os_refgrp *refgrp = NULL;
289 
290 	if (task == TASK_NULL) {
291 		return;
292 	}
293 
294 	task_require(task);
295 
296 	/*
297 	 * External ref groups need to search and potentially allocate from the
298 	 * dynamic task ref groups. This must be protected by a lock.
299 	 */
300 	if (task_refgrp_config != TASK_REF_CONFIG_OFF &&
301 	    grp == TASK_GRP_EXTERNAL) {
302 		lck_spin_lock(&task->ref_group_lock);
303 	}
304 
305 	switch (task_refgrp_config) {
306 	case TASK_REF_CONFIG_OFF:
307 		refgrp = NULL;
308 		break;
309 
310 	case TASK_REF_CONFIG_DEFAULT:
311 
312 		refgrp = (grp == TASK_GRP_EXTERNAL) ?
313 		    find_external_refgrp(&task->ref_group[1], 1, true) :
314 		    &task->ref_group[TASK_GRP_KERNEL];
315 		break;
316 
317 	case TASK_REF_CONFIG_FULL:
318 
319 		refgrp = (grp == TASK_GRP_EXTERNAL) ?
320 		    find_external_refgrp(&task->ref_group[TASK_GRP_COUNT], DYNAMIC_COUNT, true) :
321 		    &task->ref_group[grp];
322 		break;
323 	}
324 
325 	os_ref_retain_raw(&task->ref_count.ref_count, refgrp);
326 
327 	if (task_refgrp_config != TASK_REF_CONFIG_OFF &&
328 	    grp == TASK_GRP_EXTERNAL) {
329 		lck_spin_unlock(&task->ref_group_lock);
330 	}
331 }
332 
333 void
task_deallocate_grp(task_t task,task_grp_t grp)334 task_deallocate_grp(task_t task, task_grp_t grp)
335 {
336 	assert3u(grp, <, TASK_GRP_COUNT);
337 	assert(
338 		task_refgrp_config == TASK_REF_CONFIG_OFF ||
339 		task_refgrp_config == TASK_REF_CONFIG_DEFAULT ||
340 		task_refgrp_config == TASK_REF_CONFIG_FULL);
341 
342 	os_ref_count_t refs = -1;
343 	struct os_refgrp *refgrp = NULL;
344 
345 	if (task == TASK_NULL) {
346 		return;
347 	}
348 
349 	/*
350 	 * There is no need to take the ref_group_lock when de-allocating. The
351 	 * lock is only required when allocating a group.
352 	 */
353 	switch (task_refgrp_config) {
354 	case TASK_REF_CONFIG_OFF:
355 		refgrp = NULL;
356 		break;
357 
358 	case TASK_REF_CONFIG_DEFAULT:
359 		refgrp = (grp == TASK_GRP_EXTERNAL) ?
360 		    find_external_refgrp(&task->ref_group[1], 1, false) :
361 		    &task->ref_group[TASK_GRP_KERNEL];
362 		break;
363 
364 	case TASK_REF_CONFIG_FULL:
365 		refgrp = (grp == TASK_GRP_EXTERNAL) ?
366 		    find_external_refgrp(&task->ref_group[TASK_GRP_COUNT], DYNAMIC_COUNT, false) :
367 		    &task->ref_group[grp];
368 		break;
369 	}
370 
371 
372 	refs = os_ref_release_raw(&task->ref_count.ref_count, refgrp);
373 	/* Beware - the task may have been freed after this point. */
374 
375 	task_deallocate_internal(task, refs);
376 }
377 
378 void
task_reference_external(task_t task)379 task_reference_external(task_t task)
380 {
381 	task_reference_grp(task, TASK_GRP_EXTERNAL);
382 }
383 
384 void
task_deallocate_external(task_t task)385 task_deallocate_external(task_t task)
386 {
387 	task_deallocate_grp(task, TASK_GRP_EXTERNAL);
388 }
389 
390 static void
allocate_refgrp_default(task_t task)391 allocate_refgrp_default(task_t task)
392 {
393 	/* Just one static group and one dynamic group. */
394 	task->ref_group = kalloc_type(struct os_refgrp, 2,
395 	    Z_WAITOK | Z_ZERO | Z_NOFAIL);
396 
397 	task->ref_group[TASK_GRP_KERNEL] = (struct os_refgrp)
398 	    os_refgrp_initializer(local_name[TASK_GRP_KERNEL],
399 	    task_refgrp[TASK_GRP_KERNEL], OS_REFGRP_F_ALWAYS_ENABLED);
400 	os_ref_log_init(&task->ref_group[TASK_GRP_KERNEL]);
401 }
402 
403 static void
free_refgrp_default(task_t task)404 free_refgrp_default(task_t task)
405 {
406 	os_ref_log_fini(&task->ref_group[TASK_GRP_KERNEL]);
407 	/* Just one static group and one dynamic group. */
408 	kfree_type(struct os_refgrp, 2, task->ref_group);
409 }
410 
411 static void
allocate_refgrp_full(task_t task)412 allocate_refgrp_full(task_t task)
413 {
414 	task->ref_group = kalloc_type(struct os_refgrp,
415 	    TASK_GRP_COUNT + DYNAMIC_COUNT, Z_WAITOK | Z_ZERO | Z_NOFAIL);
416 
417 	for (int i = 0; i < TASK_GRP_COUNT; i++) {
418 		task->ref_group[i] = (struct os_refgrp)
419 		    os_refgrp_initializer(local_name[i], task_refgrp[i],
420 		    OS_REFGRP_F_ALWAYS_ENABLED);
421 		os_ref_log_init(&task->ref_group[i]);
422 	}
423 }
424 
425 static void
free_refgrp_full(task_t task)426 free_refgrp_full(task_t task)
427 {
428 	for (int i = 0; i < TASK_GRP_COUNT; i++) {
429 		os_ref_log_fini(&task->ref_group[i]);
430 	}
431 	kfree_type(struct os_refgrp, TASK_GRP_COUNT + DYNAMIC_COUNT, task->ref_group);
432 }
433 
434 kern_return_t
task_ref_count_init(task_t task)435 task_ref_count_init(task_t task)
436 {
437 	assert(
438 		task_refgrp_config == TASK_REF_CONFIG_OFF ||
439 		task_refgrp_config == TASK_REF_CONFIG_DEFAULT ||
440 		task_refgrp_config == TASK_REF_CONFIG_FULL);
441 
442 	switch (task_refgrp_config) {
443 	case TASK_REF_CONFIG_OFF:
444 		os_ref_init_count(&task->ref_count, &task_primary_refgrp,
445 		    TASK_REF_COUNT_INITIAL);
446 		return KERN_SUCCESS;
447 
448 
449 	case TASK_REF_CONFIG_DEFAULT:
450 		allocate_refgrp_default(task);
451 		lck_spin_init(&task->ref_group_lock, &task_lck_grp, LCK_ATTR_NULL);
452 		os_ref_init_count(&task->ref_count, &task->ref_group[TASK_GRP_KERNEL],
453 		    TASK_REF_COUNT_INITIAL);
454 		return KERN_SUCCESS;
455 
456 	case TASK_REF_CONFIG_FULL:
457 		allocate_refgrp_full(task);
458 		lck_spin_init(&task->ref_group_lock, &task_lck_grp, LCK_ATTR_NULL);
459 
460 		os_ref_init_count_internal(&task->ref_count.ref_count,
461 		    &task->ref_group[TASK_GRP_KERNEL], 1);
462 
463 		task_reference_grp(task, TASK_GRP_INTERNAL);
464 
465 		return KERN_SUCCESS;
466 	}
467 }
468 
469 void
task_ref_count_fini(task_t task)470 task_ref_count_fini(task_t task)
471 {
472 	assert(
473 		task_refgrp_config == TASK_REF_CONFIG_OFF ||
474 		task_refgrp_config == TASK_REF_CONFIG_DEFAULT ||
475 		task_refgrp_config == TASK_REF_CONFIG_FULL);
476 
477 	switch (task_refgrp_config) {
478 	case TASK_REF_CONFIG_OFF:
479 		return;
480 
481 	case TASK_REF_CONFIG_DEFAULT:
482 		lck_spin_destroy(&task->ref_group_lock, &task_lck_grp);
483 		free_refgrp_default(task);
484 		return;
485 
486 	case TASK_REF_CONFIG_FULL:
487 		lck_spin_destroy(&task->ref_group_lock, &task_lck_grp);
488 		free_refgrp_full(task);
489 		return;
490 	}
491 }
492 
493 void
task_ref_init(void)494 task_ref_init(void)
495 {
496 	char config[16] = {0};
497 
498 	/* Allow task reference group logging to be configured. */
499 	(void) PE_parse_boot_arg_str("task_refgrp", config,
500 	    sizeof(config));
501 
502 	if (strncmp(config, "full", sizeof(config)) == 0) {
503 		task_refgrp_config = TASK_REF_CONFIG_FULL;
504 	}
505 	if (strncmp(config, "off", sizeof(config)) == 0) {
506 		task_refgrp_config = TASK_REF_CONFIG_OFF;
507 	}
508 
509 	if (task_refgrp_config == TASK_REF_CONFIG_OFF) {
510 		return;
511 	}
512 
513 	for (int i = 0; i < TASK_GRP_COUNT; i++) {
514 		os_ref_log_init(task_refgrp[i]);
515 	}
516 }
517 
518 #else /* DEVELOPMENT || DEBUG */
519 
520 kern_return_t
task_ref_count_init(task_t task)521 task_ref_count_init(task_t task)
522 {
523 	/* One ref for our caller, one for being alive. */
524 	os_ref_init_count(&task->ref_count, &task_primary_refgrp,
525 	    TASK_REF_COUNT_INITIAL);
526 	return KERN_SUCCESS;
527 }
528 
529 void
task_reference_grp(task_t task,task_grp_t grp)530 task_reference_grp(task_t task, __attribute__((__unused__)) task_grp_t grp)
531 {
532 	if (task == TASK_NULL) {
533 		return;
534 	}
535 
536 	task_require(task);
537 	os_ref_retain(&task->ref_count);
538 }
539 
540 void
task_deallocate_grp(task_t task,task_grp_t grp)541 task_deallocate_grp(task_t task, __attribute__((__unused__)) task_grp_t grp)
542 {
543 	if (task == TASK_NULL) {
544 		return;
545 	}
546 
547 	os_ref_count_t refs = os_ref_release(&task->ref_count);
548 	task_deallocate_internal(task, refs);
549 }
550 
551 void
task_reference_external(task_t task)552 task_reference_external(task_t task)
553 {
554 	task_reference_grp(task, 0);
555 }
556 
557 void
task_deallocate_external(task_t task)558 task_deallocate_external(task_t task)
559 {
560 	task_deallocate_grp(task, 0);
561 }
562 
563 void
task_ref_count_fini(task_t task)564 task_ref_count_fini(__attribute__((__unused__)) task_t task)
565 {
566 }
567 
568 void
task_ref_init(void)569 task_ref_init(void)
570 {
571 }
572 
573 #endif /* DEVELOPMENT || DEBUG */
574