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