]>
Commit | Line | Data |
---|---|---|
490019cf A |
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/kernel.h> | |
29 | #include <sys/kernel_types.h> | |
30 | #include <sys/persona.h> | |
31 | ||
32 | #if CONFIG_PERSONAS | |
33 | #include <kern/assert.h> | |
34 | #include <kern/simple_lock.h> | |
35 | #include <kern/task.h> | |
36 | #include <kern/zalloc.h> | |
37 | ||
38 | #include <sys/param.h> | |
39 | #include <sys/proc_internal.h> | |
40 | #include <sys/kauth.h> | |
41 | #include <sys/proc_info.h> | |
42 | #include <sys/resourcevar.h> | |
43 | ||
44 | #define pna_info(fmt, ...) \ | |
45 | printf("%s: " fmt "\n", __func__, ## __VA_ARGS__) | |
46 | ||
47 | #define pna_err(fmt, ...) \ | |
48 | printf("ERROR[%s]: " fmt "\n", __func__, ## __VA_ARGS__) | |
49 | ||
50 | #define MAX_PERSONAS 512 | |
51 | ||
52 | #define TEMP_PERSONA_ID 499 | |
53 | ||
54 | #define FIRST_PERSONA_ID 501 | |
55 | #define PERSONA_ID_STEP 10 | |
56 | ||
57 | #define PERSONA_SYSTEM_UID ((uid_t)99) | |
58 | #define PERSONA_SYSTEM_LOGIN "system" | |
59 | ||
60 | #define PERSONA_MAGIC (0x0aa55aa0) | |
61 | #define persona_valid(p) ((p)->pna_valid == PERSONA_MAGIC) | |
62 | #define persona_mkinvalid(p) ((p)->pna_valid = ~(PERSONA_MAGIC)) | |
63 | ||
64 | static LIST_HEAD(personalist, persona) all_personas; | |
65 | static uint32_t g_total_personas; | |
66 | uint32_t g_max_personas = MAX_PERSONAS; | |
67 | ||
68 | struct persona *g_system_persona = NULL; | |
69 | ||
70 | static uid_t g_next_persona_id; | |
71 | ||
72 | lck_mtx_t all_personas_lock; | |
73 | lck_attr_t *persona_lck_attr; | |
74 | lck_grp_t *persona_lck_grp; | |
75 | lck_grp_attr_t *persona_lck_grp_attr; | |
76 | ||
77 | static zone_t persona_zone; | |
78 | ||
79 | kauth_cred_t g_default_persona_cred; | |
80 | ||
81 | #define lock_personas() lck_mtx_lock(&all_personas_lock) | |
82 | #define unlock_personas() lck_mtx_unlock(&all_personas_lock) | |
83 | ||
84 | ||
85 | extern void mach_kauth_cred_uthread_update(void); | |
86 | ||
87 | void personas_bootstrap(void) | |
88 | { | |
89 | struct posix_cred pcred; | |
90 | ||
91 | persona_dbg("Initializing persona subsystem"); | |
92 | LIST_INIT(&all_personas); | |
93 | g_total_personas = 0; | |
94 | ||
95 | g_next_persona_id = FIRST_PERSONA_ID; | |
96 | ||
97 | persona_lck_grp_attr = lck_grp_attr_alloc_init(); | |
98 | lck_grp_attr_setstat(persona_lck_grp_attr); | |
99 | ||
100 | persona_lck_grp = lck_grp_alloc_init("personas", persona_lck_grp_attr); | |
101 | persona_lck_attr = lck_attr_alloc_init(); | |
102 | ||
103 | lck_mtx_init(&all_personas_lock, persona_lck_grp, persona_lck_attr); | |
104 | ||
105 | persona_zone = zinit(sizeof(struct persona), | |
106 | MAX_PERSONAS * sizeof(struct persona), | |
107 | MAX_PERSONAS, "personas"); | |
108 | assert(persona_zone != NULL); | |
109 | ||
110 | /* | |
111 | * setup the default credentials that a persona temporarily | |
112 | * inherits (to work around kauth APIs) | |
113 | */ | |
114 | bzero(&pcred, sizeof(pcred)); | |
115 | pcred.cr_uid = pcred.cr_ruid = pcred.cr_svuid = TEMP_PERSONA_ID; | |
116 | pcred.cr_rgid = pcred.cr_svgid = TEMP_PERSONA_ID; | |
117 | pcred.cr_groups[0] = TEMP_PERSONA_ID; | |
118 | pcred.cr_ngroups = 1; | |
119 | pcred.cr_flags = CRF_NOMEMBERD; | |
120 | pcred.cr_gmuid = KAUTH_UID_NONE; | |
121 | ||
122 | g_default_persona_cred = posix_cred_create(&pcred); | |
123 | if (!g_default_persona_cred) | |
124 | panic("couldn't create default persona credentials!"); | |
125 | ||
126 | g_system_persona = persona_alloc(PERSONA_SYSTEM_UID, | |
127 | PERSONA_SYSTEM_LOGIN, | |
128 | PERSONA_SYSTEM, NULL); | |
129 | assert(g_system_persona != NULL); | |
130 | } | |
131 | ||
132 | struct persona *persona_alloc(uid_t id, const char *login, int type, int *error) | |
133 | { | |
134 | struct persona *persona, *tmp; | |
135 | int err = 0; | |
136 | kauth_cred_t tmp_cred; | |
137 | gid_t new_group; | |
138 | ||
139 | if (!login) { | |
140 | pna_err("Must provide a login name for a new persona!"); | |
141 | if (error) | |
142 | *error = EINVAL; | |
143 | return NULL; | |
144 | } | |
145 | ||
146 | if (type <= PERSONA_INVALID || type > PERSONA_TYPE_MAX) { | |
147 | pna_err("Invalid type: %d", type); | |
148 | if (error) | |
149 | *error = EINVAL; | |
150 | return NULL; | |
151 | } | |
152 | ||
153 | persona = (struct persona *)zalloc(persona_zone); | |
154 | if (!persona) { | |
155 | if (error) | |
156 | *error = ENOMEM; | |
157 | return NULL; | |
158 | } | |
159 | ||
160 | bzero(persona, sizeof(*persona)); | |
161 | ||
162 | if (hw_atomic_add(&g_total_personas, 1) > MAX_PERSONAS) { | |
163 | /* too many personas! */ | |
164 | pna_err("too many active personas!"); | |
165 | err = EBUSY; | |
166 | goto out_error; | |
167 | } | |
168 | ||
169 | strncpy(persona->pna_login, login, sizeof(persona->pna_login)-1); | |
170 | ||
171 | LIST_INIT(&persona->pna_members); | |
172 | lck_mtx_init(&persona->pna_lock, persona_lck_grp, persona_lck_attr); | |
173 | persona->pna_refcount = 1; | |
174 | ||
175 | /* | |
176 | * Setup initial (temporary) kauth_cred structure | |
177 | * We need to do this here because all kauth calls require | |
178 | * an existing cred structure. | |
179 | */ | |
180 | persona->pna_cred = kauth_cred_create(g_default_persona_cred); | |
181 | if (!persona->pna_cred) { | |
182 | pna_err("could not copy initial credentials!"); | |
183 | err = EIO; | |
184 | goto out_error; | |
185 | } | |
186 | ||
187 | lock_personas(); | |
188 | try_again: | |
189 | if (id != PERSONA_ID_NONE) | |
190 | persona->pna_id = id; | |
191 | else | |
192 | persona->pna_id = g_next_persona_id; | |
193 | ||
194 | persona_dbg("Adding %d (%s) to global list...", persona->pna_id, persona->pna_login); | |
195 | ||
196 | err = 0; | |
197 | LIST_FOREACH(tmp, &all_personas, pna_list) { | |
198 | if (id == PERSONA_ID_NONE && tmp->pna_id == id) { | |
199 | /* | |
200 | * someone else manually claimed this ID, and we're | |
201 | * trying to allocate an ID for the caller: try again | |
202 | */ | |
203 | g_next_persona_id += PERSONA_ID_STEP; | |
204 | goto try_again; | |
205 | } | |
206 | if (strncmp(tmp->pna_login, login, sizeof(tmp->pna_login)) == 0 | |
207 | || tmp->pna_id == id) { | |
208 | /* | |
209 | * Disallow use of identical login names and re-use | |
210 | * of previously allocated persona IDs | |
211 | */ | |
212 | err = EEXIST; | |
213 | break; | |
214 | } | |
215 | } | |
216 | if (err) | |
217 | goto out_unlock; | |
218 | ||
219 | /* ensure the cred has proper UID/GID defaults */ | |
220 | kauth_cred_ref(persona->pna_cred); | |
221 | tmp_cred = kauth_cred_setuidgid(persona->pna_cred, | |
222 | persona->pna_id, | |
223 | persona->pna_id); | |
224 | kauth_cred_unref(&persona->pna_cred); | |
225 | if (tmp_cred != persona->pna_cred) | |
226 | persona->pna_cred = tmp_cred; | |
227 | ||
228 | if (!persona->pna_cred) { | |
229 | err = EACCES; | |
230 | goto out_unlock; | |
231 | } | |
232 | ||
233 | /* it should be a member of exactly 1 group (equal to its UID) */ | |
234 | new_group = (gid_t)persona->pna_id; | |
235 | ||
236 | kauth_cred_ref(persona->pna_cred); | |
237 | /* opt _out_ of memberd as a default */ | |
238 | tmp_cred = kauth_cred_setgroups(persona->pna_cred, | |
239 | &new_group, 1, KAUTH_UID_NONE); | |
240 | kauth_cred_unref(&persona->pna_cred); | |
241 | if (tmp_cred != persona->pna_cred) | |
242 | persona->pna_cred = tmp_cred; | |
243 | ||
244 | if (!persona->pna_cred) { | |
245 | err = EACCES; | |
246 | goto out_unlock; | |
247 | } | |
248 | ||
249 | persona->pna_type = type; | |
250 | ||
251 | /* insert the, now valid, persona into the global list! */ | |
252 | persona->pna_valid = PERSONA_MAGIC; | |
253 | LIST_INSERT_HEAD(&all_personas, persona, pna_list); | |
254 | ||
255 | /* if the kernel supplied the persona ID, increment for next time */ | |
256 | if (id == PERSONA_ID_NONE) | |
257 | g_next_persona_id += PERSONA_ID_STEP; | |
258 | ||
259 | out_unlock: | |
260 | unlock_personas(); | |
261 | ||
262 | if (err) { | |
263 | switch (err) { | |
264 | case EEXIST: | |
265 | persona_dbg("Login '%s' (%d) already exists", | |
266 | login, persona->pna_id); | |
267 | break; | |
268 | case EACCES: | |
269 | persona_dbg("kauth_error for persona:%d", persona->pna_id); | |
270 | break; | |
271 | default: | |
272 | persona_dbg("Unknown error:%d", err); | |
273 | } | |
274 | goto out_error; | |
275 | } | |
276 | ||
277 | return persona; | |
278 | ||
279 | out_error: | |
280 | (void)hw_atomic_add(&g_total_personas, -1); | |
281 | zfree(persona_zone, persona); | |
282 | if (error) | |
283 | *error = err; | |
284 | return NULL; | |
285 | } | |
286 | ||
287 | int persona_invalidate(struct persona *persona) | |
288 | { | |
289 | int error = 0; | |
290 | if (!persona) | |
291 | return EINVAL; | |
292 | ||
293 | lock_personas(); | |
294 | persona_lock(persona); | |
295 | ||
296 | if (!persona_valid(persona)) | |
297 | panic("Double-invalidation of persona %p", persona); | |
298 | ||
299 | LIST_REMOVE(persona, pna_list); | |
300 | if (hw_atomic_add(&g_total_personas, -1) == UINT_MAX) | |
301 | panic("persona ref count underflow!\n"); | |
302 | persona_mkinvalid(persona); | |
303 | ||
304 | persona_unlock(persona); | |
305 | unlock_personas(); | |
306 | ||
307 | return error; | |
308 | } | |
309 | ||
310 | static struct persona *persona_get_locked(struct persona *persona) | |
311 | { | |
312 | if (persona->pna_refcount) { | |
313 | persona->pna_refcount++; | |
314 | return persona; | |
315 | } | |
316 | return NULL; | |
317 | } | |
318 | ||
319 | struct persona *persona_get(struct persona *persona) | |
320 | { | |
321 | struct persona *ret; | |
322 | if (!persona) | |
323 | return NULL; | |
324 | persona_lock(persona); | |
325 | ret = persona_get_locked(persona); | |
326 | persona_unlock(persona); | |
327 | ||
328 | return ret; | |
329 | } | |
330 | ||
331 | void persona_put(struct persona *persona) | |
332 | { | |
333 | int destroy = 0; | |
334 | ||
335 | if (!persona) | |
336 | return; | |
337 | ||
338 | persona_lock(persona); | |
339 | if (persona->pna_refcount >= 0) { | |
340 | if (--(persona->pna_refcount) == 0) | |
341 | destroy = 1; | |
342 | } | |
343 | persona_unlock(persona); | |
344 | ||
345 | if (!destroy) | |
346 | return; | |
347 | ||
348 | persona_dbg("Destroying persona %s", persona_desc(persona, 0)); | |
349 | ||
350 | /* release our credential reference */ | |
351 | if (persona->pna_cred) | |
352 | kauth_cred_unref(&persona->pna_cred); | |
353 | ||
354 | /* remove it from the global list and decrement the count */ | |
355 | lock_personas(); | |
356 | if (persona_valid(persona)) { | |
357 | LIST_REMOVE(persona, pna_list); | |
358 | if (hw_atomic_add(&g_total_personas, -1) == UINT_MAX) | |
359 | panic("persona count underflow!\n"); | |
360 | persona_mkinvalid(persona); | |
361 | } | |
362 | unlock_personas(); | |
363 | ||
364 | assert(LIST_EMPTY(&persona->pna_members)); | |
365 | memset(persona, 0, sizeof(*persona)); | |
366 | zfree(persona_zone, persona); | |
367 | } | |
368 | ||
369 | uid_t persona_get_id(struct persona *persona) | |
370 | { | |
371 | if (persona) | |
372 | return persona->pna_id; | |
373 | return PERSONA_ID_NONE; | |
374 | } | |
375 | ||
376 | struct persona *persona_lookup(uid_t id) | |
377 | { | |
378 | struct persona *persona, *tmp; | |
379 | ||
380 | persona = NULL; | |
381 | ||
382 | /* | |
383 | * simple, linear lookup for now: there shouldn't be too many | |
384 | * of these in memory at any given time. | |
385 | */ | |
386 | lock_personas(); | |
387 | LIST_FOREACH(tmp, &all_personas, pna_list) { | |
388 | persona_lock(tmp); | |
389 | if (tmp->pna_id == id && persona_valid(tmp)) { | |
390 | persona = persona_get_locked(tmp); | |
391 | persona_unlock(tmp); | |
392 | break; | |
393 | } | |
394 | persona_unlock(tmp); | |
395 | } | |
396 | unlock_personas(); | |
397 | ||
398 | return persona; | |
399 | } | |
400 | ||
401 | int persona_find(const char *login, uid_t uid, | |
402 | struct persona **persona, size_t *plen) | |
403 | { | |
404 | struct persona *tmp; | |
405 | int match = 0; | |
406 | size_t found = 0; | |
407 | ||
408 | if (login) | |
409 | match++; | |
410 | if (uid != PERSONA_ID_NONE) | |
411 | match++; | |
412 | ||
413 | if (match == 0) | |
414 | return EINVAL; | |
415 | ||
416 | persona_dbg("Searching with %d parameters (l:\"%s\", u:%d)", | |
417 | match, login, uid); | |
418 | ||
419 | lock_personas(); | |
420 | LIST_FOREACH(tmp, &all_personas, pna_list) { | |
421 | int m = 0; | |
422 | persona_lock(tmp); | |
423 | if (login && strncmp(tmp->pna_login, login, sizeof(tmp->pna_login)) == 0) | |
424 | m++; | |
425 | if (uid != PERSONA_ID_NONE && uid == tmp->pna_id) | |
426 | m++; | |
427 | if (m == match) { | |
428 | if (persona && *plen > found) | |
429 | persona[found] = persona_get_locked(tmp); | |
430 | found++; | |
431 | } | |
432 | #ifdef PERSONA_DEBUG | |
433 | if (m > 0) | |
434 | persona_dbg("ID:%d Matched %d/%d, found:%d, *plen:%d", | |
435 | tmp->pna_id, m, match, (int)found, (int)*plen); | |
436 | #endif | |
437 | persona_unlock(tmp); | |
438 | } | |
439 | unlock_personas(); | |
440 | ||
441 | *plen = found; | |
442 | if (!found) | |
443 | return ESRCH; | |
444 | return 0; | |
445 | } | |
446 | ||
447 | struct persona *persona_proc_get(pid_t pid) | |
448 | { | |
449 | struct persona *persona; | |
450 | proc_t p = proc_find(pid); | |
451 | ||
452 | if (!p) | |
453 | return NULL; | |
454 | ||
455 | proc_lock(p); | |
456 | persona = persona_get(p->p_persona); | |
457 | proc_unlock(p); | |
458 | ||
459 | proc_rele(p); | |
460 | ||
461 | return persona; | |
462 | } | |
463 | ||
464 | struct persona *current_persona_get(void) | |
465 | { | |
466 | proc_t p = current_proc(); | |
467 | struct persona *persona; | |
468 | ||
469 | proc_lock(p); | |
470 | persona = persona_get(p->p_persona); | |
471 | proc_unlock(p); | |
472 | ||
473 | return persona; | |
474 | } | |
475 | ||
476 | /** | |
477 | * inherit a persona from parent to child | |
478 | */ | |
479 | int persona_proc_inherit(proc_t child, proc_t parent) | |
480 | { | |
481 | if (child->p_persona != NULL) { | |
482 | persona_dbg("proc_inherit: child already in persona: %s", | |
483 | persona_desc(child->p_persona, 0)); | |
484 | return -1; | |
485 | } | |
486 | ||
487 | /* no persona to inherit */ | |
488 | if (parent->p_persona == NULL) | |
489 | return 0; | |
490 | ||
491 | return persona_proc_adopt(child, parent->p_persona, parent->p_ucred); | |
492 | } | |
493 | ||
494 | int persona_proc_adopt_id(proc_t p, uid_t id, kauth_cred_t auth_override) | |
495 | { | |
496 | int ret; | |
497 | struct persona *persona; | |
498 | ||
499 | persona = persona_lookup(id); | |
500 | if (!persona) | |
501 | return ESRCH; | |
502 | ||
503 | ret = persona_proc_adopt(p, persona, auth_override); | |
504 | ||
505 | /* put the reference from the lookup() */ | |
506 | persona_put(persona); | |
507 | ||
508 | return ret; | |
509 | } | |
510 | ||
511 | ||
512 | typedef enum e_persona_reset_op { | |
513 | PROC_REMOVE_PERSONA = 1, | |
514 | PROC_RESET_OLD_PERSONA = 2, | |
515 | } persona_reset_op_t; | |
516 | ||
517 | /* | |
518 | * internal cleanup routine for proc_set_cred_internal | |
519 | * | |
520 | */ | |
521 | static struct persona *proc_reset_persona_internal(proc_t p, persona_reset_op_t op, | |
522 | struct persona *old_persona, | |
523 | struct persona *new_persona) | |
524 | { | |
525 | #if (DEVELOPMENT || DEBUG) | |
526 | persona_lock_assert_held(new_persona); | |
527 | #endif | |
528 | ||
529 | switch (op) { | |
530 | case PROC_REMOVE_PERSONA: | |
531 | old_persona = p->p_persona; | |
532 | /* fall through */ | |
533 | case PROC_RESET_OLD_PERSONA: | |
534 | break; | |
535 | default: | |
536 | /* invalid arguments */ | |
537 | return NULL; | |
538 | } | |
539 | ||
540 | /* unlock the new persona (locked on entry) */ | |
541 | persona_unlock(new_persona); | |
542 | /* lock the old persona and the process */ | |
543 | persona_lock(old_persona); | |
544 | proc_lock(p); | |
545 | ||
546 | switch (op) { | |
547 | case PROC_REMOVE_PERSONA: | |
548 | LIST_REMOVE(p, p_persona_list); | |
549 | p->p_persona = NULL; | |
550 | break; | |
551 | case PROC_RESET_OLD_PERSONA: | |
552 | p->p_persona = old_persona; | |
553 | LIST_INSERT_HEAD(&old_persona->pna_members, p, p_persona_list); | |
554 | break; | |
555 | } | |
556 | ||
557 | proc_unlock(p); | |
558 | persona_unlock(old_persona); | |
559 | ||
560 | /* re-lock the new persona */ | |
561 | persona_lock(new_persona); | |
562 | return old_persona; | |
563 | } | |
564 | ||
565 | /* | |
566 | * Assumes persona is locked. | |
567 | * On success, takes a reference to 'persona' and returns the | |
568 | * previous persona the process had adopted. The caller is | |
569 | * responsible to release the reference. | |
570 | */ | |
571 | static struct persona *proc_set_cred_internal(proc_t p, struct persona *persona, | |
572 | kauth_cred_t auth_override, int *rlim_error) | |
573 | { | |
574 | struct persona *old_persona = NULL; | |
575 | kauth_cred_t my_cred, my_new_cred; | |
576 | uid_t old_uid, new_uid; | |
577 | int count; | |
578 | ||
579 | /* | |
580 | * This operation must be done under the proc trans lock | |
581 | * by the thread which took the trans lock! | |
582 | */ | |
583 | assert(((p->p_lflag & P_LINTRANSIT) == P_LINTRANSIT) && | |
584 | p->p_transholder == current_thread()); | |
585 | assert(persona != NULL); | |
586 | ||
587 | /* no work to do if we "re-adopt" the same persona */ | |
588 | if (p->p_persona == persona) | |
589 | return NULL; | |
590 | ||
591 | /* | |
592 | * If p is in a persona, then we need to remove 'p' from the list of | |
593 | * processes in that persona. To do this, we need to drop the lock | |
594 | * held on the incoming (new) persona and lock the old one. | |
595 | */ | |
596 | if (p->p_persona) { | |
597 | old_persona = proc_reset_persona_internal(p, PROC_REMOVE_PERSONA, | |
598 | NULL, persona); | |
599 | } | |
600 | ||
601 | if (auth_override) | |
602 | my_new_cred = auth_override; | |
603 | else | |
604 | my_new_cred = persona->pna_cred; | |
605 | ||
606 | if (!my_new_cred) | |
607 | panic("NULL credentials (persona:%p)", persona); | |
608 | ||
609 | *rlim_error = 0; | |
610 | ||
611 | kauth_cred_ref(my_new_cred); | |
612 | ||
613 | new_uid = persona->pna_id; | |
614 | ||
615 | /* | |
616 | * Check to see if we will hit a proc rlimit by moving the process | |
617 | * into the persona. If so, we'll bail early before actually moving | |
618 | * the process or changing its credentials. | |
619 | */ | |
620 | if (new_uid != 0 && | |
621 | (rlim_t)chgproccnt(new_uid, 0) > p->p_rlimit[RLIMIT_NPROC].rlim_cur) { | |
622 | pna_err("PID:%d hit proc rlimit in new persona(%d): %s", | |
623 | p->p_pid, new_uid, persona_desc(persona, 1)); | |
624 | *rlim_error = EACCES; | |
625 | (void)proc_reset_persona_internal(p, PROC_RESET_OLD_PERSONA, | |
626 | old_persona, persona); | |
627 | kauth_cred_unref(&my_new_cred); | |
628 | return NULL; | |
629 | } | |
630 | ||
631 | /* | |
632 | * Set the new credentials on the proc | |
633 | */ | |
634 | set_proc_cred: | |
635 | my_cred = kauth_cred_proc_ref(p); | |
636 | persona_dbg("proc_adopt PID:%d, %s -> %s", | |
637 | p->p_pid, | |
638 | persona_desc(old_persona, 1), | |
639 | persona_desc(persona, 1)); | |
640 | ||
641 | old_uid = kauth_cred_getruid(my_cred); | |
642 | ||
643 | if (my_cred != my_new_cred) { | |
644 | kauth_cred_t old_cred = my_cred; | |
645 | ||
646 | proc_ucred_lock(p); | |
647 | /* | |
648 | * We need to protect against a race where another thread | |
649 | * also changed the credential after we took our | |
650 | * reference. If p_ucred has changed then we should | |
651 | * restart this again with the new cred. | |
652 | */ | |
653 | if (p->p_ucred != my_cred) { | |
654 | proc_ucred_unlock(p); | |
655 | kauth_cred_unref(&my_cred); | |
656 | /* try again */ | |
657 | goto set_proc_cred; | |
658 | } | |
659 | ||
660 | /* update the credential and take a ref for the proc */ | |
661 | kauth_cred_ref(my_new_cred); | |
662 | p->p_ucred = my_new_cred; | |
663 | ||
664 | /* update cred on proc (and current thread) */ | |
665 | mach_kauth_cred_uthread_update(); | |
666 | PROC_UPDATE_CREDS_ONPROC(p); | |
667 | ||
668 | /* drop the proc's old ref on the credential */ | |
669 | kauth_cred_unref(&old_cred); | |
670 | proc_ucred_unlock(p); | |
671 | } | |
672 | ||
673 | /* drop this function's reference to the old cred */ | |
674 | kauth_cred_unref(&my_cred); | |
675 | ||
676 | /* | |
677 | * Update the proc count. | |
678 | * If the UIDs are the same, then there is no work to do. | |
679 | */ | |
680 | if (old_persona) | |
681 | old_uid = old_persona->pna_id; | |
682 | ||
683 | if (new_uid != old_uid) { | |
684 | count = chgproccnt(old_uid, -1); | |
685 | persona_dbg("Decrement %s:%d proc_count to: %d", | |
686 | old_persona ? "Persona" : "UID", old_uid, count); | |
687 | ||
688 | /* | |
689 | * Increment the proc count on the UID associated with | |
690 | * the new persona. Enforce the resource limit just | |
691 | * as in fork1() | |
692 | */ | |
693 | count = chgproccnt(new_uid, 1); | |
694 | persona_dbg("Increment Persona:%d (UID:%d) proc_count to: %d", | |
695 | new_uid, kauth_cred_getuid(my_new_cred), count); | |
696 | } | |
697 | ||
698 | OSBitOrAtomic(P_ADOPTPERSONA, &p->p_flag); | |
699 | ||
700 | proc_lock(p); | |
701 | p->p_persona = persona_get_locked(persona); | |
702 | LIST_INSERT_HEAD(&persona->pna_members, p, p_persona_list); | |
703 | proc_unlock(p); | |
704 | ||
705 | kauth_cred_unref(&my_new_cred); | |
706 | ||
707 | return old_persona; | |
708 | } | |
709 | ||
710 | int persona_proc_adopt(proc_t p, struct persona *persona, kauth_cred_t auth_override) | |
711 | { | |
712 | int error; | |
713 | struct persona *old_persona; | |
714 | struct session * sessp; | |
715 | ||
716 | if (!persona) | |
717 | return EINVAL; | |
718 | ||
719 | persona_dbg("%d adopting Persona %d (%s)", proc_pid(p), | |
720 | persona->pna_id, persona_desc(persona, 0)); | |
721 | ||
722 | persona_lock(persona); | |
723 | if (!persona->pna_cred || !persona_valid(persona)) { | |
724 | persona_dbg("Invalid persona (%s): NULL credentials!", persona_desc(persona, 1)); | |
725 | persona_unlock(persona); | |
726 | return EINVAL; | |
727 | } | |
728 | ||
729 | /* the persona credentials can no longer be adjusted */ | |
730 | persona->pna_cred_locked = 1; | |
731 | ||
732 | /* | |
733 | * assume the persona: this may drop and re-acquire the persona lock! | |
734 | */ | |
735 | error = 0; | |
736 | old_persona = proc_set_cred_internal(p, persona, auth_override, &error); | |
737 | ||
738 | /* join the process group associated with the persona */ | |
739 | if (persona->pna_pgid) { | |
740 | uid_t uid = kauth_cred_getuid(persona->pna_cred); | |
741 | persona_dbg(" PID:%d, pgid:%d%s", | |
742 | p->p_pid, persona->pna_pgid, | |
743 | persona->pna_pgid == uid ? ", new_session" : "."); | |
744 | enterpgrp(p, persona->pna_pgid, persona->pna_pgid == uid); | |
745 | } | |
746 | ||
747 | /* set the login name of the session */ | |
748 | sessp = proc_session(p); | |
749 | if (sessp != SESSION_NULL) { | |
750 | session_lock(sessp); | |
751 | bcopy(persona->pna_login, sessp->s_login, MAXLOGNAME); | |
752 | session_unlock(sessp); | |
753 | session_rele(sessp); | |
754 | } | |
755 | ||
756 | persona_unlock(persona); | |
757 | ||
758 | set_security_token(p); | |
759 | ||
760 | /* | |
761 | * Drop the reference to the old persona. | |
762 | */ | |
763 | if (old_persona) | |
764 | persona_put(old_persona); | |
765 | ||
766 | persona_dbg("%s", error == 0 ? "SUCCESS" : "FAILED"); | |
767 | return error; | |
768 | } | |
769 | ||
770 | int persona_proc_drop(proc_t p) | |
771 | { | |
772 | struct persona *persona = NULL; | |
773 | ||
774 | persona_dbg("PID:%d, %s -> <none>", p->p_pid, persona_desc(p->p_persona, 0)); | |
775 | ||
776 | /* | |
777 | * There are really no other credentials for us to assume, | |
778 | * so we'll just continue running with the credentials | |
779 | * we got from the persona. | |
780 | */ | |
781 | ||
782 | /* | |
783 | * the locks must be taken in reverse order here, so | |
784 | * we have to be careful not to cause deadlock | |
785 | */ | |
786 | try_again: | |
787 | proc_lock(p); | |
788 | if (p->p_persona) { | |
789 | uid_t puid, ruid; | |
790 | if (!persona_try_lock(p->p_persona)) { | |
791 | proc_unlock(p); | |
792 | mutex_pause(0); /* back-off time */ | |
793 | goto try_again; | |
794 | } | |
795 | persona = p->p_persona; | |
796 | LIST_REMOVE(p, p_persona_list); | |
797 | p->p_persona = NULL; | |
798 | ||
799 | ruid = kauth_cred_getruid(p->p_ucred); | |
800 | puid = kauth_cred_getuid(persona->pna_cred); | |
801 | proc_unlock(p); | |
802 | (void)chgproccnt(ruid, 1); | |
803 | (void)chgproccnt(puid, -1); | |
804 | } else { | |
805 | proc_unlock(p); | |
806 | } | |
807 | ||
808 | /* | |
809 | * if the proc had a persona, then it is still locked here | |
810 | * (preserving proper lock ordering) | |
811 | */ | |
812 | ||
813 | if (persona) { | |
814 | persona_unlock(persona); | |
815 | persona_put(persona); | |
816 | } | |
817 | ||
818 | return 0; | |
819 | } | |
820 | ||
821 | int persona_get_type(struct persona *persona) | |
822 | { | |
823 | int type; | |
824 | ||
825 | if (!persona) | |
826 | return PERSONA_INVALID; | |
827 | ||
828 | persona_lock(persona); | |
829 | if (!persona_valid(persona)) { | |
830 | persona_unlock(persona); | |
831 | return PERSONA_INVALID; | |
832 | } | |
833 | type = persona->pna_type; | |
834 | persona_unlock(persona); | |
835 | ||
836 | return type; | |
837 | } | |
838 | ||
839 | int persona_set_cred(struct persona *persona, kauth_cred_t cred) | |
840 | { | |
841 | int ret = 0; | |
842 | kauth_cred_t my_cred; | |
843 | if (!persona || !cred) | |
844 | return EINVAL; | |
845 | ||
846 | persona_lock(persona); | |
847 | if (!persona_valid(persona)) { | |
848 | ret = EINVAL; | |
849 | goto out_unlock; | |
850 | } | |
851 | if (persona->pna_cred_locked) { | |
852 | ret = EPERM; | |
853 | goto out_unlock; | |
854 | } | |
855 | ||
856 | /* create a new cred from the passed-in cred */ | |
857 | my_cred = kauth_cred_create(cred); | |
858 | ||
859 | /* ensure that the UID matches the persona ID */ | |
860 | my_cred = kauth_cred_setresuid(my_cred, persona->pna_id, | |
861 | persona->pna_id, persona->pna_id, | |
862 | KAUTH_UID_NONE); | |
863 | ||
864 | /* TODO: clear the saved GID?! */ | |
865 | ||
866 | /* replace the persona's cred with the new one */ | |
867 | if (persona->pna_cred) | |
868 | kauth_cred_unref(&persona->pna_cred); | |
869 | persona->pna_cred = my_cred; | |
870 | ||
871 | out_unlock: | |
872 | persona_unlock(persona); | |
873 | return ret; | |
874 | } | |
875 | ||
876 | int persona_set_cred_from_proc(struct persona *persona, proc_t proc) | |
877 | { | |
878 | int ret = 0; | |
879 | kauth_cred_t parent_cred, my_cred; | |
880 | if (!persona || !proc) | |
881 | return EINVAL; | |
882 | ||
883 | persona_lock(persona); | |
884 | if (!persona_valid(persona)) { | |
885 | ret = EINVAL; | |
886 | goto out_unlock; | |
887 | } | |
888 | if (persona->pna_cred_locked) { | |
889 | ret = EPERM; | |
890 | goto out_unlock; | |
891 | } | |
892 | ||
893 | parent_cred = kauth_cred_proc_ref(proc); | |
894 | ||
895 | /* TODO: clear the saved UID/GID! */ | |
896 | ||
897 | /* create a new cred from the proc's cred */ | |
898 | my_cred = kauth_cred_create(parent_cred); | |
899 | ||
900 | /* ensure that the UID matches the persona ID */ | |
901 | my_cred = kauth_cred_setresuid(my_cred, persona->pna_id, | |
902 | persona->pna_id, persona->pna_id, | |
903 | KAUTH_UID_NONE); | |
904 | ||
905 | /* replace the persona's cred with the new one */ | |
906 | if (persona->pna_cred) | |
907 | kauth_cred_unref(&persona->pna_cred); | |
908 | persona->pna_cred = my_cred; | |
909 | ||
910 | kauth_cred_unref(&parent_cred); | |
911 | ||
912 | out_unlock: | |
913 | persona_unlock(persona); | |
914 | return ret; | |
915 | } | |
916 | ||
917 | kauth_cred_t persona_get_cred(struct persona *persona) | |
918 | { | |
919 | kauth_cred_t cred = NULL; | |
920 | ||
921 | if (!persona) | |
922 | return NULL; | |
923 | ||
924 | persona_lock(persona); | |
925 | if (!persona_valid(persona)) | |
926 | goto out_unlock; | |
927 | ||
928 | if (persona->pna_cred) { | |
929 | kauth_cred_ref(persona->pna_cred); | |
930 | cred = persona->pna_cred; | |
931 | } | |
932 | ||
933 | out_unlock: | |
934 | persona_unlock(persona); | |
935 | ||
936 | return cred; | |
937 | } | |
938 | ||
939 | uid_t persona_get_uid(struct persona *persona) | |
940 | { | |
941 | uid_t uid = UID_MAX; | |
942 | ||
943 | if (!persona || !persona->pna_cred) | |
944 | return UID_MAX; | |
945 | ||
946 | persona_lock(persona); | |
947 | if (persona_valid(persona)) { | |
948 | uid = kauth_cred_getuid(persona->pna_cred); | |
949 | assert(uid == persona->pna_id); | |
950 | } | |
951 | persona_unlock(persona); | |
952 | ||
953 | return uid; | |
954 | } | |
955 | ||
956 | int persona_set_gid(struct persona *persona, gid_t gid) | |
957 | { | |
958 | int ret = 0; | |
959 | kauth_cred_t my_cred, new_cred; | |
960 | ||
961 | if (!persona || !persona->pna_cred) | |
962 | return EINVAL; | |
963 | ||
964 | persona_lock(persona); | |
965 | if (!persona_valid(persona)) { | |
966 | ret = EINVAL; | |
967 | goto out_unlock; | |
968 | } | |
969 | if (persona->pna_cred_locked) { | |
970 | ret = EPERM; | |
971 | goto out_unlock; | |
972 | } | |
973 | ||
974 | my_cred = persona->pna_cred; | |
975 | kauth_cred_ref(my_cred); | |
976 | new_cred = kauth_cred_setresgid(my_cred, gid, gid, gid); | |
977 | if (new_cred != my_cred) | |
978 | persona->pna_cred = new_cred; | |
979 | kauth_cred_unref(&my_cred); | |
980 | ||
981 | out_unlock: | |
982 | persona_unlock(persona); | |
983 | return ret; | |
984 | } | |
985 | ||
986 | gid_t persona_get_gid(struct persona *persona) | |
987 | { | |
988 | gid_t gid = GID_MAX; | |
989 | ||
990 | if (!persona || !persona->pna_cred) | |
991 | return GID_MAX; | |
992 | ||
993 | persona_lock(persona); | |
994 | if (persona_valid(persona)) | |
995 | gid = kauth_cred_getgid(persona->pna_cred); | |
996 | persona_unlock(persona); | |
997 | ||
998 | return gid; | |
999 | } | |
1000 | ||
1001 | int persona_set_groups(struct persona *persona, gid_t *groups, int ngroups, uid_t gmuid) | |
1002 | { | |
1003 | int ret = 0; | |
1004 | kauth_cred_t my_cred, new_cred; | |
1005 | ||
1006 | if (!persona || !persona->pna_cred) | |
1007 | return EINVAL; | |
1008 | if (ngroups > NGROUPS_MAX) | |
1009 | return EINVAL; | |
1010 | ||
1011 | persona_lock(persona); | |
1012 | if (!persona_valid(persona)) { | |
1013 | ret = EINVAL; | |
1014 | goto out_unlock; | |
1015 | } | |
1016 | if (persona->pna_cred_locked) { | |
1017 | ret = EPERM; | |
1018 | goto out_unlock; | |
1019 | } | |
1020 | ||
1021 | my_cred = persona->pna_cred; | |
1022 | kauth_cred_ref(my_cred); | |
1023 | new_cred = kauth_cred_setgroups(my_cred, groups, ngroups, gmuid); | |
1024 | if (new_cred != my_cred) | |
1025 | persona->pna_cred = new_cred; | |
1026 | kauth_cred_unref(&my_cred); | |
1027 | ||
1028 | out_unlock: | |
1029 | persona_unlock(persona); | |
1030 | return ret; | |
1031 | } | |
1032 | ||
1033 | int persona_get_groups(struct persona *persona, int *ngroups, gid_t *groups, int groups_sz) | |
1034 | { | |
1035 | int ret = EINVAL; | |
1036 | if (!persona || !persona->pna_cred || !groups || !ngroups) | |
1037 | return EINVAL; | |
1038 | ||
1039 | *ngroups = groups_sz; | |
1040 | ||
1041 | persona_lock(persona); | |
1042 | if (persona_valid(persona)) { | |
1043 | kauth_cred_getgroups(persona->pna_cred, groups, ngroups); | |
1044 | ret = 0; | |
1045 | } | |
1046 | persona_unlock(persona); | |
1047 | ||
1048 | return ret; | |
1049 | } | |
1050 | ||
1051 | uid_t persona_get_gmuid(struct persona *persona) | |
1052 | { | |
1053 | uid_t gmuid = KAUTH_UID_NONE; | |
1054 | ||
1055 | if (!persona || !persona->pna_cred) | |
1056 | return gmuid; | |
1057 | ||
1058 | persona_lock(persona); | |
1059 | if (!persona_valid(persona)) | |
1060 | goto out_unlock; | |
1061 | ||
1062 | posix_cred_t pcred = posix_cred_get(persona->pna_cred); | |
1063 | gmuid = pcred->cr_gmuid; | |
1064 | ||
1065 | out_unlock: | |
1066 | persona_unlock(persona); | |
1067 | return gmuid; | |
1068 | } | |
1069 | ||
1070 | int persona_get_login(struct persona *persona, char login[MAXLOGNAME+1]) | |
1071 | { | |
1072 | int ret = EINVAL; | |
1073 | if (!persona || !persona->pna_cred) | |
1074 | return EINVAL; | |
1075 | ||
1076 | persona_lock(persona); | |
1077 | if (!persona_valid(persona)) | |
1078 | goto out_unlock; | |
1079 | ||
1080 | strlcpy(login, persona->pna_login, MAXLOGNAME); | |
1081 | ret = 0; | |
1082 | ||
1083 | out_unlock: | |
1084 | persona_unlock(persona); | |
1085 | login[MAXLOGNAME] = 0; | |
1086 | ||
1087 | return ret; | |
1088 | } | |
1089 | ||
1090 | #else /* !CONFIG_PERSONAS */ | |
1091 | ||
1092 | /* | |
1093 | * symbol exports for kext compatibility | |
1094 | */ | |
1095 | ||
1096 | uid_t persona_get_id(__unused struct persona *persona) | |
1097 | { | |
1098 | return PERSONA_ID_NONE; | |
1099 | } | |
1100 | ||
1101 | int persona_get_type(__unused struct persona *persona) | |
1102 | { | |
1103 | return PERSONA_INVALID; | |
1104 | } | |
1105 | ||
1106 | kauth_cred_t persona_get_cred(__unused struct persona *persona) | |
1107 | { | |
1108 | return NULL; | |
1109 | } | |
1110 | ||
1111 | struct persona *persona_lookup(__unused uid_t id) | |
1112 | { | |
1113 | return NULL; | |
1114 | } | |
1115 | ||
1116 | int persona_find(__unused const char *login, | |
1117 | __unused uid_t uid, | |
1118 | __unused struct persona **persona, | |
1119 | __unused size_t *plen) | |
1120 | { | |
1121 | return ENOTSUP; | |
1122 | } | |
1123 | ||
1124 | struct persona *current_persona_get(void) | |
1125 | { | |
1126 | return NULL; | |
1127 | } | |
1128 | ||
1129 | struct persona *persona_get(struct persona *persona) | |
1130 | { | |
1131 | return persona; | |
1132 | } | |
1133 | ||
1134 | void persona_put(__unused struct persona *persona) | |
1135 | { | |
1136 | return; | |
1137 | } | |
1138 | #endif |