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