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 */
28static
29int
30coalition_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, out: &coal, cid: &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));
57out:
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 */
74static
75int
76coalition_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(coal_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 */
138static
139int
140coalition_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(coal_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 */
189int
190coalition(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(task: 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. */
219static int __attribute__ ((noinline))
220coalition_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_out: &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
242static int __attribute__ ((noinline))
243coalition_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
259static int
260coalition_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(s: name, n: 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
282static int
283coalition_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
304static int
305coalition_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);
321out:
322 return error;
323}
324
325int
326coalition_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(coal_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
380bad:
381 coalition_release(coal);
382 return error;
383}
384
385int
386coalition_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(cred: 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(coal_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 }
433out:
434 if (coal != COALITION_NULL) {
435 coalition_release(coal);
436 }
437 return error;
438}
439#if DEVELOPMENT || DEBUG
440static 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
472SYSCTL_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
476static 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
508SYSCTL_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
512static 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
556SYSCTL_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
560static 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
620out:
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
635SYSCTL_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
639static 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
676SYSCTL_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
679extern int unrestrict_coalition_syscalls;
680SYSCTL_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