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