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 */
25static
26int
27coalition_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
34	if ((flags & (~COALITION_CREATE_FLAG_MASK)) != 0) {
35		return EINVAL;
36	}
37
38	boolean_t privileged = flags & COALITION_CREATE_FLAG_PRIVILEGED;
39
40	kr = coalition_create_internal(&coal, privileged);
41	if (kr != KERN_SUCCESS) {
42		/* for now, the only kr is KERN_RESOURCE_SHORTAGE */
43		error = ENOMEM;
44		goto out;
45	}
46
47	cid = coalition_id(coal);
48
49#if COALITION_DEBUG
50	printf("%s(addr, %u) -> %llu\n", __func__, flags, cid);
51#endif
52	error = copyout(&cid, cidp, sizeof(cid));
53out:
54	return error;
55}
56
57/*
58 * Request to terminate the coalition identified by ID.
59 * Attempts to spawn into this coalition using the posix_spawnattr will begin
60 * failing. Processes already within the coalition may still fork.
61 * Arms the 'coalition is empty' notification when the coalition's active
62 * count reaches zero.
63 *
64 * Returns:
65 * ESRCH	No coalition with that ID could be found.
66 * EALREADY	The coalition with that ID has already been terminated.
67 * EFAULT	cidp parameter pointed to invalid memory.
68 * EPERM	Caller doesn't have permission to terminate that coalition.
69 */
70static
71int
72coalition_request_terminate_syscall(user_addr_t cidp, uint32_t flags)
73{
74	kern_return_t kr;
75	int error = 0;
76	uint64_t cid;
77	coalition_t coal;
78
79	if (flags != 0) {
80		return EINVAL;
81	}
82
83	error = copyin(cidp, &cid, sizeof(cid));
84	if (error) {
85		return error;
86	}
87
88	coal = coalition_find_by_id(cid);
89	if (coal == COALITION_NULL) {
90		return ESRCH;
91	}
92
93	kr = coalition_request_terminate_internal(coal);
94	coalition_release(coal);
95
96	switch (kr) {
97	case KERN_SUCCESS:
98		break;
99	case KERN_DEFAULT_SET:
100		error = EPERM;
101	case KERN_TERMINATED:
102		error = EALREADY;
103	case KERN_INVALID_NAME:
104		error = ESRCH;
105	default:
106		error = EIO;
107	}
108
109#if COALITION_DEBUG
110	printf("%s(%llu, %u) -> %d\n", __func__, cid, flags, error);
111#endif
112
113	return error;
114}
115
116/*
117 * Request the kernel to deallocate the coalition identified by ID, which
118 * must be both terminated and empty. This balances the reference taken
119 * in coalition_create.
120 * The memory containig the coalition object may not be freed just yet, if
121 * other kernel operations still hold references to it.
122 *
123 * Returns:
124 * EINVAL	Flags parameter was invalid
125 * ESRCH	Coalition ID refers to a coalition that doesn't exist.
126 * EBUSY	Coalition has not yet been terminated.
127 * EBUSY	Coalition is still active.
128 * EFAULT	cidp parameter pointed to invalid memory.
129 * EPERM	Caller doesn't have permission to terminate that coalition.
130 * Consumes one reference, "held" by caller since coalition_create
131 */
132static
133int
134coalition_reap_syscall(user_addr_t cidp, uint32_t flags)
135{
136	kern_return_t kr;
137	int error = 0;
138	uint64_t cid;
139	coalition_t coal;
140
141	if (flags != 0) {
142		return EINVAL;
143	}
144
145	error = copyin(cidp, &cid, sizeof(cid));
146	if (error) {
147		return error;
148	}
149
150	coal = coalition_find_by_id(cid);
151	if (coal == COALITION_NULL) {
152		return ESRCH;
153	}
154
155	kr = coalition_reap_internal(coal);
156	coalition_release(coal);
157
158	switch (kr) {
159	case KERN_SUCCESS:
160		break;
161	case KERN_DEFAULT_SET:
162		error = EPERM;
163	case KERN_TERMINATED:
164		error = ESRCH;
165	case KERN_FAILURE:
166		error = EBUSY;
167	default:
168		error = EIO;
169	}
170
171#if COALITION_DEBUG
172	printf("%s(%llu, %u) -> %d\n", __func__, cid, flags, error);
173#endif
174
175	return error;
176}
177
178/* Syscall demux.
179 * Returns EPERM if the calling process is not privileged to make this call.
180 */
181int coalition(proc_t p, struct coalition_args *cap, __unused int32_t *retval)
182{
183	uint32_t operation = cap->operation;
184	user_addr_t cidp = cap->cid;
185	uint32_t flags = cap->flags;
186	int error = 0;
187
188	if (!task_is_in_privileged_coalition(p->task)) {
189		return EPERM;
190	}
191
192	switch (operation) {
193	case COALITION_OP_CREATE:
194		error = coalition_create_syscall(cidp, flags);
195		break;
196	case COALITION_OP_REAP:
197		error = coalition_reap_syscall(cidp, flags);
198		break;
199	case COALITION_OP_TERMINATE:
200		error = coalition_request_terminate_syscall(cidp, flags);
201		break;
202	default:
203		error = ENOSYS;
204	}
205	return error;
206}
207
208/* This is a temporary interface, likely to be changed by 15385642. */
209static int __attribute__ ((noinline))
210coalition_info_resource_usage(coalition_t coal, user_addr_t buffer, user_size_t bufsize)
211{
212	kern_return_t kr;
213	struct coalition_resource_usage cru;
214
215	if (bufsize != sizeof(cru)) {
216		return EINVAL;
217	}
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, bufsize);
233}
234
235int 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
278bad:
279	coalition_release(coal);
280	return error;
281}
282