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 | |
49 | extern kern_return_t bank_get_bank_ledger_thread_group_and_persona(void *voucher, |
50 | void *bankledger, void **banktg, uint32_t *persona_id); |
51 | |
52 | static int |
53 | kpersona_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 | |
88 | static int |
89 | kpersona_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 | |
123 | static int |
124 | kpersona_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 | |
195 | out_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 | |
208 | static int |
209 | kpersona_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 | |
240 | static int |
241 | kpersona_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 | |
261 | static int |
262 | kpersona_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 | |
306 | static void |
307 | kpersona_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 | |
323 | static int |
324 | kpersona_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 | |
367 | static int |
368 | kpersona_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 | |
399 | static int |
400 | kpersona_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 | |
451 | out: |
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 | */ |
467 | int |
468 | persona(__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 | |