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