]>
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 | ||
490019cf A |
287 | static struct persona *persona_get_locked(struct persona *persona) |
288 | { | |
289 | if (persona->pna_refcount) { | |
290 | persona->pna_refcount++; | |
291 | return persona; | |
292 | } | |
293 | return NULL; | |
294 | } | |
295 | ||
296 | struct persona *persona_get(struct persona *persona) | |
297 | { | |
298 | struct persona *ret; | |
299 | if (!persona) | |
300 | return NULL; | |
301 | persona_lock(persona); | |
302 | ret = persona_get_locked(persona); | |
303 | persona_unlock(persona); | |
304 | ||
305 | return ret; | |
306 | } | |
307 | ||
308 | void persona_put(struct persona *persona) | |
309 | { | |
310 | int destroy = 0; | |
311 | ||
312 | if (!persona) | |
313 | return; | |
314 | ||
315 | persona_lock(persona); | |
316 | if (persona->pna_refcount >= 0) { | |
317 | if (--(persona->pna_refcount) == 0) | |
318 | destroy = 1; | |
319 | } | |
320 | persona_unlock(persona); | |
321 | ||
322 | if (!destroy) | |
323 | return; | |
324 | ||
325 | persona_dbg("Destroying persona %s", persona_desc(persona, 0)); | |
326 | ||
327 | /* release our credential reference */ | |
328 | if (persona->pna_cred) | |
329 | kauth_cred_unref(&persona->pna_cred); | |
330 | ||
331 | /* remove it from the global list and decrement the count */ | |
332 | lock_personas(); | |
a39ff7e2 | 333 | persona_lock(persona); |
490019cf A |
334 | if (persona_valid(persona)) { |
335 | LIST_REMOVE(persona, pna_list); | |
336 | if (hw_atomic_add(&g_total_personas, -1) == UINT_MAX) | |
337 | panic("persona count underflow!\n"); | |
338 | persona_mkinvalid(persona); | |
339 | } | |
a39ff7e2 | 340 | persona_unlock(persona); |
490019cf A |
341 | unlock_personas(); |
342 | ||
343 | assert(LIST_EMPTY(&persona->pna_members)); | |
344 | memset(persona, 0, sizeof(*persona)); | |
345 | zfree(persona_zone, persona); | |
346 | } | |
347 | ||
348 | uid_t persona_get_id(struct persona *persona) | |
349 | { | |
350 | if (persona) | |
351 | return persona->pna_id; | |
352 | return PERSONA_ID_NONE; | |
353 | } | |
354 | ||
355 | struct persona *persona_lookup(uid_t id) | |
356 | { | |
357 | struct persona *persona, *tmp; | |
358 | ||
359 | persona = NULL; | |
360 | ||
361 | /* | |
362 | * simple, linear lookup for now: there shouldn't be too many | |
363 | * of these in memory at any given time. | |
364 | */ | |
365 | lock_personas(); | |
366 | LIST_FOREACH(tmp, &all_personas, pna_list) { | |
367 | persona_lock(tmp); | |
368 | if (tmp->pna_id == id && persona_valid(tmp)) { | |
369 | persona = persona_get_locked(tmp); | |
370 | persona_unlock(tmp); | |
371 | break; | |
372 | } | |
373 | persona_unlock(tmp); | |
374 | } | |
375 | unlock_personas(); | |
376 | ||
377 | return persona; | |
378 | } | |
379 | ||
a39ff7e2 A |
380 | struct persona *persona_lookup_and_invalidate(uid_t id) |
381 | { | |
382 | struct persona *persona, *entry, *tmp; | |
383 | ||
384 | persona = NULL; | |
385 | ||
386 | lock_personas(); | |
387 | LIST_FOREACH_SAFE(entry, &all_personas, pna_list, tmp) { | |
388 | persona_lock(entry); | |
389 | if (entry->pna_id == id) { | |
390 | if (persona_valid(entry)) { | |
391 | persona = persona_get_locked(entry); | |
392 | assert(persona != NULL); | |
393 | LIST_REMOVE(persona, pna_list); | |
394 | if (hw_atomic_add(&g_total_personas, -1) == UINT_MAX) | |
395 | panic("persona ref count underflow!\n"); | |
396 | persona_mkinvalid(persona); | |
397 | } | |
398 | persona_unlock(entry); | |
399 | break; | |
400 | } | |
401 | persona_unlock(entry); | |
402 | } | |
403 | unlock_personas(); | |
404 | ||
405 | return persona; | |
406 | } | |
407 | ||
490019cf A |
408 | int persona_find(const char *login, uid_t uid, |
409 | struct persona **persona, size_t *plen) | |
410 | { | |
411 | struct persona *tmp; | |
412 | int match = 0; | |
413 | size_t found = 0; | |
414 | ||
415 | if (login) | |
416 | match++; | |
417 | if (uid != PERSONA_ID_NONE) | |
418 | match++; | |
419 | ||
420 | if (match == 0) | |
421 | return EINVAL; | |
422 | ||
423 | persona_dbg("Searching with %d parameters (l:\"%s\", u:%d)", | |
424 | match, login, uid); | |
425 | ||
426 | lock_personas(); | |
427 | LIST_FOREACH(tmp, &all_personas, pna_list) { | |
428 | int m = 0; | |
429 | persona_lock(tmp); | |
430 | if (login && strncmp(tmp->pna_login, login, sizeof(tmp->pna_login)) == 0) | |
431 | m++; | |
432 | if (uid != PERSONA_ID_NONE && uid == tmp->pna_id) | |
433 | m++; | |
434 | if (m == match) { | |
435 | if (persona && *plen > found) | |
436 | persona[found] = persona_get_locked(tmp); | |
437 | found++; | |
438 | } | |
439 | #ifdef PERSONA_DEBUG | |
440 | if (m > 0) | |
441 | persona_dbg("ID:%d Matched %d/%d, found:%d, *plen:%d", | |
442 | tmp->pna_id, m, match, (int)found, (int)*plen); | |
443 | #endif | |
444 | persona_unlock(tmp); | |
445 | } | |
446 | unlock_personas(); | |
447 | ||
448 | *plen = found; | |
449 | if (!found) | |
450 | return ESRCH; | |
451 | return 0; | |
452 | } | |
453 | ||
454 | struct persona *persona_proc_get(pid_t pid) | |
455 | { | |
456 | struct persona *persona; | |
457 | proc_t p = proc_find(pid); | |
458 | ||
459 | if (!p) | |
460 | return NULL; | |
461 | ||
462 | proc_lock(p); | |
463 | persona = persona_get(p->p_persona); | |
464 | proc_unlock(p); | |
465 | ||
466 | proc_rele(p); | |
467 | ||
468 | return persona; | |
469 | } | |
470 | ||
471 | struct persona *current_persona_get(void) | |
472 | { | |
473 | proc_t p = current_proc(); | |
474 | struct persona *persona; | |
475 | ||
476 | proc_lock(p); | |
477 | persona = persona_get(p->p_persona); | |
478 | proc_unlock(p); | |
479 | ||
480 | return persona; | |
481 | } | |
482 | ||
483 | /** | |
484 | * inherit a persona from parent to child | |
485 | */ | |
486 | int persona_proc_inherit(proc_t child, proc_t parent) | |
487 | { | |
488 | if (child->p_persona != NULL) { | |
489 | persona_dbg("proc_inherit: child already in persona: %s", | |
490 | persona_desc(child->p_persona, 0)); | |
491 | return -1; | |
492 | } | |
493 | ||
494 | /* no persona to inherit */ | |
495 | if (parent->p_persona == NULL) | |
496 | return 0; | |
497 | ||
498 | return persona_proc_adopt(child, parent->p_persona, parent->p_ucred); | |
499 | } | |
500 | ||
501 | int persona_proc_adopt_id(proc_t p, uid_t id, kauth_cred_t auth_override) | |
502 | { | |
503 | int ret; | |
504 | struct persona *persona; | |
505 | ||
506 | persona = persona_lookup(id); | |
507 | if (!persona) | |
508 | return ESRCH; | |
509 | ||
510 | ret = persona_proc_adopt(p, persona, auth_override); | |
511 | ||
512 | /* put the reference from the lookup() */ | |
513 | persona_put(persona); | |
514 | ||
515 | return ret; | |
516 | } | |
517 | ||
518 | ||
519 | typedef enum e_persona_reset_op { | |
520 | PROC_REMOVE_PERSONA = 1, | |
521 | PROC_RESET_OLD_PERSONA = 2, | |
522 | } persona_reset_op_t; | |
523 | ||
524 | /* | |
525 | * internal cleanup routine for proc_set_cred_internal | |
526 | * | |
527 | */ | |
528 | static struct persona *proc_reset_persona_internal(proc_t p, persona_reset_op_t op, | |
529 | struct persona *old_persona, | |
530 | struct persona *new_persona) | |
531 | { | |
532 | #if (DEVELOPMENT || DEBUG) | |
533 | persona_lock_assert_held(new_persona); | |
534 | #endif | |
535 | ||
536 | switch (op) { | |
537 | case PROC_REMOVE_PERSONA: | |
538 | old_persona = p->p_persona; | |
539 | /* fall through */ | |
540 | case PROC_RESET_OLD_PERSONA: | |
541 | break; | |
542 | default: | |
543 | /* invalid arguments */ | |
544 | return NULL; | |
545 | } | |
546 | ||
547 | /* unlock the new persona (locked on entry) */ | |
548 | persona_unlock(new_persona); | |
549 | /* lock the old persona and the process */ | |
550 | persona_lock(old_persona); | |
551 | proc_lock(p); | |
552 | ||
553 | switch (op) { | |
554 | case PROC_REMOVE_PERSONA: | |
555 | LIST_REMOVE(p, p_persona_list); | |
556 | p->p_persona = NULL; | |
557 | break; | |
558 | case PROC_RESET_OLD_PERSONA: | |
559 | p->p_persona = old_persona; | |
560 | LIST_INSERT_HEAD(&old_persona->pna_members, p, p_persona_list); | |
561 | break; | |
562 | } | |
563 | ||
564 | proc_unlock(p); | |
565 | persona_unlock(old_persona); | |
566 | ||
567 | /* re-lock the new persona */ | |
568 | persona_lock(new_persona); | |
569 | return old_persona; | |
570 | } | |
571 | ||
572 | /* | |
573 | * Assumes persona is locked. | |
574 | * On success, takes a reference to 'persona' and returns the | |
575 | * previous persona the process had adopted. The caller is | |
576 | * responsible to release the reference. | |
577 | */ | |
578 | static struct persona *proc_set_cred_internal(proc_t p, struct persona *persona, | |
579 | kauth_cred_t auth_override, int *rlim_error) | |
580 | { | |
581 | struct persona *old_persona = NULL; | |
582 | kauth_cred_t my_cred, my_new_cred; | |
583 | uid_t old_uid, new_uid; | |
584 | int count; | |
585 | ||
586 | /* | |
587 | * This operation must be done under the proc trans lock | |
588 | * by the thread which took the trans lock! | |
589 | */ | |
590 | assert(((p->p_lflag & P_LINTRANSIT) == P_LINTRANSIT) && | |
591 | p->p_transholder == current_thread()); | |
592 | assert(persona != NULL); | |
593 | ||
594 | /* no work to do if we "re-adopt" the same persona */ | |
595 | if (p->p_persona == persona) | |
596 | return NULL; | |
597 | ||
598 | /* | |
599 | * If p is in a persona, then we need to remove 'p' from the list of | |
600 | * processes in that persona. To do this, we need to drop the lock | |
601 | * held on the incoming (new) persona and lock the old one. | |
602 | */ | |
603 | if (p->p_persona) { | |
604 | old_persona = proc_reset_persona_internal(p, PROC_REMOVE_PERSONA, | |
605 | NULL, persona); | |
606 | } | |
607 | ||
608 | if (auth_override) | |
609 | my_new_cred = auth_override; | |
610 | else | |
611 | my_new_cred = persona->pna_cred; | |
612 | ||
613 | if (!my_new_cred) | |
614 | panic("NULL credentials (persona:%p)", persona); | |
615 | ||
616 | *rlim_error = 0; | |
617 | ||
618 | kauth_cred_ref(my_new_cred); | |
619 | ||
620 | new_uid = persona->pna_id; | |
621 | ||
622 | /* | |
623 | * Check to see if we will hit a proc rlimit by moving the process | |
624 | * into the persona. If so, we'll bail early before actually moving | |
625 | * the process or changing its credentials. | |
626 | */ | |
627 | if (new_uid != 0 && | |
628 | (rlim_t)chgproccnt(new_uid, 0) > p->p_rlimit[RLIMIT_NPROC].rlim_cur) { | |
629 | pna_err("PID:%d hit proc rlimit in new persona(%d): %s", | |
630 | p->p_pid, new_uid, persona_desc(persona, 1)); | |
631 | *rlim_error = EACCES; | |
632 | (void)proc_reset_persona_internal(p, PROC_RESET_OLD_PERSONA, | |
633 | old_persona, persona); | |
634 | kauth_cred_unref(&my_new_cred); | |
635 | return NULL; | |
636 | } | |
637 | ||
638 | /* | |
639 | * Set the new credentials on the proc | |
640 | */ | |
641 | set_proc_cred: | |
642 | my_cred = kauth_cred_proc_ref(p); | |
643 | persona_dbg("proc_adopt PID:%d, %s -> %s", | |
644 | p->p_pid, | |
645 | persona_desc(old_persona, 1), | |
646 | persona_desc(persona, 1)); | |
647 | ||
648 | old_uid = kauth_cred_getruid(my_cred); | |
649 | ||
650 | if (my_cred != my_new_cred) { | |
651 | kauth_cred_t old_cred = my_cred; | |
652 | ||
653 | proc_ucred_lock(p); | |
654 | /* | |
655 | * We need to protect against a race where another thread | |
656 | * also changed the credential after we took our | |
657 | * reference. If p_ucred has changed then we should | |
658 | * restart this again with the new cred. | |
659 | */ | |
660 | if (p->p_ucred != my_cred) { | |
661 | proc_ucred_unlock(p); | |
662 | kauth_cred_unref(&my_cred); | |
663 | /* try again */ | |
664 | goto set_proc_cred; | |
665 | } | |
666 | ||
667 | /* update the credential and take a ref for the proc */ | |
668 | kauth_cred_ref(my_new_cred); | |
669 | p->p_ucred = my_new_cred; | |
670 | ||
671 | /* update cred on proc (and current thread) */ | |
672 | mach_kauth_cred_uthread_update(); | |
673 | PROC_UPDATE_CREDS_ONPROC(p); | |
674 | ||
675 | /* drop the proc's old ref on the credential */ | |
676 | kauth_cred_unref(&old_cred); | |
677 | proc_ucred_unlock(p); | |
678 | } | |
679 | ||
680 | /* drop this function's reference to the old cred */ | |
681 | kauth_cred_unref(&my_cred); | |
682 | ||
683 | /* | |
684 | * Update the proc count. | |
685 | * If the UIDs are the same, then there is no work to do. | |
686 | */ | |
687 | if (old_persona) | |
688 | old_uid = old_persona->pna_id; | |
689 | ||
690 | if (new_uid != old_uid) { | |
691 | count = chgproccnt(old_uid, -1); | |
692 | persona_dbg("Decrement %s:%d proc_count to: %d", | |
693 | old_persona ? "Persona" : "UID", old_uid, count); | |
694 | ||
695 | /* | |
696 | * Increment the proc count on the UID associated with | |
697 | * the new persona. Enforce the resource limit just | |
698 | * as in fork1() | |
699 | */ | |
700 | count = chgproccnt(new_uid, 1); | |
701 | persona_dbg("Increment Persona:%d (UID:%d) proc_count to: %d", | |
702 | new_uid, kauth_cred_getuid(my_new_cred), count); | |
703 | } | |
704 | ||
705 | OSBitOrAtomic(P_ADOPTPERSONA, &p->p_flag); | |
706 | ||
707 | proc_lock(p); | |
708 | p->p_persona = persona_get_locked(persona); | |
709 | LIST_INSERT_HEAD(&persona->pna_members, p, p_persona_list); | |
710 | proc_unlock(p); | |
711 | ||
712 | kauth_cred_unref(&my_new_cred); | |
713 | ||
714 | return old_persona; | |
715 | } | |
716 | ||
717 | int persona_proc_adopt(proc_t p, struct persona *persona, kauth_cred_t auth_override) | |
718 | { | |
719 | int error; | |
720 | struct persona *old_persona; | |
721 | struct session * sessp; | |
722 | ||
723 | if (!persona) | |
724 | return EINVAL; | |
725 | ||
726 | persona_dbg("%d adopting Persona %d (%s)", proc_pid(p), | |
727 | persona->pna_id, persona_desc(persona, 0)); | |
728 | ||
729 | persona_lock(persona); | |
730 | if (!persona->pna_cred || !persona_valid(persona)) { | |
731 | persona_dbg("Invalid persona (%s): NULL credentials!", persona_desc(persona, 1)); | |
732 | persona_unlock(persona); | |
733 | return EINVAL; | |
734 | } | |
735 | ||
736 | /* the persona credentials can no longer be adjusted */ | |
737 | persona->pna_cred_locked = 1; | |
738 | ||
739 | /* | |
740 | * assume the persona: this may drop and re-acquire the persona lock! | |
741 | */ | |
742 | error = 0; | |
743 | old_persona = proc_set_cred_internal(p, persona, auth_override, &error); | |
744 | ||
745 | /* join the process group associated with the persona */ | |
746 | if (persona->pna_pgid) { | |
747 | uid_t uid = kauth_cred_getuid(persona->pna_cred); | |
748 | persona_dbg(" PID:%d, pgid:%d%s", | |
749 | p->p_pid, persona->pna_pgid, | |
750 | persona->pna_pgid == uid ? ", new_session" : "."); | |
751 | enterpgrp(p, persona->pna_pgid, persona->pna_pgid == uid); | |
752 | } | |
753 | ||
754 | /* set the login name of the session */ | |
755 | sessp = proc_session(p); | |
756 | if (sessp != SESSION_NULL) { | |
757 | session_lock(sessp); | |
758 | bcopy(persona->pna_login, sessp->s_login, MAXLOGNAME); | |
759 | session_unlock(sessp); | |
760 | session_rele(sessp); | |
761 | } | |
762 | ||
763 | persona_unlock(persona); | |
764 | ||
765 | set_security_token(p); | |
766 | ||
767 | /* | |
768 | * Drop the reference to the old persona. | |
769 | */ | |
770 | if (old_persona) | |
771 | persona_put(old_persona); | |
772 | ||
773 | persona_dbg("%s", error == 0 ? "SUCCESS" : "FAILED"); | |
774 | return error; | |
775 | } | |
776 | ||
777 | int persona_proc_drop(proc_t p) | |
778 | { | |
779 | struct persona *persona = NULL; | |
780 | ||
781 | persona_dbg("PID:%d, %s -> <none>", p->p_pid, persona_desc(p->p_persona, 0)); | |
782 | ||
783 | /* | |
784 | * There are really no other credentials for us to assume, | |
785 | * so we'll just continue running with the credentials | |
786 | * we got from the persona. | |
787 | */ | |
788 | ||
789 | /* | |
790 | * the locks must be taken in reverse order here, so | |
791 | * we have to be careful not to cause deadlock | |
792 | */ | |
793 | try_again: | |
794 | proc_lock(p); | |
795 | if (p->p_persona) { | |
796 | uid_t puid, ruid; | |
797 | if (!persona_try_lock(p->p_persona)) { | |
798 | proc_unlock(p); | |
799 | mutex_pause(0); /* back-off time */ | |
800 | goto try_again; | |
801 | } | |
802 | persona = p->p_persona; | |
803 | LIST_REMOVE(p, p_persona_list); | |
804 | p->p_persona = NULL; | |
805 | ||
806 | ruid = kauth_cred_getruid(p->p_ucred); | |
807 | puid = kauth_cred_getuid(persona->pna_cred); | |
808 | proc_unlock(p); | |
809 | (void)chgproccnt(ruid, 1); | |
810 | (void)chgproccnt(puid, -1); | |
811 | } else { | |
812 | proc_unlock(p); | |
813 | } | |
814 | ||
815 | /* | |
816 | * if the proc had a persona, then it is still locked here | |
817 | * (preserving proper lock ordering) | |
818 | */ | |
819 | ||
820 | if (persona) { | |
821 | persona_unlock(persona); | |
822 | persona_put(persona); | |
823 | } | |
824 | ||
825 | return 0; | |
826 | } | |
827 | ||
828 | int persona_get_type(struct persona *persona) | |
829 | { | |
830 | int type; | |
831 | ||
832 | if (!persona) | |
833 | return PERSONA_INVALID; | |
834 | ||
835 | persona_lock(persona); | |
836 | if (!persona_valid(persona)) { | |
837 | persona_unlock(persona); | |
838 | return PERSONA_INVALID; | |
839 | } | |
840 | type = persona->pna_type; | |
841 | persona_unlock(persona); | |
842 | ||
843 | return type; | |
844 | } | |
845 | ||
846 | int persona_set_cred(struct persona *persona, kauth_cred_t cred) | |
847 | { | |
848 | int ret = 0; | |
849 | kauth_cred_t my_cred; | |
850 | if (!persona || !cred) | |
851 | return EINVAL; | |
852 | ||
853 | persona_lock(persona); | |
854 | if (!persona_valid(persona)) { | |
855 | ret = EINVAL; | |
856 | goto out_unlock; | |
857 | } | |
858 | if (persona->pna_cred_locked) { | |
859 | ret = EPERM; | |
860 | goto out_unlock; | |
861 | } | |
862 | ||
863 | /* create a new cred from the passed-in cred */ | |
864 | my_cred = kauth_cred_create(cred); | |
865 | ||
866 | /* ensure that the UID matches the persona ID */ | |
867 | my_cred = kauth_cred_setresuid(my_cred, persona->pna_id, | |
868 | persona->pna_id, persona->pna_id, | |
869 | KAUTH_UID_NONE); | |
870 | ||
871 | /* TODO: clear the saved GID?! */ | |
872 | ||
873 | /* replace the persona's cred with the new one */ | |
874 | if (persona->pna_cred) | |
875 | kauth_cred_unref(&persona->pna_cred); | |
876 | persona->pna_cred = my_cred; | |
877 | ||
878 | out_unlock: | |
879 | persona_unlock(persona); | |
880 | return ret; | |
881 | } | |
882 | ||
883 | int persona_set_cred_from_proc(struct persona *persona, proc_t proc) | |
884 | { | |
885 | int ret = 0; | |
886 | kauth_cred_t parent_cred, my_cred; | |
887 | if (!persona || !proc) | |
888 | return EINVAL; | |
889 | ||
890 | persona_lock(persona); | |
891 | if (!persona_valid(persona)) { | |
892 | ret = EINVAL; | |
893 | goto out_unlock; | |
894 | } | |
895 | if (persona->pna_cred_locked) { | |
896 | ret = EPERM; | |
897 | goto out_unlock; | |
898 | } | |
899 | ||
900 | parent_cred = kauth_cred_proc_ref(proc); | |
901 | ||
902 | /* TODO: clear the saved UID/GID! */ | |
903 | ||
904 | /* create a new cred from the proc's cred */ | |
905 | my_cred = kauth_cred_create(parent_cred); | |
906 | ||
907 | /* ensure that the UID matches the persona ID */ | |
908 | my_cred = kauth_cred_setresuid(my_cred, persona->pna_id, | |
909 | persona->pna_id, persona->pna_id, | |
910 | KAUTH_UID_NONE); | |
911 | ||
912 | /* replace the persona's cred with the new one */ | |
913 | if (persona->pna_cred) | |
914 | kauth_cred_unref(&persona->pna_cred); | |
915 | persona->pna_cred = my_cred; | |
916 | ||
917 | kauth_cred_unref(&parent_cred); | |
918 | ||
919 | out_unlock: | |
920 | persona_unlock(persona); | |
921 | return ret; | |
922 | } | |
923 | ||
924 | kauth_cred_t persona_get_cred(struct persona *persona) | |
925 | { | |
926 | kauth_cred_t cred = NULL; | |
927 | ||
928 | if (!persona) | |
929 | return NULL; | |
930 | ||
931 | persona_lock(persona); | |
932 | if (!persona_valid(persona)) | |
933 | goto out_unlock; | |
934 | ||
935 | if (persona->pna_cred) { | |
936 | kauth_cred_ref(persona->pna_cred); | |
937 | cred = persona->pna_cred; | |
938 | } | |
939 | ||
940 | out_unlock: | |
941 | persona_unlock(persona); | |
942 | ||
943 | return cred; | |
944 | } | |
945 | ||
946 | uid_t persona_get_uid(struct persona *persona) | |
947 | { | |
948 | uid_t uid = UID_MAX; | |
949 | ||
950 | if (!persona || !persona->pna_cred) | |
951 | return UID_MAX; | |
952 | ||
953 | persona_lock(persona); | |
954 | if (persona_valid(persona)) { | |
955 | uid = kauth_cred_getuid(persona->pna_cred); | |
956 | assert(uid == persona->pna_id); | |
957 | } | |
958 | persona_unlock(persona); | |
959 | ||
960 | return uid; | |
961 | } | |
962 | ||
963 | int persona_set_gid(struct persona *persona, gid_t gid) | |
964 | { | |
965 | int ret = 0; | |
966 | kauth_cred_t my_cred, new_cred; | |
967 | ||
968 | if (!persona || !persona->pna_cred) | |
969 | return EINVAL; | |
970 | ||
971 | persona_lock(persona); | |
972 | if (!persona_valid(persona)) { | |
973 | ret = EINVAL; | |
974 | goto out_unlock; | |
975 | } | |
976 | if (persona->pna_cred_locked) { | |
977 | ret = EPERM; | |
978 | goto out_unlock; | |
979 | } | |
980 | ||
981 | my_cred = persona->pna_cred; | |
982 | kauth_cred_ref(my_cred); | |
983 | new_cred = kauth_cred_setresgid(my_cred, gid, gid, gid); | |
984 | if (new_cred != my_cred) | |
985 | persona->pna_cred = new_cred; | |
986 | kauth_cred_unref(&my_cred); | |
987 | ||
988 | out_unlock: | |
989 | persona_unlock(persona); | |
990 | return ret; | |
991 | } | |
992 | ||
993 | gid_t persona_get_gid(struct persona *persona) | |
994 | { | |
995 | gid_t gid = GID_MAX; | |
996 | ||
997 | if (!persona || !persona->pna_cred) | |
998 | return GID_MAX; | |
999 | ||
1000 | persona_lock(persona); | |
1001 | if (persona_valid(persona)) | |
1002 | gid = kauth_cred_getgid(persona->pna_cred); | |
1003 | persona_unlock(persona); | |
1004 | ||
1005 | return gid; | |
1006 | } | |
1007 | ||
527f9951 | 1008 | int persona_set_groups(struct persona *persona, gid_t *groups, unsigned ngroups, uid_t gmuid) |
490019cf A |
1009 | { |
1010 | int ret = 0; | |
1011 | kauth_cred_t my_cred, new_cred; | |
1012 | ||
1013 | if (!persona || !persona->pna_cred) | |
1014 | return EINVAL; | |
1015 | if (ngroups > NGROUPS_MAX) | |
1016 | return EINVAL; | |
1017 | ||
1018 | persona_lock(persona); | |
1019 | if (!persona_valid(persona)) { | |
1020 | ret = EINVAL; | |
1021 | goto out_unlock; | |
1022 | } | |
1023 | if (persona->pna_cred_locked) { | |
1024 | ret = EPERM; | |
1025 | goto out_unlock; | |
1026 | } | |
1027 | ||
1028 | my_cred = persona->pna_cred; | |
1029 | kauth_cred_ref(my_cred); | |
527f9951 | 1030 | new_cred = kauth_cred_setgroups(my_cred, groups, (int)ngroups, gmuid); |
490019cf A |
1031 | if (new_cred != my_cred) |
1032 | persona->pna_cred = new_cred; | |
1033 | kauth_cred_unref(&my_cred); | |
1034 | ||
1035 | out_unlock: | |
1036 | persona_unlock(persona); | |
1037 | return ret; | |
1038 | } | |
1039 | ||
527f9951 | 1040 | int persona_get_groups(struct persona *persona, unsigned *ngroups, gid_t *groups, unsigned groups_sz) |
490019cf A |
1041 | { |
1042 | int ret = EINVAL; | |
527f9951 | 1043 | if (!persona || !persona->pna_cred || !groups || !ngroups || groups_sz > NGROUPS) |
490019cf A |
1044 | return EINVAL; |
1045 | ||
1046 | *ngroups = groups_sz; | |
1047 | ||
1048 | persona_lock(persona); | |
1049 | if (persona_valid(persona)) { | |
527f9951 A |
1050 | int kauth_ngroups = (int)groups_sz; |
1051 | kauth_cred_getgroups(persona->pna_cred, groups, &kauth_ngroups); | |
1052 | *ngroups = (unsigned)kauth_ngroups; | |
490019cf A |
1053 | ret = 0; |
1054 | } | |
1055 | persona_unlock(persona); | |
1056 | ||
1057 | return ret; | |
1058 | } | |
1059 | ||
1060 | uid_t persona_get_gmuid(struct persona *persona) | |
1061 | { | |
1062 | uid_t gmuid = KAUTH_UID_NONE; | |
1063 | ||
1064 | if (!persona || !persona->pna_cred) | |
1065 | return gmuid; | |
1066 | ||
1067 | persona_lock(persona); | |
1068 | if (!persona_valid(persona)) | |
1069 | goto out_unlock; | |
1070 | ||
1071 | posix_cred_t pcred = posix_cred_get(persona->pna_cred); | |
1072 | gmuid = pcred->cr_gmuid; | |
1073 | ||
1074 | out_unlock: | |
1075 | persona_unlock(persona); | |
1076 | return gmuid; | |
1077 | } | |
1078 | ||
1079 | int persona_get_login(struct persona *persona, char login[MAXLOGNAME+1]) | |
1080 | { | |
1081 | int ret = EINVAL; | |
1082 | if (!persona || !persona->pna_cred) | |
1083 | return EINVAL; | |
1084 | ||
1085 | persona_lock(persona); | |
1086 | if (!persona_valid(persona)) | |
1087 | goto out_unlock; | |
1088 | ||
1089 | strlcpy(login, persona->pna_login, MAXLOGNAME); | |
1090 | ret = 0; | |
1091 | ||
1092 | out_unlock: | |
1093 | persona_unlock(persona); | |
1094 | login[MAXLOGNAME] = 0; | |
1095 | ||
1096 | return ret; | |
1097 | } | |
1098 | ||
1099 | #else /* !CONFIG_PERSONAS */ | |
1100 | ||
1101 | /* | |
1102 | * symbol exports for kext compatibility | |
1103 | */ | |
1104 | ||
1105 | uid_t persona_get_id(__unused struct persona *persona) | |
1106 | { | |
1107 | return PERSONA_ID_NONE; | |
1108 | } | |
1109 | ||
1110 | int persona_get_type(__unused struct persona *persona) | |
1111 | { | |
1112 | return PERSONA_INVALID; | |
1113 | } | |
1114 | ||
1115 | kauth_cred_t persona_get_cred(__unused struct persona *persona) | |
1116 | { | |
1117 | return NULL; | |
1118 | } | |
1119 | ||
1120 | struct persona *persona_lookup(__unused uid_t id) | |
1121 | { | |
1122 | return NULL; | |
1123 | } | |
1124 | ||
1125 | int persona_find(__unused const char *login, | |
1126 | __unused uid_t uid, | |
1127 | __unused struct persona **persona, | |
1128 | __unused size_t *plen) | |
1129 | { | |
1130 | return ENOTSUP; | |
1131 | } | |
1132 | ||
1133 | struct persona *current_persona_get(void) | |
1134 | { | |
1135 | return NULL; | |
1136 | } | |
1137 | ||
1138 | struct persona *persona_get(struct persona *persona) | |
1139 | { | |
1140 | return persona; | |
1141 | } | |
1142 | ||
1143 | void persona_put(__unused struct persona *persona) | |
1144 | { | |
1145 | return; | |
1146 | } | |
1147 | #endif |