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