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