xref: /xnu-10002.61.3/bsd/kern/sys_coalition.c (revision 0f4c859e951fba394238ab619495c4e1d54d0f34)
1 #include <kern/kern_types.h>
2 #include <kern/thread_group.h>
3 #include <mach/mach_types.h>
4 #include <mach/boolean.h>
5 
6 #include <kern/coalition.h>
7 
8 #include <sys/coalition.h>
9 #include <sys/errno.h>
10 #include <sys/kauth.h>
11 #include <sys/kernel.h>
12 #include <sys/sysproto.h>
13 #include <sys/systm.h>
14 #include <sys/ubc.h> /* mach_to_bsd_errno */
15 
16 /* Coalitions syscalls */
17 
18 /*
19  * Create a new, empty coalition and return its ID.
20  *
21  * Returns:
22  * EINVAL	Flags parameter was invalid
23  * ENOMEM	Unable to allocate kernel resources for a new coalition
24  * EFAULT	cidp parameter pointed to invalid memory.
25  *
26  * Returns with reference held for userspace caller.
27  */
28 static
29 int
coalition_create_syscall(user_addr_t cidp,uint32_t flags)30 coalition_create_syscall(user_addr_t cidp, uint32_t flags)
31 {
32 	int error = 0;
33 	kern_return_t kr;
34 	uint64_t cid;
35 	coalition_t coal;
36 	int type = COALITION_CREATE_FLAGS_GET_TYPE(flags);
37 	int role = COALITION_CREATE_FLAGS_GET_ROLE(flags);
38 	boolean_t privileged = !!(flags & COALITION_CREATE_FLAGS_PRIVILEGED);
39 	boolean_t efficient = !!(flags & COALITION_CREATE_FLAGS_EFFICIENT);
40 
41 	if ((flags & (~COALITION_CREATE_FLAGS_MASK)) != 0) {
42 		return EINVAL;
43 	}
44 	if (type < 0 || type > COALITION_TYPE_MAX) {
45 		return EINVAL;
46 	}
47 
48 	kr = coalition_create_internal(type, role, privileged, efficient, &coal, &cid);
49 	if (kr != KERN_SUCCESS) {
50 		/* for now, the only kr is KERN_RESOURCE_SHORTAGE */
51 		error = ENOMEM;
52 		goto out;
53 	}
54 
55 	coal_dbg("(addr, %u) -> %llu", flags, cid);
56 	error = copyout(&cid, cidp, sizeof(cid));
57 out:
58 	return error;
59 }
60 
61 /*
62  * Request to terminate the coalition identified by ID.
63  * Attempts to spawn into this coalition using the posix_spawnattr will begin
64  * failing. Processes already within the coalition may still fork.
65  * Arms the 'coalition is empty' notification when the coalition's active
66  * count reaches zero.
67  *
68  * Returns:
69  * ESRCH	No coalition with that ID could be found.
70  * EALREADY	The coalition with that ID has already been terminated.
71  * EFAULT	cidp parameter pointed to invalid memory.
72  * EPERM	Caller doesn't have permission to terminate that coalition.
73  */
74 static
75 int
coalition_request_terminate_syscall(user_addr_t cidp,uint32_t flags)76 coalition_request_terminate_syscall(user_addr_t cidp, uint32_t flags)
77 {
78 	kern_return_t kr;
79 	int error = 0;
80 	uint64_t cid;
81 	coalition_t coal;
82 
83 	if (flags != 0) {
84 		return EINVAL;
85 	}
86 
87 	error = copyin(cidp, &cid, sizeof(cid));
88 	if (error) {
89 		return error;
90 	}
91 
92 	coal = coalition_find_by_id(cid);
93 	if (coal == COALITION_NULL) {
94 		return ESRCH;
95 	}
96 
97 	kr = coalition_request_terminate_internal(coal);
98 	coalition_release(coal);
99 
100 	switch (kr) {
101 	case KERN_SUCCESS:
102 		break;
103 	case KERN_DEFAULT_SET:
104 		error = EPERM;
105 		break;
106 	case KERN_TERMINATED:
107 		error = EALREADY;
108 		break;
109 	case KERN_INVALID_NAME:
110 		error = ESRCH;
111 		break;
112 	default:
113 		error = EIO;
114 		break;
115 	}
116 
117 	coal_dbg("(%llu, %u) -> %d", cid, flags, error);
118 
119 	return error;
120 }
121 
122 /*
123  * Request the kernel to deallocate the coalition identified by ID, which
124  * must be both terminated and empty. This balances the reference taken
125  * in coalition_create.
126  * The memory containing the coalition object may not be freed just yet, if
127  * other kernel operations still hold references to it.
128  *
129  * Returns:
130  * EINVAL	Flags parameter was invalid
131  * ESRCH	Coalition ID refers to a coalition that doesn't exist.
132  * EBUSY	Coalition has not yet been terminated.
133  * EBUSY	Coalition is still active.
134  * EFAULT	cidp parameter pointed to invalid memory.
135  * EPERM	Caller doesn't have permission to terminate that coalition.
136  * Consumes one reference, "held" by caller since coalition_create
137  */
138 static
139 int
coalition_reap_syscall(user_addr_t cidp,uint32_t flags)140 coalition_reap_syscall(user_addr_t cidp, uint32_t flags)
141 {
142 	kern_return_t kr;
143 	int error = 0;
144 	uint64_t cid;
145 	coalition_t coal;
146 
147 	if (flags != 0) {
148 		return EINVAL;
149 	}
150 
151 	error = copyin(cidp, &cid, sizeof(cid));
152 	if (error) {
153 		return error;
154 	}
155 
156 	coal = coalition_find_by_id(cid);
157 	if (coal == COALITION_NULL) {
158 		return ESRCH;
159 	}
160 
161 	kr = coalition_reap_internal(coal);
162 	coalition_release(coal);
163 
164 	switch (kr) {
165 	case KERN_SUCCESS:
166 		break;
167 	case KERN_DEFAULT_SET:
168 		error = EPERM;
169 		break;
170 	case KERN_TERMINATED:
171 		error = ESRCH;
172 		break;
173 	case KERN_FAILURE:
174 		error = EBUSY;
175 		break;
176 	default:
177 		error = EIO;
178 		break;
179 	}
180 
181 	coal_dbg("(%llu, %u) -> %d", cid, flags, error);
182 
183 	return error;
184 }
185 
186 /* Syscall demux.
187  * Returns EPERM if the calling process is not privileged to make this call.
188  */
189 int
coalition(proc_t p,struct coalition_args * cap,__unused int32_t * retval)190 coalition(proc_t p, struct coalition_args *cap, __unused int32_t *retval)
191 {
192 	uint32_t operation = cap->operation;
193 	user_addr_t cidp = cap->cid;
194 	uint32_t flags = cap->flags;
195 	int error = 0;
196 	int type = COALITION_CREATE_FLAGS_GET_TYPE(flags);
197 
198 	if (!task_is_in_privileged_coalition(proc_task(p), type)) {
199 		return EPERM;
200 	}
201 
202 	switch (operation) {
203 	case COALITION_OP_CREATE:
204 		error = coalition_create_syscall(cidp, flags);
205 		break;
206 	case COALITION_OP_REAP:
207 		error = coalition_reap_syscall(cidp, flags);
208 		break;
209 	case COALITION_OP_TERMINATE:
210 		error = coalition_request_terminate_syscall(cidp, flags);
211 		break;
212 	default:
213 		error = ENOSYS;
214 	}
215 	return error;
216 }
217 
218 /* This is a temporary interface, likely to be changed by 15385642. */
219 static int __attribute__ ((noinline))
coalition_info_resource_usage(coalition_t coal,user_addr_t buffer,user_size_t bufsize)220 coalition_info_resource_usage(coalition_t coal, user_addr_t buffer, user_size_t bufsize)
221 {
222 	kern_return_t kr;
223 	struct coalition_resource_usage cru = {};
224 
225 	kr = coalition_resource_usage_internal(coal, &cru);
226 
227 	switch (kr) {
228 	case KERN_INVALID_ARGUMENT:
229 		return EINVAL;
230 	case KERN_RESOURCE_SHORTAGE:
231 		return ENOMEM;
232 	case KERN_SUCCESS:
233 		break;
234 	default:
235 		return EIO; /* shrug */
236 	}
237 
238 	return copyout(&cru, buffer, MIN(bufsize, sizeof(cru)));
239 }
240 
241 #if DEVELOPMENT || DEBUG
242 static int __attribute__ ((noinline))
coalition_info_get_debug_info(coalition_t coal,user_addr_t buffer,user_size_t bufsize)243 coalition_info_get_debug_info(coalition_t coal, user_addr_t buffer, user_size_t bufsize)
244 {
245 	kern_return_t kr;
246 	struct coalinfo_debuginfo c_debuginfo = {};
247 
248 	kr = coalition_debug_info_internal(coal, &c_debuginfo);
249 
250 	if (kr != KERN_SUCCESS) {
251 		return mach_to_bsd_errno(kr);
252 	}
253 
254 	return copyout(&c_debuginfo, buffer, MIN(bufsize, sizeof(c_debuginfo)));
255 }
256 #endif /* DEVELOPMENT || DEBUG */
257 
258 #if CONFIG_THREAD_GROUPS
259 static int
coalition_info_set_name_internal(coalition_t coal,user_addr_t buffer,user_size_t bufsize)260 coalition_info_set_name_internal(coalition_t coal, user_addr_t buffer, user_size_t bufsize)
261 {
262 	int error;
263 	char name[THREAD_GROUP_MAXNAME];
264 
265 	if (coalition_type(coal) != COALITION_TYPE_JETSAM) {
266 		return EINVAL;
267 	}
268 	bzero(name, sizeof(name));
269 	error = copyin(buffer, name, MIN(bufsize, sizeof(name) - 1));
270 	if (error) {
271 		return error;
272 	}
273 	struct thread_group *tg = coalition_get_thread_group(coal);
274 	thread_group_set_name(tg, name);
275 	return error;
276 }
277 
278 #else /* CONFIG_THREAD_GROUPS */
279 #define coalition_info_set_name_internal(...) 0
280 #endif /* CONFIG_THREAD_GROUPS */
281 
282 static int
coalition_info_efficiency(coalition_t coal,user_addr_t buffer,user_size_t bufsize)283 coalition_info_efficiency(coalition_t coal, user_addr_t buffer, user_size_t bufsize)
284 {
285 	int error = 0;
286 	if (coalition_type(coal) != COALITION_TYPE_JETSAM) {
287 		return EINVAL;
288 	}
289 	uint64_t flags = 0;
290 	error = copyin(buffer, &flags, MIN(bufsize, sizeof(flags)));
291 	if (error) {
292 		return error;
293 	}
294 	if ((flags & COALITION_EFFICIENCY_VALID_FLAGS) == 0) {
295 		return EINVAL;
296 	}
297 	if (flags & COALITION_FLAGS_EFFICIENT) {
298 		// No longer supported; this flag must be set during create.
299 		return ENOTSUP;
300 	}
301 	return error;
302 }
303 
304 static int
coalition_ledger_logical_writes_limit(coalition_t coal,user_addr_t buffer,user_size_t bufsize)305 coalition_ledger_logical_writes_limit(coalition_t coal, user_addr_t buffer, user_size_t bufsize)
306 {
307 	int error = 0;
308 	int64_t limit = 0;
309 
310 	if (coalition_type(coal) != COALITION_TYPE_RESOURCE) {
311 		error = EINVAL;
312 		goto out;
313 	}
314 	error = copyin(buffer, &limit, MIN(bufsize, sizeof(limit)));
315 	if (error) {
316 		goto out;
317 	}
318 
319 
320 	error = coalition_ledger_set_logical_writes_limit(coal, limit);
321 out:
322 	return error;
323 }
324 
325 int
coalition_info(proc_t p,struct coalition_info_args * uap,__unused int32_t * retval)326 coalition_info(proc_t p, struct coalition_info_args *uap, __unused int32_t *retval)
327 {
328 	user_addr_t cidp = uap->cid;
329 	user_addr_t buffer = uap->buffer;
330 	user_addr_t bufsizep = uap->bufsize;
331 	user_size_t bufsize;
332 	uint32_t flavor = uap->flavor;
333 	int error;
334 	uint64_t cid;
335 	coalition_t coal;
336 
337 	error = copyin(cidp, &cid, sizeof(cid));
338 	if (error) {
339 		return error;
340 	}
341 
342 	coal = coalition_find_by_id(cid);
343 	if (coal == COALITION_NULL) {
344 		return ESRCH;
345 	}
346 	/* TODO: priv check? EPERM or ESRCH? */
347 
348 	if (IS_64BIT_PROCESS(p)) {
349 		user64_size_t size64;
350 		error = copyin(bufsizep, &size64, sizeof(size64));
351 		bufsize = (user_size_t)size64;
352 	} else {
353 		user32_size_t size32;
354 		error = copyin(bufsizep, &size32, sizeof(size32));
355 		bufsize = (user_size_t)size32;
356 	}
357 	if (error) {
358 		goto bad;
359 	}
360 
361 	switch (flavor) {
362 	case COALITION_INFO_RESOURCE_USAGE:
363 		error = coalition_info_resource_usage(coal, buffer, bufsize);
364 		break;
365 	case COALITION_INFO_SET_NAME:
366 		error = coalition_info_set_name_internal(coal, buffer, bufsize);
367 		break;
368 	case COALITION_INFO_SET_EFFICIENCY:
369 		error = coalition_info_efficiency(coal, buffer, bufsize);
370 		break;
371 #if DEVELOPMENT || DEBUG
372 	case COALITION_INFO_GET_DEBUG_INFO:
373 		error = coalition_info_get_debug_info(coal, buffer, bufsize);
374 		break;
375 #endif /* DEVELOPMENT || DEBUG */
376 	default:
377 		error = EINVAL;
378 	}
379 
380 bad:
381 	coalition_release(coal);
382 	return error;
383 }
384 
385 int
coalition_ledger(__unused proc_t p,__unused struct coalition_ledger_args * uap,__unused int32_t * retval)386 coalition_ledger(__unused proc_t p, __unused struct coalition_ledger_args *uap, __unused int32_t *retval)
387 {
388 	user_addr_t cidp = uap->cid;
389 	user_addr_t buffer = uap->buffer;
390 	user_addr_t bufsizep = uap->bufsize;
391 	user_size_t bufsize;
392 	uint32_t operation = uap->operation;
393 	int error;
394 	uint64_t cid;
395 	coalition_t coal = COALITION_NULL;
396 
397 	if (!kauth_cred_issuser(kauth_cred_get())) {
398 		error = EPERM;
399 		goto out;
400 	}
401 
402 	error = copyin(cidp, &cid, sizeof(cid));
403 	if (error) {
404 		goto out;
405 	}
406 
407 	coal = coalition_find_by_id(cid);
408 	if (coal == COALITION_NULL) {
409 		error = ESRCH;
410 		goto out;
411 	}
412 
413 	if (IS_64BIT_PROCESS(p)) {
414 		user64_size_t size64;
415 		error = copyin(bufsizep, &size64, sizeof(size64));
416 		bufsize = (user_size_t)size64;
417 	} else {
418 		user32_size_t size32;
419 		error = copyin(bufsizep, &size32, sizeof(size32));
420 		bufsize = (user_size_t)size32;
421 	}
422 	if (error) {
423 		goto out;
424 	}
425 
426 	switch (operation) {
427 	case COALITION_LEDGER_SET_LOGICAL_WRITES_LIMIT:
428 		error = coalition_ledger_logical_writes_limit(coal, buffer, bufsize);
429 		break;
430 	default:
431 		error = EINVAL;
432 	}
433 out:
434 	if (coal != COALITION_NULL) {
435 		coalition_release(coal);
436 	}
437 	return error;
438 }
439 #if DEVELOPMENT || DEBUG
440 static int sysctl_coalition_get_ids SYSCTL_HANDLER_ARGS
441 {
442 #pragma unused(oidp, arg1, arg2)
443 	int error, pid;
444 	proc_t tproc;
445 	uint64_t value;
446 	uint64_t ids[COALITION_NUM_TYPES] = {};
447 
448 
449 	error = SYSCTL_IN(req, &value, sizeof(value));
450 	if (error) {
451 		return error;
452 	}
453 	if (!req->newptr) {
454 		pid = proc_getpid(req->p);
455 	} else {
456 		pid = (int)value;
457 	}
458 
459 	coal_dbg("looking up coalitions for pid:%d", pid);
460 	tproc = proc_find(pid);
461 	if (tproc == NULL) {
462 		coal_dbg("ERROR: Couldn't find pid:%d", pid);
463 		return ESRCH;
464 	}
465 
466 	task_coalition_ids(proc_task(tproc), ids);
467 	proc_rele(tproc);
468 
469 	return SYSCTL_OUT(req, ids, sizeof(ids));
470 }
471 
472 SYSCTL_PROC(_kern, OID_AUTO, coalitions, CTLTYPE_QUAD | CTLFLAG_RW | CTLFLAG_LOCKED,
473     0, 0, sysctl_coalition_get_ids, "Q", "coalition ids of a given process");
474 
475 
476 static int sysctl_coalition_get_roles SYSCTL_HANDLER_ARGS
477 {
478 #pragma unused(oidp, arg1, arg2)
479 	int error, pid;
480 	proc_t tproc;
481 	int value;
482 	int roles[COALITION_NUM_TYPES] = {};
483 
484 
485 	error = SYSCTL_IN(req, &value, sizeof(value));
486 	if (error) {
487 		return error;
488 	}
489 	if (!req->newptr) {
490 		pid = proc_getpid(req->p);
491 	} else {
492 		pid = (int)value;
493 	}
494 
495 	coal_dbg("looking up coalitions for pid:%d", pid);
496 	tproc = proc_find(pid);
497 	if (tproc == NULL) {
498 		coal_dbg("ERROR: Couldn't find pid:%d", pid);
499 		return ESRCH;
500 	}
501 
502 	task_coalition_roles(proc_task(tproc), roles);
503 	proc_rele(tproc);
504 
505 	return SYSCTL_OUT(req, roles, sizeof(roles));
506 }
507 
508 SYSCTL_PROC(_kern, OID_AUTO, coalition_roles, CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_LOCKED,
509     0, 0, sysctl_coalition_get_roles, "I", "coalition roles of a given process");
510 
511 
512 static int sysctl_coalition_get_page_count SYSCTL_HANDLER_ARGS
513 {
514 #pragma unused(oidp, arg1, arg2)
515 	int error, pid;
516 	proc_t tproc;
517 	coalition_t coal;
518 	uint64_t value;
519 	uint64_t pgcount[COALITION_NUM_TYPES];
520 
521 
522 	error = SYSCTL_IN(req, &value, sizeof(value));
523 	if (error) {
524 		return error;
525 	}
526 	if (!req->newptr) {
527 		pid = proc_getpid(req->p);
528 	} else {
529 		pid = (int)value;
530 	}
531 
532 	coal_dbg("looking up coalitions for pid:%d", pid);
533 	tproc = proc_find(pid);
534 	if (tproc == NULL) {
535 		coal_dbg("ERROR: Couldn't find pid:%d", pid);
536 		return ESRCH;
537 	}
538 
539 	memset(pgcount, 0, sizeof(pgcount));
540 
541 	for (int t = 0; t < COALITION_NUM_TYPES; t++) {
542 		coal = task_get_coalition(proc_task(tproc), t);
543 		if (coal != COALITION_NULL) {
544 			int ntasks = 0;
545 			pgcount[t] = coalition_get_page_count(coal, &ntasks);
546 			coal_dbg("PID:%d, Coalition:%lld, type:%d, pgcount:%lld",
547 			    pid, coalition_id(coal), t, pgcount[t]);
548 		}
549 	}
550 
551 	proc_rele(tproc);
552 
553 	return SYSCTL_OUT(req, pgcount, sizeof(pgcount));
554 }
555 
556 SYSCTL_PROC(_kern, OID_AUTO, coalition_page_count, CTLTYPE_QUAD | CTLFLAG_RW | CTLFLAG_LOCKED,
557     0, 0, sysctl_coalition_get_page_count, "Q", "coalition page count of a specified process");
558 
559 
560 static int sysctl_coalition_get_pid_list SYSCTL_HANDLER_ARGS
561 {
562 #pragma unused(oidp, arg1, arg2)
563 	int error, type, sort_order, pid;
564 	int value[3];
565 	int has_pid = 1;
566 
567 	coalition_t coal = COALITION_NULL;
568 	proc_t tproc = PROC_NULL;
569 	int npids = 0;
570 	int pidlist[100] = { 0, };
571 
572 
573 	error = SYSCTL_IN(req, &value, sizeof(value));
574 	if (error) {
575 		has_pid = 0;
576 		error = SYSCTL_IN(req, &value, sizeof(value) - sizeof(value[0]));
577 	}
578 	if (error) {
579 		return error;
580 	}
581 	if (!req->newptr) {
582 		type = COALITION_TYPE_RESOURCE;
583 		sort_order = COALITION_SORT_DEFAULT;
584 		pid = proc_getpid(req->p);
585 	} else {
586 		type = value[0];
587 		sort_order = value[1];
588 		if (has_pid) {
589 			pid = value[2];
590 		} else {
591 			pid = proc_getpid(req->p);
592 		}
593 	}
594 
595 	if (type < 0 || type >= COALITION_NUM_TYPES) {
596 		return EINVAL;
597 	}
598 
599 	coal_dbg("getting constituent PIDS for coalition of type %d "
600 	    "containing pid:%d (sort:%d)", type, pid, sort_order);
601 	tproc = proc_find(pid);
602 	if (tproc == NULL) {
603 		coal_dbg("ERROR: Couldn't find pid:%d", pid);
604 		return ESRCH;
605 	}
606 
607 	coal = task_get_coalition(proc_task(tproc), type);
608 	if (coal == COALITION_NULL) {
609 		goto out;
610 	}
611 
612 	npids = coalition_get_pid_list(coal, COALITION_ROLEMASK_ALLROLES, sort_order,
613 	    pidlist, sizeof(pidlist) / sizeof(pidlist[0]));
614 	if (npids > (int)(sizeof(pidlist) / sizeof(pidlist[0]))) {
615 		coal_dbg("Too many members in coalition %llu (from pid:%d): %d!",
616 		    coalition_id(coal), pid, npids);
617 		npids = sizeof(pidlist) / sizeof(pidlist[0]);
618 	}
619 
620 out:
621 	proc_rele(tproc);
622 
623 	if (npids < 0) {
624 		/* npids is a negative errno */
625 		return -npids;
626 	}
627 
628 	if (npids == 0) {
629 		return ENOENT;
630 	}
631 
632 	return SYSCTL_OUT(req, pidlist, sizeof(pidlist[0]) * npids);
633 }
634 
635 SYSCTL_PROC(_kern, OID_AUTO, coalition_pid_list, CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_LOCKED,
636     0, 0, sysctl_coalition_get_pid_list, "I", "list of PIDS which are members of the coalition of the current process");
637 
638 #if DEVELOPMENT
639 static int sysctl_coalition_notify SYSCTL_HANDLER_ARGS
640 {
641 #pragma unused(oidp, arg1, arg2)
642 	int error, should_set;
643 	coalition_t coal;
644 	uint64_t value[2];
645 
646 	should_set = 1;
647 	error = SYSCTL_IN(req, value, sizeof(value));
648 	if (error) {
649 		error = SYSCTL_IN(req, value, sizeof(value) - sizeof(value[0]));
650 		if (error) {
651 			return error;
652 		}
653 		should_set = 0;
654 	}
655 	if (!req->newptr) {
656 		return error;
657 	}
658 
659 	coal = coalition_find_by_id(value[0]);
660 	if (coal == COALITION_NULL) {
661 		coal_dbg("Can't find coalition with ID:%lld", value[0]);
662 		return ESRCH;
663 	}
664 
665 	if (should_set) {
666 		coalition_set_notify(coal, (int)value[1]);
667 	}
668 
669 	value[0] = (uint64_t)coalition_should_notify(coal);
670 
671 	coalition_release(coal);
672 
673 	return SYSCTL_OUT(req, value, sizeof(value[0]));
674 }
675 
676 SYSCTL_PROC(_kern, OID_AUTO, coalition_notify, CTLTYPE_QUAD | CTLFLAG_RW | CTLFLAG_LOCKED,
677     0, 0, sysctl_coalition_notify, "Q", "get/set coalition notification flag");
678 
679 extern int unrestrict_coalition_syscalls;
680 SYSCTL_INT(_kern, OID_AUTO, unrestrict_coalitions,
681     CTLFLAG_RW, &unrestrict_coalition_syscalls, 0,
682     "unrestrict the coalition interface");
683 
684 #endif /* DEVELOPMENT */
685 
686 #endif /* DEVELOPMENT || DEBUG */
687