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