xref: /xnu-8792.61.2/bsd/kern/sys_coalition.c (revision 42e220869062b56f8d7d0726fd4c88954f87902c)
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(proc_task(p), 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 		return ENOTSUP;
283 	}
284 	return error;
285 }
286 
287 static int
coalition_ledger_logical_writes_limit(coalition_t coal,user_addr_t buffer,user_size_t bufsize)288 coalition_ledger_logical_writes_limit(coalition_t coal, user_addr_t buffer, user_size_t bufsize)
289 {
290 	int error = 0;
291 	int64_t limit = 0;
292 
293 	if (coalition_type(coal) != COALITION_TYPE_RESOURCE) {
294 		error = EINVAL;
295 		goto out;
296 	}
297 	error = copyin(buffer, &limit, MIN(bufsize, sizeof(limit)));
298 	if (error) {
299 		goto out;
300 	}
301 
302 
303 	error = coalition_ledger_set_logical_writes_limit(coal, limit);
304 out:
305 	return error;
306 }
307 
308 int
coalition_info(proc_t p,struct coalition_info_args * uap,__unused int32_t * retval)309 coalition_info(proc_t p, struct coalition_info_args *uap, __unused int32_t *retval)
310 {
311 	user_addr_t cidp = uap->cid;
312 	user_addr_t buffer = uap->buffer;
313 	user_addr_t bufsizep = uap->bufsize;
314 	user_size_t bufsize;
315 	uint32_t flavor = uap->flavor;
316 	int error;
317 	uint64_t cid;
318 	coalition_t coal;
319 
320 	error = copyin(cidp, &cid, sizeof(cid));
321 	if (error) {
322 		return error;
323 	}
324 
325 	coal = coalition_find_by_id(cid);
326 	if (coal == COALITION_NULL) {
327 		return ESRCH;
328 	}
329 	/* TODO: priv check? EPERM or ESRCH? */
330 
331 	if (IS_64BIT_PROCESS(p)) {
332 		user64_size_t size64;
333 		error = copyin(bufsizep, &size64, sizeof(size64));
334 		bufsize = (user_size_t)size64;
335 	} else {
336 		user32_size_t size32;
337 		error = copyin(bufsizep, &size32, sizeof(size32));
338 		bufsize = (user_size_t)size32;
339 	}
340 	if (error) {
341 		goto bad;
342 	}
343 
344 	switch (flavor) {
345 	case COALITION_INFO_RESOURCE_USAGE:
346 		error = coalition_info_resource_usage(coal, buffer, bufsize);
347 		break;
348 	case COALITION_INFO_SET_NAME:
349 		error = coalition_info_set_name_internal(coal, buffer, bufsize);
350 		break;
351 	case COALITION_INFO_SET_EFFICIENCY:
352 		error = coalition_info_efficiency(coal, buffer, bufsize);
353 		break;
354 	default:
355 		error = EINVAL;
356 	}
357 
358 bad:
359 	coalition_release(coal);
360 	return error;
361 }
362 
363 int
coalition_ledger(__unused proc_t p,__unused struct coalition_ledger_args * uap,__unused int32_t * retval)364 coalition_ledger(__unused proc_t p, __unused struct coalition_ledger_args *uap, __unused int32_t *retval)
365 {
366 	user_addr_t cidp = uap->cid;
367 	user_addr_t buffer = uap->buffer;
368 	user_addr_t bufsizep = uap->bufsize;
369 	user_size_t bufsize;
370 	uint32_t operation = uap->operation;
371 	int error;
372 	uint64_t cid;
373 	coalition_t coal = COALITION_NULL;
374 
375 	if (!kauth_cred_issuser(kauth_cred_get())) {
376 		error = EPERM;
377 		goto out;
378 	}
379 
380 	error = copyin(cidp, &cid, sizeof(cid));
381 	if (error) {
382 		goto out;
383 	}
384 
385 	coal = coalition_find_by_id(cid);
386 	if (coal == COALITION_NULL) {
387 		error = ESRCH;
388 		goto out;
389 	}
390 
391 	if (IS_64BIT_PROCESS(p)) {
392 		user64_size_t size64;
393 		error = copyin(bufsizep, &size64, sizeof(size64));
394 		bufsize = (user_size_t)size64;
395 	} else {
396 		user32_size_t size32;
397 		error = copyin(bufsizep, &size32, sizeof(size32));
398 		bufsize = (user_size_t)size32;
399 	}
400 	if (error) {
401 		goto out;
402 	}
403 
404 	switch (operation) {
405 	case COALITION_LEDGER_SET_LOGICAL_WRITES_LIMIT:
406 		error = coalition_ledger_logical_writes_limit(coal, buffer, bufsize);
407 		break;
408 	default:
409 		error = EINVAL;
410 	}
411 out:
412 	if (coal != COALITION_NULL) {
413 		coalition_release(coal);
414 	}
415 	return error;
416 }
417 #if DEVELOPMENT || DEBUG
418 static int sysctl_coalition_get_ids SYSCTL_HANDLER_ARGS
419 {
420 #pragma unused(oidp, arg1, arg2)
421 	int error, pid;
422 	proc_t tproc;
423 	uint64_t value;
424 	uint64_t ids[COALITION_NUM_TYPES] = {};
425 
426 
427 	error = SYSCTL_IN(req, &value, sizeof(value));
428 	if (error) {
429 		return error;
430 	}
431 	if (!req->newptr) {
432 		pid = proc_getpid(req->p);
433 	} else {
434 		pid = (int)value;
435 	}
436 
437 	coal_dbg("looking up coalitions for pid:%d", pid);
438 	tproc = proc_find(pid);
439 	if (tproc == NULL) {
440 		coal_dbg("ERROR: Couldn't find pid:%d", pid);
441 		return ESRCH;
442 	}
443 
444 	task_coalition_ids(proc_task(tproc), ids);
445 	proc_rele(tproc);
446 
447 	return SYSCTL_OUT(req, ids, sizeof(ids));
448 }
449 
450 SYSCTL_PROC(_kern, OID_AUTO, coalitions, CTLTYPE_QUAD | CTLFLAG_RW | CTLFLAG_LOCKED,
451     0, 0, sysctl_coalition_get_ids, "Q", "coalition ids of a given process");
452 
453 
454 static int sysctl_coalition_get_roles SYSCTL_HANDLER_ARGS
455 {
456 #pragma unused(oidp, arg1, arg2)
457 	int error, pid;
458 	proc_t tproc;
459 	int value;
460 	int roles[COALITION_NUM_TYPES] = {};
461 
462 
463 	error = SYSCTL_IN(req, &value, sizeof(value));
464 	if (error) {
465 		return error;
466 	}
467 	if (!req->newptr) {
468 		pid = proc_getpid(req->p);
469 	} else {
470 		pid = (int)value;
471 	}
472 
473 	coal_dbg("looking up coalitions for pid:%d", pid);
474 	tproc = proc_find(pid);
475 	if (tproc == NULL) {
476 		coal_dbg("ERROR: Couldn't find pid:%d", pid);
477 		return ESRCH;
478 	}
479 
480 	task_coalition_roles(proc_task(tproc), roles);
481 	proc_rele(tproc);
482 
483 	return SYSCTL_OUT(req, roles, sizeof(roles));
484 }
485 
486 SYSCTL_PROC(_kern, OID_AUTO, coalition_roles, CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_LOCKED,
487     0, 0, sysctl_coalition_get_roles, "I", "coalition roles of a given process");
488 
489 
490 static int sysctl_coalition_get_page_count SYSCTL_HANDLER_ARGS
491 {
492 #pragma unused(oidp, arg1, arg2)
493 	int error, pid;
494 	proc_t tproc;
495 	coalition_t coal;
496 	uint64_t value;
497 	uint64_t pgcount[COALITION_NUM_TYPES];
498 
499 
500 	error = SYSCTL_IN(req, &value, sizeof(value));
501 	if (error) {
502 		return error;
503 	}
504 	if (!req->newptr) {
505 		pid = proc_getpid(req->p);
506 	} else {
507 		pid = (int)value;
508 	}
509 
510 	coal_dbg("looking up coalitions for pid:%d", pid);
511 	tproc = proc_find(pid);
512 	if (tproc == NULL) {
513 		coal_dbg("ERROR: Couldn't find pid:%d", pid);
514 		return ESRCH;
515 	}
516 
517 	memset(pgcount, 0, sizeof(pgcount));
518 
519 	for (int t = 0; t < COALITION_NUM_TYPES; t++) {
520 		coal = task_get_coalition(proc_task(tproc), t);
521 		if (coal != COALITION_NULL) {
522 			int ntasks = 0;
523 			pgcount[t] = coalition_get_page_count(coal, &ntasks);
524 			coal_dbg("PID:%d, Coalition:%lld, type:%d, pgcount:%lld",
525 			    pid, coalition_id(coal), t, pgcount[t]);
526 		}
527 	}
528 
529 	proc_rele(tproc);
530 
531 	return SYSCTL_OUT(req, pgcount, sizeof(pgcount));
532 }
533 
534 SYSCTL_PROC(_kern, OID_AUTO, coalition_page_count, CTLTYPE_QUAD | CTLFLAG_RW | CTLFLAG_LOCKED,
535     0, 0, sysctl_coalition_get_page_count, "Q", "coalition page count of a specified process");
536 
537 
538 static int sysctl_coalition_get_pid_list SYSCTL_HANDLER_ARGS
539 {
540 #pragma unused(oidp, arg1, arg2)
541 	int error, type, sort_order, pid;
542 	int value[3];
543 	int has_pid = 1;
544 
545 	coalition_t coal = COALITION_NULL;
546 	proc_t tproc = PROC_NULL;
547 	int npids = 0;
548 	int pidlist[100] = { 0, };
549 
550 
551 	error = SYSCTL_IN(req, &value, sizeof(value));
552 	if (error) {
553 		has_pid = 0;
554 		error = SYSCTL_IN(req, &value, sizeof(value) - sizeof(value[0]));
555 	}
556 	if (error) {
557 		return error;
558 	}
559 	if (!req->newptr) {
560 		type = COALITION_TYPE_RESOURCE;
561 		sort_order = COALITION_SORT_DEFAULT;
562 		pid = proc_getpid(req->p);
563 	} else {
564 		type = value[0];
565 		sort_order = value[1];
566 		if (has_pid) {
567 			pid = value[2];
568 		} else {
569 			pid = proc_getpid(req->p);
570 		}
571 	}
572 
573 	if (type < 0 || type >= COALITION_NUM_TYPES) {
574 		return EINVAL;
575 	}
576 
577 	coal_dbg("getting constituent PIDS for coalition of type %d "
578 	    "containing pid:%d (sort:%d)", type, pid, sort_order);
579 	tproc = proc_find(pid);
580 	if (tproc == NULL) {
581 		coal_dbg("ERROR: Couldn't find pid:%d", pid);
582 		return ESRCH;
583 	}
584 
585 	coal = task_get_coalition(proc_task(tproc), type);
586 	if (coal == COALITION_NULL) {
587 		goto out;
588 	}
589 
590 	npids = coalition_get_pid_list(coal, COALITION_ROLEMASK_ALLROLES, sort_order,
591 	    pidlist, sizeof(pidlist) / sizeof(pidlist[0]));
592 	if (npids > (int)(sizeof(pidlist) / sizeof(pidlist[0]))) {
593 		coal_dbg("Too many members in coalition %llu (from pid:%d): %d!",
594 		    coalition_id(coal), pid, npids);
595 		npids = sizeof(pidlist) / sizeof(pidlist[0]);
596 	}
597 
598 out:
599 	proc_rele(tproc);
600 
601 	if (npids < 0) {
602 		/* npids is a negative errno */
603 		return -npids;
604 	}
605 
606 	if (npids == 0) {
607 		return ENOENT;
608 	}
609 
610 	return SYSCTL_OUT(req, pidlist, sizeof(pidlist[0]) * npids);
611 }
612 
613 SYSCTL_PROC(_kern, OID_AUTO, coalition_pid_list, CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_LOCKED,
614     0, 0, sysctl_coalition_get_pid_list, "I", "list of PIDS which are members of the coalition of the current process");
615 
616 #if DEVELOPMENT
617 static int sysctl_coalition_notify SYSCTL_HANDLER_ARGS
618 {
619 #pragma unused(oidp, arg1, arg2)
620 	int error, should_set;
621 	coalition_t coal;
622 	uint64_t value[2];
623 
624 	should_set = 1;
625 	error = SYSCTL_IN(req, value, sizeof(value));
626 	if (error) {
627 		error = SYSCTL_IN(req, value, sizeof(value) - sizeof(value[0]));
628 		if (error) {
629 			return error;
630 		}
631 		should_set = 0;
632 	}
633 	if (!req->newptr) {
634 		return error;
635 	}
636 
637 	coal = coalition_find_by_id(value[0]);
638 	if (coal == COALITION_NULL) {
639 		coal_dbg("Can't find coalition with ID:%lld", value[0]);
640 		return ESRCH;
641 	}
642 
643 	if (should_set) {
644 		coalition_set_notify(coal, (int)value[1]);
645 	}
646 
647 	value[0] = (uint64_t)coalition_should_notify(coal);
648 
649 	coalition_release(coal);
650 
651 	return SYSCTL_OUT(req, value, sizeof(value[0]));
652 }
653 
654 SYSCTL_PROC(_kern, OID_AUTO, coalition_notify, CTLTYPE_QUAD | CTLFLAG_RW | CTLFLAG_LOCKED,
655     0, 0, sysctl_coalition_notify, "Q", "get/set coalition notification flag");
656 
657 extern int unrestrict_coalition_syscalls;
658 SYSCTL_INT(_kern, OID_AUTO, unrestrict_coalitions,
659     CTLFLAG_RW, &unrestrict_coalition_syscalls, 0,
660     "unrestrict the coalition interface");
661 
662 #endif /* DEVELOPMENT */
663 
664 #endif /* DEVELOPMENT || DEBUG */
665