1/*
2 * Copyright (c) 2015 Apple Inc. All rights reserved.
3 *
4 * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. The rights granted to you under the License
10 * may not be used to create, or enable the creation or redistribution of,
11 * unlawful or unlicensed copies of an Apple operating system, or to
12 * circumvent, violate, or enable the circumvention or violation of, any
13 * terms of an Apple operating system software license agreement.
14 *
15 * Please obtain a copy of the License at
16 * http://www.opensource.apple.com/apsl/ and read it before using this file.
17 *
18 * The Original Code and all software distributed under the License are
19 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
20 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
21 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
22 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
23 * Please see the License for the specific language governing rights and
24 * limitations under the License.
25 *
26 * @APPLE_OSREFERENCE_LICENSE_HEADER_END@
27 */
28#include <sys/param.h>
29#include <sys/kernel.h>
30#include <sys/kernel_types.h>
31#include <sys/sysproto.h>
32
33#include <sys/kauth.h>
34#include <sys/malloc.h>
35#include <sys/persona.h>
36#include <sys/proc.h>
37
38#include <libkern/libkern.h>
39
40static int kpersona_copyin(user_addr_t infop, struct kpersona_info *kinfo)
41{
42 uint32_t info_v = 0;
43 int error;
44
45 error = copyin(infop, &info_v, sizeof(info_v));
46 if (error)
47 return error;
48
49 /* only support a single version of the struct for now */
50 if (info_v != PERSONA_INFO_V1)
51 return EINVAL;
52
53 error = copyin(infop, kinfo, sizeof(*kinfo));
54
55 /* enforce NULL termination on strings */
56 kinfo->persona_name[MAXLOGNAME] = 0;
57
58 return error;
59}
60
61static int kpersona_copyout(struct kpersona_info *kinfo, user_addr_t infop)
62{
63 uint32_t info_v;
64 int error;
65
66 error = copyin(infop, &info_v, sizeof(info_v));
67 if (error)
68 return error;
69
70 /* only support a single version of the struct for now */
71 /* TODO: in the future compare info_v to kinfo->persona_info_version */
72 if (info_v != PERSONA_INFO_V1)
73 return EINVAL;
74
75 error = copyout(kinfo, infop, sizeof(*kinfo));
76 return error;
77}
78
79
80static int kpersona_alloc_syscall(user_addr_t infop, user_addr_t idp)
81{
82 int error;
83 struct kpersona_info kinfo;
84 struct persona *persona;
85 uid_t id = PERSONA_ID_NONE;
86 const char *login;
87
88 /*
89 * TODO: rdar://problem/19981151
90 * Add entitlement check!
91 */
92 if (!kauth_cred_issuser(kauth_cred_get()))
93 return EPERM;
94
95 error = kpersona_copyin(infop, &kinfo);
96 if (error)
97 return error;
98
99 login = kinfo.persona_name[0] ? kinfo.persona_name : NULL;
100 if (kinfo.persona_id != PERSONA_ID_NONE && kinfo.persona_id != (uid_t)0)
101 id = kinfo.persona_id;
102
103 error = 0;
104 persona = persona_alloc(id, login, kinfo.persona_type, &error);
105 if (!persona)
106 return error;
107
108 error = persona_init_begin(persona);
109 if (error) {
110 goto out_persona_err;
111 }
112
113 if (kinfo.persona_gid) {
114 error = persona_set_gid(persona, kinfo.persona_gid);
115 if (error)
116 goto out_persona_err;
117 }
118
119 if (kinfo.persona_ngroups > 0) {
120 /* force gmuid 0 to *opt-out* of memberd */
121 if (kinfo.persona_gmuid == 0)
122 kinfo.persona_gmuid = KAUTH_UID_NONE;
123
124 error = persona_set_groups(persona, kinfo.persona_groups,
125 kinfo.persona_ngroups,
126 kinfo.persona_gmuid);
127 if (error)
128 goto out_persona_err;
129 }
130
131 error = copyout(&persona->pna_id, idp, sizeof(persona->pna_id));
132 if (error) {
133 goto out_persona_err;
134 }
135
136 kinfo.persona_id = persona->pna_id;
137 error = kpersona_copyout(&kinfo, infop);
138 if (error) {
139 goto out_persona_err;
140 }
141
142 persona_init_end(persona, error);
143
144 /*
145 * On success, we have a persona structure in the global list with a
146 * single reference count on it. The corresponding _dealloc() call
147 * will release this reference.
148 */
149 return error;
150
151out_persona_err:
152 assert(error != 0);
153 persona_init_end(persona, error);
154
155#if PERSONA_DEBUG
156 printf("%s: ERROR:%d\n", __func__, error);
157#endif
158 if (persona)
159 persona_put(persona);
160 return error;
161}
162
163static int kpersona_dealloc_syscall(user_addr_t idp)
164{
165 int error = 0;
166 uid_t persona_id;
167 struct persona *persona;
168
169 if (!kauth_cred_issuser(kauth_cred_get()))
170 return EPERM;
171
172 error = copyin(idp, &persona_id, sizeof(persona_id));
173 if (error)
174 return error;
175
176 /* invalidate the persona (deny subsequent spawn/fork) */
177 persona = persona_lookup_and_invalidate(persona_id);
178
179 if (!persona)
180 return ESRCH;
181
182 /* one reference from the _lookup() */
183 persona_put(persona);
184
185 /* one reference from the _alloc() */
186 persona_put(persona);
187
188 return error;
189}
190
191static int kpersona_get_syscall(user_addr_t idp)
192{
193 int error;
194 struct persona *persona = current_persona_get();
195
196 if (!persona)
197 return ESRCH;
198
199 error = copyout(&persona->pna_id, idp, sizeof(persona->pna_id));
200 persona_put(persona);
201
202 return error;
203}
204
205static int kpersona_info_syscall(user_addr_t idp, user_addr_t infop)
206{
207 int error;
208 uid_t persona_id;
209 struct persona *persona;
210 struct kpersona_info kinfo;
211
212 error = copyin(idp, &persona_id, sizeof(persona_id));
213 if (error)
214 return error;
215
216 /*
217 * TODO: rdar://problem/19981151
218 * Add entitlement check!
219 */
220
221 persona = persona_lookup(persona_id);
222 if (!persona)
223 return ESRCH;
224
225 persona_dbg("FOUND: persona: id:%d, gid:%d, login:\"%s\"",
226 persona->pna_id, persona_get_gid(persona),
227 persona->pna_login);
228
229 memset(&kinfo, 0, sizeof(kinfo));
230 kinfo.persona_info_version = PERSONA_INFO_V1;
231 kinfo.persona_id = persona->pna_id;
232 kinfo.persona_type = persona->pna_type;
233 kinfo.persona_gid = persona_get_gid(persona);
234 unsigned ngroups = 0;
235 persona_get_groups(persona, &ngroups, kinfo.persona_groups, NGROUPS);
236 kinfo.persona_ngroups = ngroups;
237 kinfo.persona_gmuid = persona_get_gmuid(persona);
238
239 /*
240 * NULL termination is assured b/c persona_name is
241 * exactly MAXLOGNAME + 1 bytes (and has been memset to 0)
242 */
243 strncpy(kinfo.persona_name, persona->pna_login, MAXLOGNAME);
244
245 persona_put(persona);
246
247 error = kpersona_copyout(&kinfo, infop);
248
249 return error;
250}
251
252static int kpersona_pidinfo_syscall(user_addr_t idp, user_addr_t infop)
253{
254 int error;
255 pid_t pid;
256 struct persona *persona;
257 struct kpersona_info kinfo;
258
259 error = copyin(idp, &pid, sizeof(pid));
260 if (error)
261 return error;
262
263 if (!kauth_cred_issuser(kauth_cred_get())
264 && (pid != current_proc()->p_pid))
265 return EPERM;
266
267 persona = persona_proc_get(pid);
268 if (!persona)
269 return ESRCH;
270
271 memset(&kinfo, 0, sizeof(kinfo));
272 kinfo.persona_info_version = PERSONA_INFO_V1;
273 kinfo.persona_id = persona->pna_id;
274 kinfo.persona_type = persona->pna_type;
275 kinfo.persona_gid = persona_get_gid(persona);
276 unsigned ngroups = 0;
277 persona_get_groups(persona, &ngroups, kinfo.persona_groups, NGROUPS);
278 kinfo.persona_ngroups = ngroups;
279 kinfo.persona_gmuid = persona_get_gmuid(persona);
280
281 strncpy(kinfo.persona_name, persona->pna_login, MAXLOGNAME);
282
283 persona_put(persona);
284
285 error = kpersona_copyout(&kinfo, infop);
286
287 return error;
288}
289
290static int kpersona_find_syscall(user_addr_t infop, user_addr_t idp, user_addr_t idlenp)
291{
292 int error;
293 struct kpersona_info kinfo;
294 const char *login;
295 size_t u_idlen, k_idlen = 0;
296 struct persona **persona = NULL;
297
298 error = copyin(idlenp, &u_idlen, sizeof(u_idlen));
299 if (error)
300 return error;
301
302 if (u_idlen > g_max_personas)
303 u_idlen = g_max_personas;
304
305 error = kpersona_copyin(infop, &kinfo);
306 if (error)
307 goto out;
308
309 login = kinfo.persona_name[0] ? kinfo.persona_name : NULL;
310
311 if (u_idlen > 0) {
312 MALLOC(persona, struct persona **, sizeof(*persona) * u_idlen,
313 M_TEMP, M_WAITOK|M_ZERO);
314 if (!persona) {
315 error = ENOMEM;
316 goto out;
317 }
318 }
319
320 k_idlen = u_idlen;
321 error = persona_find(login, kinfo.persona_id, persona, &k_idlen);
322 if (error)
323 goto out;
324
325 /* copyout all the IDs of each persona we found */
326 for (size_t i = 0; i < k_idlen; i++) {
327 if (i >= u_idlen)
328 break;
329 error = copyout(&persona[i]->pna_id,
330 idp + (i * sizeof(persona[i]->pna_id)),
331 sizeof(persona[i]->pna_id));
332 if (error)
333 goto out;
334 }
335
336out:
337 if (persona) {
338 for (size_t i = 0; i < u_idlen; i++)
339 persona_put(persona[i]);
340 FREE(persona, M_TEMP);
341 }
342
343 (void)copyout(&k_idlen, idlenp, sizeof(u_idlen));
344
345 return error;
346}
347
348
349/*
350 * Syscall entry point / demux.
351 */
352int persona(__unused proc_t p, struct persona_args *pargs, __unused int32_t *retval)
353{
354 int error;
355 uint32_t op = pargs->operation;
356 /* uint32_t flags = pargs->flags; */
357 user_addr_t infop = pargs->info;
358 user_addr_t idp = pargs->id;
359
360 switch (op) {
361 case PERSONA_OP_ALLOC:
362 error = kpersona_alloc_syscall(infop, idp);
363 break;
364 case PERSONA_OP_DEALLOC:
365 error = kpersona_dealloc_syscall(idp);
366 break;
367 case PERSONA_OP_GET:
368 error = kpersona_get_syscall(idp);
369 break;
370 case PERSONA_OP_INFO:
371 error = kpersona_info_syscall(idp, infop);
372 break;
373 case PERSONA_OP_PIDINFO:
374 error = kpersona_pidinfo_syscall(idp, infop);
375 break;
376 case PERSONA_OP_FIND:
377 error = kpersona_find_syscall(infop, idp, pargs->idlen);
378 break;
379 default:
380 error = ENOSYS;
381 break;
382 }
383
384 return error;
385}
386