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 <kern/task.h>
39#include <kern/thread.h>
40#include <mach/thread_act.h>
41#include <mach/mach_types.h>
42
43#include <libkern/libkern.h>
44#include <IOKit/IOBSD.h>
45
46#define PERSONA_INFO_V1_SIZE offsetof(struct kpersona_info, persona_uid)
47#define PERSONA_INFO_V2_SIZE sizeof(struct kpersona_info)
48
49extern kern_return_t bank_get_bank_ledger_thread_group_and_persona(void *voucher,
50 void *bankledger, void **banktg, uint32_t *persona_id);
51
52static int
53kpersona_copyin(user_addr_t infop, struct kpersona_info *kinfo)
54{
55 uint32_t info_v = 0;
56 int error;
57 size_t kinfo_size;
58
59 memset(s: kinfo, c: 0, n: sizeof(struct kpersona_info));
60 /* Initialize fields introduced in newer versions. They won't be covered by
61 * the copyin() if the caller passed an old version.
62 */
63 kinfo->persona_uid = KAUTH_UID_NONE;
64
65 error = copyin(infop, &info_v, sizeof(info_v));
66 if (error) {
67 return error;
68 }
69
70 switch (info_v) {
71 case PERSONA_INFO_V1:
72 kinfo_size = PERSONA_INFO_V1_SIZE;
73 break;
74 case PERSONA_INFO_V2:
75 kinfo_size = PERSONA_INFO_V2_SIZE;
76 break;
77 default:
78 return EINVAL;
79 }
80 error = copyin(infop, kinfo, kinfo_size);
81
82 /* enforce NULL termination on strings */
83 kinfo->persona_name[MAXLOGNAME] = 0;
84
85 return error;
86}
87
88static int
89kpersona_copyout(struct kpersona_info *kinfo, user_addr_t infop)
90{
91 uint32_t info_v;
92 int error;
93 size_t kinfo_size;
94
95 error = copyin(infop, &info_v, sizeof(info_v));
96 if (error) {
97 return error;
98 }
99
100 switch (info_v) {
101 case PERSONA_INFO_V1:
102 kinfo_size = PERSONA_INFO_V1_SIZE;
103 break;
104 case PERSONA_INFO_V2:
105 kinfo_size = PERSONA_INFO_V2_SIZE;
106 break;
107 default:
108 return EINVAL;
109 }
110
111 /* preserve version field specified by the caller */
112 uint32_t original_version = kinfo->persona_info_version;
113 kinfo->persona_info_version = info_v;
114
115 error = copyout(kinfo, infop, kinfo_size);
116
117 kinfo->persona_info_version = original_version;
118
119 return error;
120}
121
122
123static int
124kpersona_alloc_syscall(user_addr_t infop, user_addr_t idp, user_addr_t path)
125{
126 int error;
127 struct kpersona_info kinfo;
128 struct persona *persona = NULL;
129 uid_t id = PERSONA_ID_NONE;
130 const char *login;
131 char *pna_path = NULL;
132
133 if (!IOCurrentTaskHasEntitlement(PERSONA_MGMT_ENTITLEMENT)) {
134 return EPERM;
135 }
136
137 error = kpersona_copyin(infop, kinfo: &kinfo);
138 if (error) {
139 return error;
140 }
141
142 login = kinfo.persona_name[0] ? kinfo.persona_name : NULL;
143 if (kinfo.persona_id != PERSONA_ID_NONE && kinfo.persona_id != (uid_t)0) {
144 id = kinfo.persona_id;
145 }
146
147 if (path) {
148 pna_path = zalloc_flags(ZV_NAMEI, Z_WAITOK | Z_ZERO);
149
150 size_t pathlen;
151 error = copyinstr(uaddr: path, kaddr: (void *)pna_path, MAXPATHLEN, done: &pathlen);
152 if (error) {
153 zfree(ZV_NAMEI, pna_path);
154 return error;
155 }
156 }
157
158 error = 0;
159 persona = persona_alloc(id, login, type: kinfo.persona_type, path: pna_path, uid: kinfo.persona_uid, error: &error);
160 if (!persona) {
161 if (pna_path != NULL) {
162 zfree(ZV_NAMEI, pna_path);
163 }
164 return error;
165 }
166
167 /* persona struct contains a reference to pna_path */
168 pna_path = NULL;
169
170 error = persona_init_begin(persona);
171 if (error) {
172 goto out_persona_err;
173 }
174
175 error = copyout(&persona->pna_id, idp, sizeof(persona->pna_id));
176 if (error) {
177 goto out_persona_err;
178 }
179
180 kinfo.persona_id = persona->pna_id;
181 error = kpersona_copyout(kinfo: &kinfo, infop);
182 if (error) {
183 goto out_persona_err;
184 }
185
186 persona_init_end(persona, error);
187
188 /*
189 * On success, we have a persona structure in the global list with a
190 * single reference count on it. The corresponding _dealloc() call
191 * will release this reference.
192 */
193 return error;
194
195out_persona_err:
196 assert(error != 0);
197 persona_init_end(persona, error);
198
199#if PERSONA_DEBUG
200 printf("%s: ERROR:%d\n", __func__, error);
201#endif
202 if (persona) {
203 persona_put(persona);
204 }
205 return error;
206}
207
208static int
209kpersona_dealloc_syscall(user_addr_t idp)
210{
211 int error = 0;
212 uid_t persona_id;
213 struct persona *persona;
214
215 if (!IOCurrentTaskHasEntitlement(PERSONA_MGMT_ENTITLEMENT)) {
216 return EPERM;
217 }
218
219 error = copyin(idp, &persona_id, sizeof(persona_id));
220 if (error) {
221 return error;
222 }
223
224 /* invalidate the persona (deny subsequent spawn/fork) */
225 persona = persona_lookup_and_invalidate(id: persona_id);
226
227 if (!persona) {
228 return ESRCH;
229 }
230
231 /* one reference from the _lookup() */
232 persona_put(persona);
233
234 /* one reference from the _alloc() */
235 persona_put(persona);
236
237 return error;
238}
239
240static int
241kpersona_get_syscall(user_addr_t idp)
242{
243 int error;
244 uid_t current_persona_id;
245 struct persona *persona;
246
247 current_persona_id = current_persona_get_id();
248
249 /* Make sure the persona is still valid */
250 persona = persona_lookup(id: current_persona_id);
251 if (!persona) {
252 return ESRCH;
253 }
254
255 error = copyout(&persona->pna_id, idp, sizeof(persona->pna_id));
256 persona_put(persona);
257
258 return error;
259}
260
261static int
262kpersona_getpath_syscall(user_addr_t idp, user_addr_t path)
263{
264 int error;
265 uid_t persona_id;
266 struct persona *persona;
267 size_t pathlen;
268 uid_t lookup_persona_id = PERSONA_ID_NONE;
269
270 if (!path) {
271 return EINVAL;
272 }
273
274 error = copyin(idp, &persona_id, sizeof(persona_id));
275 if (error) {
276 return error;
277 }
278
279 /* Get current thread's persona id to compare if the
280 * input persona_id matches the current persona id
281 */
282 lookup_persona_id = current_persona_get_id();
283
284 if (persona_id && persona_id != lookup_persona_id) {
285 if (!kauth_cred_issuser(cred: kauth_cred_get()) &&
286 !IOCurrentTaskHasEntitlement(PERSONA_MGMT_ENTITLEMENT)) {
287 return EPERM;
288 }
289 lookup_persona_id = persona_id;
290 }
291
292 persona = persona_lookup(id: lookup_persona_id);
293 if (!persona) {
294 return ESRCH;
295 }
296
297 if (persona->pna_path) {
298 error = copyoutstr(kaddr: (void *)persona->pna_path, udaddr: path, MAXPATHLEN, done: &pathlen);
299 }
300
301 persona_put(persona);
302
303 return error;
304}
305
306static void
307kpersona_populate(struct kpersona_info *kinfo, struct persona *persona)
308{
309 memset(s: kinfo, c: 0, n: sizeof(struct kpersona_info));
310
311 kinfo->persona_info_version = PERSONA_INFO_V2;
312 kinfo->persona_id = persona->pna_id;
313 kinfo->persona_type = persona->pna_type;
314 kinfo->persona_uid = persona->pna_uid;
315
316 /*
317 * NULL termination is assured b/c persona_name is
318 * exactly MAXLOGNAME + 1 bytes (and has been memset to 0)
319 */
320 strncpy(kinfo->persona_name, persona->pna_login, MAXLOGNAME);
321}
322
323static int
324kpersona_info_syscall(user_addr_t idp, user_addr_t infop)
325{
326 int error;
327 uid_t persona_id;
328 struct persona *persona;
329 struct kpersona_info kinfo;
330 uid_t lookup_persona_id = PERSONA_ID_NONE;
331
332 error = copyin(idp, &persona_id, sizeof(persona_id));
333 if (error) {
334 return error;
335 }
336
337 /* Get current thread's persona id to compare if the
338 * input persona_id matches the current persona id
339 */
340 lookup_persona_id = current_persona_get_id();
341
342 if (persona_id && persona_id != lookup_persona_id) {
343 if (!kauth_cred_issuser(cred: kauth_cred_get()) &&
344 !IOCurrentTaskHasEntitlement(PERSONA_MGMT_ENTITLEMENT)) {
345 return EPERM;
346 }
347 lookup_persona_id = persona_id;
348 }
349
350 persona = persona_lookup(id: lookup_persona_id);
351 if (!persona) {
352 return ESRCH;
353 }
354
355 persona_dbg("FOUND: persona: id:%d, login:\"%s\"",
356 persona->pna_id, persona->pna_login);
357
358 kpersona_populate(kinfo: &kinfo, persona);
359
360 persona_put(persona);
361
362 error = kpersona_copyout(kinfo: &kinfo, infop);
363
364 return error;
365}
366
367static int
368kpersona_pidinfo_syscall(user_addr_t idp, user_addr_t infop)
369{
370 int error;
371 pid_t pid;
372 struct persona *persona;
373 struct kpersona_info kinfo;
374
375 error = copyin(idp, &pid, sizeof(pid));
376 if (error) {
377 return error;
378 }
379
380 if (!kauth_cred_issuser(cred: kauth_cred_get())
381 && (pid != proc_getpid(current_proc()))) {
382 return EPERM;
383 }
384
385 persona = persona_proc_get(pid);
386 if (!persona) {
387 return ESRCH;
388 }
389
390 kpersona_populate(kinfo: &kinfo, persona);
391
392 persona_put(persona);
393
394 error = kpersona_copyout(kinfo: &kinfo, infop);
395
396 return error;
397}
398
399static int
400kpersona_find_syscall(user_addr_t infop, user_addr_t idp, user_addr_t idlenp)
401{
402 int error;
403 struct kpersona_info kinfo;
404 const char *login;
405 size_t u_idlen, k_idlen = 0;
406 struct persona **persona = NULL;
407
408 error = copyin(idlenp, &u_idlen, sizeof(u_idlen));
409 if (error) {
410 return error;
411 }
412
413 if (u_idlen > g_max_personas) {
414 u_idlen = g_max_personas;
415 }
416
417 error = kpersona_copyin(infop, kinfo: &kinfo);
418 if (error) {
419 goto out;
420 }
421
422 login = kinfo.persona_name[0] ? kinfo.persona_name : NULL;
423
424 if (u_idlen > 0) {
425 persona = kalloc_type(struct persona *, u_idlen, Z_WAITOK | Z_ZERO);
426 if (!persona) {
427 error = ENOMEM;
428 goto out;
429 }
430 }
431
432 k_idlen = u_idlen;
433 error = persona_find_all(login, uid: kinfo.persona_id, persona_type: (persona_type_t)kinfo.persona_type, persona, plen: &k_idlen);
434 if (error) {
435 goto out;
436 }
437
438 /* copyout all the IDs of each persona we found */
439 for (size_t i = 0; i < k_idlen; i++) {
440 if (i >= u_idlen) {
441 break;
442 }
443 error = copyout(&persona[i]->pna_id,
444 idp + (i * sizeof(persona[i]->pna_id)),
445 sizeof(persona[i]->pna_id));
446 if (error) {
447 goto out;
448 }
449 }
450
451out:
452 if (persona) {
453 for (size_t i = 0; i < u_idlen; i++) {
454 persona_put(persona: persona[i]);
455 }
456 kfree_type(struct persona *, u_idlen, persona);
457 }
458
459 (void)copyout(&k_idlen, idlenp, sizeof(u_idlen));
460
461 return error;
462}
463
464/*
465 * Syscall entry point / demux.
466 */
467int
468persona(__unused proc_t p, struct persona_args *pargs, __unused int32_t *retval)
469{
470 int error;
471 uint32_t op = pargs->operation;
472 /* uint32_t flags = pargs->flags; */
473 user_addr_t infop = pargs->info;
474 user_addr_t idp = pargs->id;
475 user_addr_t path = pargs->path;
476
477 switch (op) {
478 case PERSONA_OP_ALLOC:
479 error = kpersona_alloc_syscall(infop, idp, USER_ADDR_NULL);
480 break;
481 case PERSONA_OP_PALLOC:
482 error = kpersona_alloc_syscall(infop, idp, path);
483 break;
484 case PERSONA_OP_DEALLOC:
485 error = kpersona_dealloc_syscall(idp);
486 break;
487 case PERSONA_OP_GET:
488 error = kpersona_get_syscall(idp);
489 break;
490 case PERSONA_OP_GETPATH:
491 error = kpersona_getpath_syscall(idp, path);
492 break;
493 case PERSONA_OP_INFO:
494 error = kpersona_info_syscall(idp, infop);
495 break;
496 case PERSONA_OP_PIDINFO:
497 error = kpersona_pidinfo_syscall(idp, infop);
498 break;
499 case PERSONA_OP_FIND:
500 case PERSONA_OP_FIND_BY_TYPE:
501 error = kpersona_find_syscall(infop, idp, idlenp: pargs->idlen);
502 break;
503 default:
504 error = ENOSYS;
505 break;
506 }
507
508 return error;
509}
510