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