]>
Commit | Line | Data |
---|---|---|
b0d623f7 A |
1 | /*- |
2 | * Copyright (c) 2008-2009 Apple Inc. | |
3 | * All rights reserved. | |
4 | * | |
5 | * Redistribution and use in source and binary forms, with or without | |
6 | * modification, are permitted provided that the following conditions | |
7 | * are met: | |
8 | * 1. Redistributions of source code must retain the above copyright | |
9 | * notice, this list of conditions and the following disclaimer. | |
10 | * 2. Redistributions in binary form must reproduce the above copyright | |
11 | * notice, this list of conditions and the following disclaimer in the | |
12 | * documentation and/or other materials provided with the distribution. | |
13 | * 3. Neither the name of Apple Inc. ("Apple") nor the names of | |
14 | * its contributors may be used to endorse or promote products derived | |
15 | * from this software without specific prior written permission. | |
16 | * | |
17 | * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND | |
18 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
19 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
20 | * ARE DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR | |
21 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |
22 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS | |
23 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) | |
24 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, | |
25 | * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING | |
26 | * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
27 | * POSSIBILITY OF SUCH DAMAGE. | |
28 | */ | |
29 | ||
30 | #include <sys/kernel.h> | |
31 | #include <sys/event.h> | |
32 | #include <sys/kauth.h> | |
33 | #include <sys/queue.h> | |
34 | #include <sys/syscall.h> | |
35 | #include <sys/sysent.h> | |
36 | #include <sys/sysproto.h> | |
37 | #include <sys/systm.h> | |
38 | #include <sys/ucred.h> | |
39 | ||
40 | #include <libkern/OSAtomic.h> | |
41 | ||
42 | #include <bsm/audit.h> | |
43 | #include <security/audit/audit.h> | |
44 | #include <security/audit/audit_bsd.h> | |
45 | #include <security/audit/audit_private.h> | |
46 | ||
47 | #include <vm/vm_protos.h> | |
48 | #include <kern/audit_sessionport.h> | |
49 | ||
50 | kern_return_t ipc_object_copyin(ipc_space_t, mach_port_name_t, | |
51 | mach_msg_type_name_t, ipc_port_t *); | |
52 | void ipc_port_release_send(ipc_port_t); | |
53 | ||
54 | /* | |
55 | * The default auditinfo_addr entry for ucred. | |
56 | */ | |
57 | struct auditinfo_addr audit_default_aia = { | |
58 | .ai_auid = AU_DEFAUDITID, | |
59 | .ai_asid = AU_DEFAUDITSID, | |
60 | .ai_termid = { .at_type = AU_IPv4, }, | |
61 | }; | |
62 | ||
63 | #if CONFIG_AUDIT | |
64 | ||
65 | /* | |
66 | * Currently the hash table is a fixed size. | |
67 | */ | |
68 | #define HASH_TABLE_SIZE 97 | |
69 | #define HASH_ASID(asid) (audit_session_hash(asid) % HASH_TABLE_SIZE) | |
70 | ||
71 | /* | |
72 | * Audit Session Entry. This is treated as an object with public and private | |
73 | * data. The se_auinfo field is the only information that is public and | |
74 | * needs to be the first entry. | |
75 | */ | |
76 | struct au_sentry { | |
77 | auditinfo_addr_t se_auinfo; /* Public audit session data. */ | |
78 | #define se_asid se_auinfo.ai_asid | |
79 | #define se_auid se_auinfo.ai_auid | |
80 | #define se_mask se_auinfo.ai_mask | |
81 | #define se_termid se_auinfo.ai_termid | |
82 | #define se_flags se_auinfo.ai_flags | |
83 | ||
84 | long se_refcnt; /* Reference count. */ | |
85 | long se_procnt; /* Processes in session. */ | |
86 | ipc_port_t se_port; /* Session port. */ | |
87 | struct klist se_klist; /* Knotes for session */ | |
88 | struct mtx se_klist_mtx; /* se_klist mutex */ | |
89 | LIST_ENTRY(au_sentry) se_link; /* Hash bucket link list (1) */ | |
90 | }; | |
91 | typedef struct au_sentry au_sentry_t; | |
92 | ||
93 | #define AU_SENTRY_PTR(aia_p) ((au_sentry_t *)(aia_p)) | |
94 | ||
95 | static struct rwlock se_entry_lck; /* (1) lock for se_link above */ | |
96 | ||
97 | LIST_HEAD(au_sentry_head, au_sentry); | |
98 | static struct au_sentry_head *au_sentry_bucket = NULL; | |
99 | ||
100 | /* | |
101 | * Audit Propagation Knote List is a list of kevent knotes that are assosiated | |
102 | * with an any ASID knote. If the any ASID gets modified or deleted these are | |
103 | * modified or deleted as well. | |
104 | */ | |
105 | struct au_plist { | |
106 | struct knote *pl_knote; /* ptr to per-session knote */ | |
107 | LIST_ENTRY(au_plist) pl_link; /* list link (2) */ | |
108 | }; | |
109 | typedef struct au_plist au_plist_t; | |
110 | ||
111 | struct au_plisthead { | |
112 | struct rlck ph_rlck; /* (2) lock for pl_link list */ | |
113 | LIST_HEAD(au_plhead, au_plist) ph_head; /* list head */ | |
114 | }; | |
115 | typedef struct au_plisthead au_plisthead_t; | |
116 | ||
117 | #define EV_ANY_ASID EV_FLAG0 | |
118 | ||
119 | MALLOC_DEFINE(M_AU_SESSION, "audit_session", "Audit session data"); | |
120 | MALLOC_DEFINE(M_AU_EV_PLIST, "audit_ev_plist", "Audit session event plist"); | |
121 | ||
122 | /* | |
123 | * Kevent filters. | |
124 | */ | |
125 | static int audit_filt_sessionattach(struct knote *kn); | |
126 | static void audit_filt_sessiondetach(struct knote *kn); | |
127 | static void audit_filt_sessiontouch(struct knote *kn, | |
128 | struct kevent64_s *kev, long type); | |
129 | static int audit_filt_session(struct knote *kn, long hint); | |
130 | ||
131 | static void audit_register_kevents(uint32_t asid, uint32_t auid); | |
132 | ||
133 | struct filterops audit_session_filtops = { | |
134 | .f_attach = audit_filt_sessionattach, | |
135 | .f_detach = audit_filt_sessiondetach, | |
136 | .f_touch = audit_filt_sessiontouch, | |
137 | .f_event = audit_filt_session, | |
138 | }; | |
139 | ||
140 | /* | |
141 | * The klist for consumers that are interested in any session (ASID). This list | |
142 | * is not associated with any data structure but is used for registering | |
143 | * new kevents when sessions are created. This klist is lock by | |
144 | * anyas_klist_mtx. | |
145 | */ | |
146 | static struct klist anyas_klist; | |
147 | struct mtx anyas_klist_mtx; | |
148 | ||
149 | #define AUDIT_ANYAS_KLIST_LOCK_INIT() mtx_init(&anyas_klist_mtx, \ | |
150 | "audit anyas_klist_mtx", NULL, MTX_DEF) | |
151 | #define AUDIT_ANYAS_KLIST_LOCK() mtx_lock(&anyas_klist_mtx) | |
152 | #define AUDIT_ANYAS_KLIST_UNLOCK() mtx_unlock(&anyas_klist_mtx) | |
153 | #define AUDIT_ANYAS_KLIST_LOCK_ASSERT() mtx_assert(&anyas_klist_mtx, MA_OWNED) | |
154 | ||
155 | #define AUDIT_SENTRY_RWLOCK_INIT() rw_init(&se_entry_lck, \ | |
156 | "audit se_entry_lck") | |
157 | #define AUDIT_SENTRY_RLOCK() rw_rlock(&se_entry_lck) | |
158 | #define AUDIT_SENTRY_WLOCK() rw_wlock(&se_entry_lck) | |
159 | #define AUDIT_SENTRY_RWLOCK_ASSERT() rw_assert(&se_entry_lck, RA_LOCKED) | |
160 | #define AUDIT_SENTRY_RUNLOCK() rw_runlock(&se_entry_lck) | |
161 | #define AUDIT_SENTRY_WUNLOCK() rw_wunlock(&se_entry_lck) | |
162 | ||
163 | #define AUDIT_SE_KLIST_LOCK_INIT(se, n) mtx_init(&(se)->se_klist_mtx, \ | |
164 | n, NULL, MTX_DEF) | |
165 | #define AUDIT_SE_KLIST_LOCK(se) mtx_lock(&(se)->se_klist_mtx) | |
166 | #define AUDIT_SE_KLIST_UNLOCK(se) mtx_unlock(&(se)->se_klist_mtx) | |
167 | #define AUDIT_SE_KLIST_LOCK_DESTROY(se) mtx_destroy(&(se)->se_klist_mtx) | |
168 | #define AUDIT_SE_KLIST_LOCK_ASSERT(se) mtx_assert(&(se)->se_klist_mtx, \ | |
169 | MA_OWNED) | |
170 | ||
171 | #define AUDIT_PLIST_LOCK_INIT(pl) rlck_init(&(pl)->ph_rlck, \ | |
172 | "audit ph_rlck") | |
173 | #define AUDIT_PLIST_LOCK(pl) rlck_lock(&(pl)->ph_rlck) | |
174 | #define AUDIT_PLIST_UNLOCK(pl) rlck_unlock(&(pl)->ph_rlck) | |
175 | #define AUDIT_PLIST_LOCK_DESTROY(pl) rlck_destroy(&(pl)->ph_rlck) | |
176 | ||
177 | #if AUDIT_SESSION_DEBUG | |
178 | #include <kern/kalloc.h> | |
179 | ||
180 | struct au_sentry_debug { | |
181 | auditinfo_addr_t se_auinfo; | |
182 | long se_refcnt; | |
183 | long se_procnt; | |
184 | }; | |
185 | typedef struct au_sentry_debug au_sentry_debug_t; | |
186 | ||
187 | static int audit_sysctl_session_debug(struct sysctl_oid *oidp, void *arg1, | |
188 | int arg2, struct sysctl_req *req); | |
189 | ||
190 | SYSCTL_PROC(_kern, OID_AUTO, audit_session_debug, CTLFLAG_RD, NULL, 0, | |
191 | audit_sysctl_session_debug, "S,audit_session_debug", | |
192 | "Current session debug info for auditing."); | |
193 | ||
194 | /* | |
195 | * Copy out the session debug info via the sysctl interface. The userland code | |
196 | * is something like the following: | |
197 | * | |
198 | * error = sysctlbyname("kern.audit_session_debug", buffer_ptr, &buffer_len, | |
199 | * NULL, 0); | |
200 | */ | |
201 | static int | |
202 | audit_sysctl_session_debug(__unused struct sysctl_oid *oidp, | |
203 | __unused void *arg1, __unused int arg2, struct sysctl_req *req) | |
204 | { | |
205 | au_sentry_t *se; | |
206 | au_sentry_debug_t *sed_tab, *next_sed; | |
207 | int i, entry_cnt = 0; | |
208 | size_t sz; | |
209 | int err = 0; | |
210 | ||
211 | /* | |
212 | * This provides a read-only node. | |
213 | */ | |
214 | if (req->newptr != USER_ADDR_NULL) | |
215 | return (EPERM); | |
216 | ||
217 | /* | |
218 | * Walk the audit session hash table to determine the size. | |
219 | */ | |
220 | AUDIT_SENTRY_RLOCK(); | |
221 | for(i = 0; i < HASH_TABLE_SIZE; i++) | |
222 | LIST_FOREACH(se, &au_sentry_bucket[i], se_link) | |
223 | if (se != NULL) | |
224 | entry_cnt++; | |
225 | ||
226 | /* | |
227 | * If just querying then return the space required. There is an | |
228 | * obvious race condition here so we just fudge this by 3 in case | |
229 | * the audit session table grows. | |
230 | */ | |
231 | if (req->oldptr == USER_ADDR_NULL) { | |
232 | req->oldidx = (entry_cnt + 3) * sizeof(au_sentry_debug_t); | |
233 | AUDIT_SENTRY_RUNLOCK(); | |
234 | return (0); | |
235 | } | |
236 | ||
237 | /* | |
238 | * Alloc a temporary buffer. | |
239 | */ | |
240 | if (req->oldlen < (entry_cnt * sizeof(au_sentry_debug_t))) { | |
241 | AUDIT_SENTRY_RUNLOCK(); | |
242 | return (ENOMEM); | |
243 | } | |
244 | /* | |
245 | * We hold the lock over the alloc since we don't want the table to | |
246 | * grow on us. Therefore, use the non-blocking version of kalloc(). | |
247 | */ | |
248 | sed_tab = (au_sentry_debug_t *)kalloc_noblock(entry_cnt * | |
249 | sizeof(au_sentry_debug_t)); | |
250 | if (sed_tab == NULL) { | |
251 | AUDIT_SENTRY_RUNLOCK(); | |
252 | return (ENOMEM); | |
253 | } | |
254 | bzero(sed_tab, entry_cnt * sizeof(au_sentry_debug_t)); | |
255 | ||
256 | /* | |
257 | * Walk the audit session hash table and build the record array. | |
258 | */ | |
259 | sz = 0; | |
260 | next_sed = sed_tab; | |
261 | for(i = 0; i < HASH_TABLE_SIZE; i++) { | |
262 | LIST_FOREACH(se, &au_sentry_bucket[i], se_link) { | |
263 | if (se != NULL) { | |
264 | bcopy(se, next_sed, sizeof(next_sed)); | |
265 | next_sed++; | |
266 | sz += sizeof(au_sentry_debug_t); | |
267 | } | |
268 | } | |
269 | } | |
270 | AUDIT_SENTRY_RUNLOCK(); | |
271 | ||
272 | req->oldlen = sz; | |
273 | err = SYSCTL_OUT(req, sed_tab, sz); | |
274 | kfree(sed_tab, entry_cnt * sizeof(au_sentry_debug_t)); | |
275 | ||
276 | return (err); | |
277 | } | |
278 | ||
279 | #endif /* AUDIT_SESSION_DEBUG */ | |
280 | ||
281 | /* | |
282 | * Hash the audit session ID using a simple 32-bit mix. | |
283 | */ | |
284 | static inline uint32_t | |
285 | audit_session_hash(au_asid_t asid) | |
286 | { | |
287 | uint32_t a = (uint32_t) asid; | |
288 | ||
289 | a = (a - (a << 6)) ^ (a >> 17); | |
290 | a = (a - (a << 9)) ^ (a << 4); | |
291 | a = (a - (a << 3)) ^ (a << 10); | |
292 | a = a ^ (a >> 15); | |
293 | ||
294 | return (a); | |
295 | } | |
296 | ||
297 | /* | |
298 | * Do an hash lookup and find the session entry for a given ASID. Return NULL | |
299 | * if not found. | |
300 | */ | |
301 | static au_sentry_t * | |
302 | audit_session_find(au_asid_t asid) | |
303 | { | |
304 | uint32_t hkey; | |
305 | au_sentry_t *found_se; | |
306 | ||
307 | AUDIT_SENTRY_RWLOCK_ASSERT(); | |
308 | ||
309 | hkey = HASH_ASID(asid); | |
310 | ||
311 | LIST_FOREACH(found_se, &au_sentry_bucket[hkey], se_link) | |
312 | if (found_se->se_asid == asid) | |
313 | return (found_se); | |
314 | return (NULL); | |
315 | } | |
316 | ||
317 | /* | |
318 | * Call kqueue knote while holding the session entry klist lock. | |
319 | */ | |
320 | static void | |
321 | audit_session_knote(au_sentry_t *se, long hint) | |
322 | { | |
323 | ||
324 | AUDIT_SE_KLIST_LOCK(se); | |
325 | KNOTE(&se->se_klist, hint); | |
326 | AUDIT_SE_KLIST_UNLOCK(se); | |
327 | } | |
328 | ||
329 | /* | |
330 | * Remove the given audit_session entry from the hash table. | |
331 | */ | |
332 | static void | |
333 | audit_session_remove(au_sentry_t *se) | |
334 | { | |
335 | uint32_t hkey; | |
336 | au_sentry_t *found_se, *tmp_se; | |
337 | ||
338 | KASSERT(se->se_refcnt == 0, ("audit_session_remove: ref count != 0")); | |
339 | ||
340 | hkey = HASH_ASID(se->se_asid); | |
341 | ||
342 | AUDIT_SENTRY_WLOCK(); | |
343 | LIST_FOREACH_SAFE(found_se, &au_sentry_bucket[hkey], se_link, tmp_se) { | |
344 | if (found_se == se) { | |
345 | ||
346 | audit_session_knote(found_se, NOTE_AS_CLOSE); | |
347 | ||
348 | LIST_REMOVE(found_se, se_link); | |
349 | AUDIT_SENTRY_WUNLOCK(); | |
350 | AUDIT_SE_KLIST_LOCK_DESTROY(found_se); | |
351 | found_se->se_refcnt = 0; | |
352 | free(found_se, M_AU_SESSION); | |
353 | ||
354 | return; | |
355 | } | |
356 | } | |
357 | AUDIT_SENTRY_WUNLOCK(); | |
358 | } | |
359 | ||
360 | /* | |
361 | * Reference the session by incrementing the sentry ref count. | |
362 | */ | |
363 | static void | |
364 | audit_ref_session(au_sentry_t *se) | |
365 | { | |
366 | long old_val; | |
367 | ||
368 | old_val = OSAddAtomicLong(1, &se->se_refcnt); | |
369 | KASSERT(old_val < 100000, | |
370 | ("audit_ref_session: Too many references on session.")); | |
371 | } | |
372 | ||
373 | /* | |
374 | * Decrement the sentry ref count and remove the session entry if last one. | |
375 | */ | |
376 | static void | |
377 | audit_unref_session(au_sentry_t *se) | |
378 | { | |
379 | long old_val; | |
380 | ||
381 | old_val = OSAddAtomicLong(-1, &se->se_refcnt); | |
382 | if (old_val == 1) | |
383 | audit_session_remove(se); | |
384 | KASSERT(old_val > 0, | |
385 | ("audit_unref_session: Too few references on session.")); | |
386 | } | |
387 | ||
388 | /* | |
389 | * Increment the process count in the session. | |
390 | */ | |
391 | static void | |
392 | audit_inc_procount(au_sentry_t *se) | |
393 | { | |
394 | long old_val; | |
395 | ||
396 | old_val = OSAddAtomicLong(1, &se->se_procnt); | |
397 | KASSERT(old_val <= PID_MAX, | |
398 | ("audit_inc_procount: proc count > PID_MAX")); | |
399 | } | |
400 | ||
401 | /* | |
402 | * Decrement the process count and add a knote if it is the last process | |
403 | * to exit the session. | |
404 | */ | |
405 | static void | |
406 | audit_dec_procount(au_sentry_t *se) | |
407 | { | |
408 | long old_val; | |
409 | ||
410 | old_val = OSAddAtomicLong(-1, &se->se_procnt); | |
411 | if (old_val == 1) | |
412 | audit_session_knote(se, NOTE_AS_END); | |
413 | KASSERT(old_val >= 1, | |
414 | ("audit_dec_procount: proc count < 0")); | |
415 | } | |
416 | ||
417 | /* | |
418 | * Update the session entry and check to see if anything was updated. | |
419 | * Returns: | |
420 | * 0 Nothing was updated (We don't care about process preselection masks) | |
421 | * 1 Something was updated. | |
422 | */ | |
423 | static int | |
424 | audit_update_sentry(au_sentry_t *se, auditinfo_addr_t *new_aia) | |
425 | { | |
426 | auditinfo_addr_t *aia = &se->se_auinfo; | |
427 | int update; | |
428 | ||
429 | KASSERT(new_aia != &audit_default_aia, | |
430 | ("audit_update_sentry: Trying to update the default aia.")); | |
431 | ||
432 | update = (aia->ai_auid != new_aia->ai_auid || | |
433 | bcmp(&aia->ai_termid, &new_aia->ai_termid, | |
434 | sizeof(new_aia->ai_termid)) || | |
435 | aia->ai_flags != new_aia->ai_flags); | |
436 | ||
437 | if (update) | |
438 | bcopy(new_aia, aia, sizeof(*aia)); | |
439 | ||
440 | return (update); | |
441 | } | |
442 | ||
443 | /* | |
444 | * Return the next session ID. The range of kernel generated audit session IDs | |
445 | * is ASSIGNED_ASID_MIN to ASSIGNED_ASID_MAX. | |
446 | */ | |
447 | static uint32_t | |
448 | audit_session_nextid(void) | |
449 | { | |
450 | static uint32_t next_asid = ASSIGNED_ASID_MIN; | |
451 | ||
452 | AUDIT_SENTRY_RWLOCK_ASSERT(); | |
453 | ||
454 | if (next_asid > ASSIGNED_ASID_MAX) | |
455 | next_asid = ASSIGNED_ASID_MIN; | |
456 | ||
457 | return (next_asid++); | |
458 | } | |
459 | ||
460 | /* | |
461 | * Allocated a new audit_session entry and add it to the hash table. If the | |
462 | * given ASID is set to AU_ASSIGN_ASID then audit_session_new() will pick an | |
463 | * audit session ID. Otherwise, it attempts use the one given. It creates a | |
464 | * reference to the entry that must be unref'ed. | |
465 | */ | |
466 | static auditinfo_addr_t * | |
467 | audit_session_new(auditinfo_addr_t *new_aia, int newprocess) | |
468 | { | |
469 | au_asid_t asid; | |
470 | au_sentry_t *se = NULL; | |
471 | auditinfo_addr_t *aia = NULL; | |
472 | char nm[LOCK_MAX_NAME]; | |
473 | ||
474 | KASSERT(new_aia != NULL, ("audit_session_new: new_aia == NULL")); | |
475 | ||
476 | asid = new_aia->ai_asid; | |
477 | ||
478 | #if 0 /* XXX this assertion is currently broken by securityd/LoginWindow */ | |
479 | KASSERT((asid != AU_ASSIGN_ASID && asid <= PID_MAX), | |
480 | ("audit_session_new: illegal ASID value: %d", asid)); | |
481 | #endif | |
482 | ||
483 | /* | |
484 | * Alloc a new session entry now so we don't wait holding the lock. | |
485 | */ | |
486 | se = malloc(sizeof(au_sentry_t), M_AU_SESSION, M_WAITOK | M_ZERO); | |
487 | ||
488 | snprintf(nm, sizeof(nm), "audit se_klist_mtx %d", asid); | |
489 | AUDIT_SE_KLIST_LOCK_INIT(se, nm); | |
490 | ||
491 | /* | |
492 | * Find an unique session ID, if desired. | |
493 | */ | |
494 | AUDIT_SENTRY_WLOCK(); | |
495 | if (asid == AU_ASSIGN_ASID) { | |
496 | do { | |
497 | asid = (au_asid_t)audit_session_nextid(); | |
498 | } while(audit_session_find(asid) != NULL); | |
499 | } else { | |
500 | au_sentry_t *found_se = NULL; | |
501 | ||
502 | /* | |
503 | * Check to see if the requested ASID is already in the | |
504 | * hash table. If so, update it with the new auditinfo. | |
505 | */ | |
506 | if ((found_se = audit_session_find(asid)) != NULL) { | |
507 | int updated; | |
508 | ||
509 | updated = audit_update_sentry(found_se, new_aia); | |
510 | audit_ref_session(found_se); | |
511 | ||
512 | AUDIT_SENTRY_WUNLOCK(); | |
513 | AUDIT_SE_KLIST_LOCK_DESTROY(se); | |
514 | free(se, M_AU_SESSION); | |
515 | ||
516 | if (updated) | |
517 | audit_session_knote(found_se, NOTE_AS_UPDATE); | |
518 | ||
519 | /* | |
520 | * If this is a new process joining this session then | |
521 | * we need to update the proc count. | |
522 | */ | |
523 | if (newprocess) | |
524 | audit_inc_procount(found_se); | |
525 | ||
526 | return (&found_se->se_auinfo); | |
527 | } | |
528 | } | |
529 | ||
530 | /* | |
531 | * Start the reference and proc count at 1 to account for the process | |
532 | * that invoked this via setaudit_addr() (or friends). | |
533 | */ | |
534 | se->se_refcnt = se->se_procnt = 1; | |
535 | ||
536 | /* | |
537 | * Populate the new session entry. Note that process masks are stored | |
538 | * in kauth ucred so just zero them here. | |
539 | */ | |
540 | se->se_port = IPC_PORT_NULL; | |
541 | aia = &se->se_auinfo; | |
542 | aia->ai_asid = asid; | |
543 | aia->ai_auid = new_aia->ai_auid; | |
544 | bzero(&new_aia->ai_mask, sizeof(new_aia->ai_mask)); | |
545 | bcopy(&new_aia->ai_termid, &aia->ai_termid, sizeof(aia->ai_termid)); | |
546 | aia->ai_flags = new_aia->ai_flags; | |
547 | ||
548 | /* | |
549 | * Add it to the hash table. | |
550 | */ | |
551 | LIST_INSERT_HEAD(&au_sentry_bucket[HASH_ASID(asid)], se, se_link); | |
552 | AUDIT_SENTRY_WUNLOCK(); | |
553 | ||
554 | /* | |
555 | * Register kevents for consumers wanting events for any ASID | |
556 | * and knote the event. | |
557 | */ | |
558 | audit_register_kevents(se->se_asid, se->se_auid); | |
559 | audit_session_knote(se, NOTE_AS_START); | |
560 | ||
561 | return (aia); | |
562 | } | |
563 | ||
564 | /* | |
565 | * Lookup an existing session. A copy of the audit session info for a given | |
566 | * ASID is returned in ret_aia. Returns 0 on success. | |
567 | */ | |
568 | int | |
569 | audit_session_lookup(au_asid_t asid, auditinfo_addr_t *ret_aia) | |
570 | { | |
571 | au_sentry_t *se = NULL; | |
572 | ||
573 | if ((uint32_t)asid > ASSIGNED_ASID_MAX) | |
574 | return (-1); | |
575 | AUDIT_SENTRY_RLOCK(); | |
576 | if ((se = audit_session_find(asid)) == NULL) { | |
577 | AUDIT_SENTRY_RUNLOCK(); | |
578 | return (1); | |
579 | } | |
580 | if (ret_aia != NULL) | |
581 | bcopy(&se->se_auinfo, ret_aia, sizeof(*ret_aia)); | |
582 | AUDIT_SENTRY_RUNLOCK(); | |
583 | ||
584 | return (0); | |
585 | } | |
586 | ||
587 | /* | |
588 | * Add a reference to the session entry. | |
589 | */ | |
590 | void | |
591 | audit_session_ref(kauth_cred_t cred) | |
592 | { | |
593 | auditinfo_addr_t *aia_p; | |
594 | ||
595 | KASSERT(IS_VALID_CRED(cred), | |
596 | ("audit_session_ref: Invalid kauth_cred.")); | |
597 | ||
598 | aia_p = cred->cr_audit.as_aia_p; | |
599 | ||
600 | if (IS_VALID_SESSION(aia_p)) | |
601 | audit_ref_session(AU_SENTRY_PTR(aia_p)); | |
602 | } | |
603 | ||
604 | /* | |
605 | * Remove a reference to the session entry. | |
606 | */ | |
607 | void | |
608 | audit_session_unref(kauth_cred_t cred) | |
609 | { | |
610 | auditinfo_addr_t *aia_p; | |
611 | ||
612 | KASSERT(IS_VALID_CRED(cred), | |
613 | ("audit_session_unref: Invalid kauth_cred.")); | |
614 | ||
615 | aia_p = cred->cr_audit.as_aia_p; | |
616 | ||
617 | if (IS_VALID_SESSION(aia_p)) | |
618 | audit_unref_session(AU_SENTRY_PTR(aia_p)); | |
619 | } | |
620 | ||
621 | void | |
622 | audit_session_procnew(kauth_cred_t cred) | |
623 | { | |
624 | auditinfo_addr_t *aia_p; | |
625 | ||
626 | KASSERT(IS_VALID_CRED(cred), | |
627 | ("audit_session_procnew: Invalid kauth_cred.")); | |
628 | ||
629 | aia_p = cred->cr_audit.as_aia_p; | |
630 | ||
631 | if (IS_VALID_SESSION(aia_p)) | |
632 | audit_inc_procount(AU_SENTRY_PTR(aia_p)); | |
633 | } | |
634 | ||
635 | void | |
636 | audit_session_procexit(kauth_cred_t cred) | |
637 | { | |
638 | auditinfo_addr_t *aia_p; | |
639 | ||
640 | KASSERT(IS_VALID_CRED(cred), | |
641 | ("audit_session_procexit: Invalid kauth_cred.")); | |
642 | ||
643 | aia_p = cred->cr_audit.as_aia_p; | |
644 | ||
645 | if (IS_VALID_SESSION(aia_p)) | |
646 | audit_dec_procount(AU_SENTRY_PTR(aia_p)); | |
647 | } | |
648 | ||
649 | /* | |
650 | * Init the audit session code. | |
651 | */ | |
652 | void | |
653 | audit_session_init(void) | |
654 | { | |
655 | int i; | |
656 | ||
657 | KASSERT((ASSIGNED_ASID_MAX - ASSIGNED_ASID_MIN) > PID_MAX, | |
658 | ("audit_session_init: ASSIGNED_ASID_MAX is not large enough.")); | |
659 | ||
660 | AUDIT_SENTRY_RWLOCK_INIT(); | |
661 | AUDIT_ANYAS_KLIST_LOCK_INIT(); | |
662 | ||
663 | au_sentry_bucket = malloc( sizeof(struct au_sentry) * | |
664 | HASH_TABLE_SIZE, M_AU_SESSION, M_WAITOK | M_ZERO); | |
665 | ||
666 | for (i = 0; i < HASH_TABLE_SIZE; i++) | |
667 | LIST_INIT(&au_sentry_bucket[i]); | |
668 | } | |
669 | ||
670 | /* | |
671 | * Allocate a new kevent propagation list (plist). | |
672 | */ | |
673 | static caddr_t | |
674 | audit_new_plist(void) | |
675 | { | |
676 | au_plisthead_t *plhead; | |
677 | ||
678 | plhead = malloc(sizeof(au_plisthead_t), M_AU_EV_PLIST, M_WAITOK | | |
679 | M_ZERO); | |
680 | ||
681 | LIST_INIT(&plhead->ph_head); | |
682 | AUDIT_PLIST_LOCK_INIT(plhead); | |
683 | ||
684 | return ((caddr_t) plhead); | |
685 | } | |
686 | ||
687 | /* | |
688 | * Destroy a kevent propagation list (plist). The anyas_klist_mtx mutex must be | |
689 | * held by the caller. | |
690 | */ | |
691 | static void | |
692 | audit_destroy_plist(struct knote *anyas_kn) | |
693 | { | |
694 | au_plisthead_t *plhead; | |
695 | au_plist_t *plentry, *ple_tmp; | |
696 | struct kevent64_s kev; | |
697 | ||
698 | KASSERT(anyas_kn != NULL, ("audit_destroy_plist: anyas = NULL")); | |
699 | plhead = (au_plisthead_t *)anyas_kn->kn_hook; | |
700 | KASSERT(plhead != NULL, ("audit_destroy_plist: plhead = NULL")); | |
701 | ||
702 | /* | |
703 | * Delete everything in the propagation list. | |
704 | */ | |
705 | AUDIT_PLIST_LOCK(plhead); | |
706 | LIST_FOREACH_SAFE(plentry, &plhead->ph_head, pl_link, ple_tmp) { | |
707 | struct kqueue *kq = plentry->pl_knote->kn_kq; | |
708 | ||
709 | kev.ident = plentry->pl_knote->kn_id; | |
710 | kev.filter = EVFILT_SESSION; | |
711 | kev.flags = EV_DELETE; | |
712 | ||
713 | /* | |
714 | * The plist entry gets removed in rm_from_plist() which is | |
715 | * called indirectly by kevent_register(). | |
716 | */ | |
717 | kevent_register(kq, &kev, NULL); | |
718 | } | |
719 | AUDIT_PLIST_UNLOCK(plhead); | |
720 | ||
721 | /* | |
722 | * Remove the head. | |
723 | */ | |
724 | AUDIT_PLIST_LOCK_DESTROY(plhead); | |
725 | free(plhead, M_AU_EV_PLIST); | |
726 | } | |
727 | ||
728 | /* | |
729 | * Add a knote pointer entry to the kevent propagation list. | |
730 | */ | |
731 | static void | |
732 | audit_add_to_plist(struct knote *anyas_kn, struct knote *kn) | |
733 | { | |
734 | au_plisthead_t *plhead; | |
735 | au_plist_t *plentry; | |
736 | ||
737 | KASSERT(anyas_kn != NULL, ("audit_add_to_plist: anyas = NULL")); | |
738 | plhead = (au_plisthead_t *)anyas_kn->kn_hook; | |
739 | KASSERT(plhead != NULL, ("audit_add_to_plist: plhead = NULL")); | |
740 | ||
741 | plentry = malloc(sizeof(au_plist_t), M_AU_EV_PLIST, M_WAITOK | M_ZERO); | |
742 | ||
743 | plentry->pl_knote = kn; | |
744 | AUDIT_PLIST_LOCK(plhead); | |
745 | LIST_INSERT_HEAD(&plhead->ph_head, plentry, pl_link); | |
746 | AUDIT_PLIST_UNLOCK(plhead); | |
747 | } | |
748 | ||
749 | /* | |
750 | * Remote a knote pointer entry from the kevent propagation list. The lock | |
751 | * on the plist may already be head (by audit_destroy_plist() above) so we use | |
752 | * a recursive lock. | |
753 | */ | |
754 | static void | |
755 | audit_rm_from_plist(struct knote *kn) | |
756 | { | |
757 | struct knote *anyas_kn; | |
758 | au_plisthead_t *plhd; | |
759 | au_plist_t *plentry, *ple_tmp; | |
760 | ||
761 | KASSERT(kn != NULL, ("audit_rm_from_plist: kn = NULL")); | |
762 | anyas_kn = (struct knote *)kn->kn_hook; | |
763 | KASSERT(anyas_kn != NULL, ("audit_rm_to_plist: anyas = NULL")); | |
764 | plhd = (au_plisthead_t *)anyas_kn->kn_hook; | |
765 | ||
766 | AUDIT_PLIST_LOCK(plhd); | |
767 | LIST_FOREACH_SAFE(plentry, &plhd->ph_head, pl_link, ple_tmp) { | |
768 | if (plentry->pl_knote == kn) { | |
769 | LIST_REMOVE(plentry, pl_link); | |
770 | free(plentry, M_AU_EV_PLIST); | |
771 | AUDIT_PLIST_UNLOCK(plhd); | |
772 | return; | |
773 | } | |
774 | } | |
775 | AUDIT_PLIST_UNLOCK(plhd); | |
776 | } | |
777 | ||
778 | /* | |
779 | * The attach filter for EVFILT_SESSION. | |
780 | */ | |
781 | static int | |
782 | audit_filt_sessionattach(struct knote *kn) | |
783 | { | |
784 | au_sentry_t *se = NULL; | |
785 | ||
786 | /* | |
787 | * Check flags for the events we currently support. | |
788 | */ | |
789 | if ((kn->kn_sfflags & (NOTE_AS_START | NOTE_AS_END | NOTE_AS_CLOSE | |
790 | | NOTE_AS_UPDATE | NOTE_AS_ERR)) == 0) | |
791 | return (ENOTSUP); | |
792 | ||
793 | /* | |
794 | * If the interest is in any session then add to the any ASID knote | |
795 | * list. Otherwise, add it to the knote list assosiated with the | |
796 | * given session. | |
797 | */ | |
798 | if (kn->kn_id == AS_ANY_ASID) { | |
799 | ||
800 | kn->kn_flags |= EV_CLEAR; | |
801 | kn->kn_ptr.p_se = NULL; | |
802 | ||
803 | /* | |
804 | * Attach a kevent propagation list for any kevents that get | |
805 | * added. | |
806 | */ | |
807 | kn->kn_hook = audit_new_plist(); | |
808 | ||
809 | AUDIT_ANYAS_KLIST_LOCK(); | |
810 | KNOTE_ATTACH(&anyas_klist, kn); | |
811 | AUDIT_ANYAS_KLIST_UNLOCK(); | |
812 | ||
813 | return (0); | |
814 | } else { | |
815 | ||
816 | /* | |
817 | * NOTE: The anyas klist lock will be held in this | |
818 | * part of the code when indirectly called from | |
819 | * audit_register_kevents() below. | |
820 | */ | |
821 | ||
822 | /* | |
823 | * Check to make sure it is a valid ASID. | |
824 | */ | |
825 | if (kn->kn_id > ASSIGNED_ASID_MAX) | |
826 | return (EINVAL); | |
827 | ||
828 | AUDIT_SENTRY_RLOCK(); | |
829 | se = audit_session_find(kn->kn_id); | |
830 | AUDIT_SENTRY_RUNLOCK(); | |
831 | if (se == NULL) | |
832 | return (EINVAL); | |
833 | ||
834 | AUDIT_SE_KLIST_LOCK(se); | |
835 | kn->kn_flags |= EV_CLEAR; | |
836 | kn->kn_ptr.p_se = se; | |
837 | ||
838 | /* | |
839 | * If this attach is the result of an "any ASID" (pseudo) | |
840 | * kevent then attach the any session knote ptr to this knote. | |
841 | * Also, add this knote to the its propagation list. | |
842 | */ | |
843 | if (kn->kn_flags & EV_ANY_ASID) { | |
844 | struct knote *anyas_kn = | |
845 | (struct knote *)((uintptr_t)kn->kn_kevent.ext[0]); | |
846 | kn->kn_hook = (caddr_t) anyas_kn; | |
847 | kn->kn_flags &= ~EV_ANY_ASID; | |
848 | audit_add_to_plist(anyas_kn, kn); | |
849 | } else | |
850 | kn->kn_hook = NULL; | |
851 | KNOTE_ATTACH(&se->se_klist, kn); | |
852 | AUDIT_SE_KLIST_UNLOCK(se); | |
853 | ||
854 | return (0); | |
855 | } | |
856 | } | |
857 | ||
858 | /* | |
859 | * The detach filter for EVFILT_SESSION. | |
860 | */ | |
861 | static void | |
862 | audit_filt_sessiondetach(struct knote *kn) | |
863 | { | |
864 | au_sentry_t *se = NULL; | |
865 | ||
866 | if (kn->kn_id == AS_ANY_ASID) { | |
867 | ||
868 | AUDIT_ANYAS_KLIST_LOCK(); | |
869 | audit_destroy_plist(kn); | |
870 | KNOTE_DETACH(&anyas_klist, kn); | |
871 | AUDIT_ANYAS_KLIST_UNLOCK(); | |
872 | ||
873 | } else { | |
874 | /* | |
875 | * If this knote was created by any ASID kevent then remove | |
876 | * from kevent propagation list. | |
877 | */ | |
878 | if (kn->kn_hook != NULL) { | |
879 | audit_rm_from_plist(kn); | |
880 | kn->kn_hook = NULL; | |
881 | } | |
882 | ||
883 | /* | |
884 | * Check to see if already detached. | |
885 | */ | |
886 | se = kn->kn_ptr.p_se; | |
887 | if (se != NULL) { | |
888 | AUDIT_SE_KLIST_LOCK(se); | |
889 | kn->kn_ptr.p_se = NULL; | |
890 | KNOTE_DETACH(&se->se_klist, kn); | |
891 | AUDIT_SE_KLIST_UNLOCK(se); | |
892 | } | |
893 | } | |
894 | } | |
895 | ||
896 | /* | |
897 | * The touch filter for EVFILT_SESSION. Check for any ASID kevent updates and | |
898 | * propagate the change. | |
899 | */ | |
900 | static void | |
901 | audit_filt_sessiontouch(struct knote *kn, struct kevent64_s *kev, long type) | |
902 | { | |
903 | struct knote *ple_kn; | |
904 | struct kqueue *kq; | |
905 | au_sentry_t *se; | |
906 | au_plisthead_t *plhead; | |
907 | au_plist_t *plentry; | |
908 | struct kevent64_s newkev; | |
909 | ||
910 | switch (type) { | |
911 | case EVENT_REGISTER: | |
912 | kn->kn_sfflags = kev->fflags; | |
913 | kn->kn_sdata = kev->data; | |
914 | /* | |
915 | * If an any ASID kevent was updated then we may need to | |
916 | * propagate the update. | |
917 | */ | |
918 | if (kev->ident == AS_ANY_ASID && kn->kn_hook != NULL) { | |
919 | ||
920 | /* | |
921 | * Propagate the change to each of the session kevents | |
922 | * that were created by this any ASID kevent. | |
923 | */ | |
924 | plhead = (au_plisthead_t *)kn->kn_hook; | |
925 | AUDIT_PLIST_LOCK(plhead); | |
926 | LIST_FOREACH(plentry, &plhead->ph_head, pl_link) { | |
927 | ||
928 | if ((ple_kn = plentry->pl_knote) == NULL) | |
929 | continue; | |
930 | if ((se = ple_kn->kn_ptr.p_se) == NULL) | |
931 | continue; | |
932 | if ((kq = ple_kn->kn_kq) == NULL) | |
933 | continue; | |
934 | ||
935 | newkev.ident = plentry->pl_knote->kn_id; | |
936 | newkev.filter = EVFILT_SESSION; | |
937 | newkev.flags = kev->flags; | |
938 | newkev.fflags = kev->fflags; | |
939 | newkev.data = kev->data; | |
940 | newkev.udata = kev->udata; | |
941 | kevent_register(kq, &newkev, NULL); | |
942 | } | |
943 | AUDIT_PLIST_UNLOCK(plhead); | |
944 | } | |
945 | break; | |
946 | ||
947 | case EVENT_PROCESS: | |
948 | *kev = kn->kn_kevent; | |
949 | if (kn->kn_flags & EV_CLEAR) { | |
950 | kn->kn_data = 0; | |
951 | kn->kn_fflags = 0; | |
952 | } | |
953 | break; | |
954 | ||
955 | default: | |
956 | KASSERT((type == EVENT_REGISTER || type == EVENT_PROCESS), | |
957 | ("filt_sessiontouch(): invalid type (%ld)", type)); | |
958 | break; | |
959 | } | |
960 | } | |
961 | ||
962 | /* | |
963 | * Event filter for EVFILT_SESSION. The AUDIT_SE_KLIST_LOCK should be held | |
964 | * by audit_session_knote(). | |
965 | */ | |
966 | static int | |
967 | audit_filt_session(struct knote *kn, long hint) | |
968 | { | |
969 | int events = (int)hint; | |
970 | au_sentry_t *se = kn->kn_ptr.p_se; | |
971 | ||
972 | if (hint != 0 && se != NULL) { | |
973 | ||
974 | if (kn->kn_sfflags & events) { | |
975 | kn->kn_fflags |= events; | |
976 | kn->kn_data = se->se_auid; | |
977 | } | |
978 | ||
979 | /* | |
980 | * If this is the last possible event for the knote, | |
981 | * detach the knote from the audit session before the | |
982 | * session goes away. | |
983 | */ | |
984 | if (events & NOTE_AS_CLOSE) { | |
985 | ||
986 | /* | |
987 | * If created by any ASID kevent then remove from | |
988 | * propagation list. | |
989 | */ | |
990 | if (kn->kn_hook != NULL) { | |
991 | audit_rm_from_plist(kn); | |
992 | kn->kn_hook = NULL; | |
993 | } | |
994 | kn->kn_flags |= (EV_EOF | EV_ONESHOT); | |
995 | kn->kn_ptr.p_se = NULL; | |
996 | AUDIT_SE_KLIST_LOCK_ASSERT(se); | |
997 | KNOTE_DETACH(&se->se_klist, kn); | |
998 | ||
999 | return (1); | |
1000 | } | |
1001 | } | |
1002 | return (kn->kn_fflags != 0); | |
1003 | } | |
1004 | ||
1005 | /* | |
1006 | * For all the consumers wanting events for all sessions, register new | |
1007 | * kevents associated with the session for the given ASID. The actual | |
1008 | * attachment is done by the EVFILT_SESSION attach filter above. | |
1009 | */ | |
1010 | static void | |
1011 | audit_register_kevents(uint32_t asid, uint32_t auid) | |
1012 | { | |
1013 | struct knote *kn; | |
1014 | ||
1015 | AUDIT_ANYAS_KLIST_LOCK(); | |
1016 | SLIST_FOREACH(kn, &anyas_klist, kn_selnext) { | |
1017 | struct kqueue *kq = kn->kn_kq; | |
1018 | struct kevent64_s kev; | |
1019 | int err; | |
1020 | ||
1021 | kev.ident = asid; | |
1022 | kev.filter = EVFILT_SESSION; | |
1023 | kev.flags = kn->kn_flags | EV_ADD | EV_ENABLE | EV_ANY_ASID; | |
1024 | kev.fflags = kn->kn_sfflags; | |
1025 | kev.data = auid; | |
1026 | kev.udata = kn->kn_kevent.udata; | |
1027 | ||
1028 | /* | |
1029 | * Save the knote ptr for this "any ASID" knote for the attach | |
1030 | * filter. | |
1031 | */ | |
1032 | kev.ext[0] = (uint64_t)((uintptr_t)kn); | |
1033 | ||
1034 | /* | |
1035 | * XXX kevent_register() may block here alloc'ing a new knote. | |
1036 | * We may want to think about using a lockless linked list or | |
1037 | * at least a sleep rwlock for the anyas_klist. | |
1038 | */ | |
1039 | err = kevent_register(kq, &kev, NULL); | |
1040 | if (err) | |
1041 | kn->kn_fflags |= NOTE_AS_ERR; | |
1042 | } | |
1043 | AUDIT_ANYAS_KLIST_UNLOCK(); | |
1044 | } | |
1045 | ||
1046 | /* | |
1047 | * Safely update kauth cred of the given process with new the given audit info. | |
1048 | * If the newprocess flag is set then we need to account for this process in | |
1049 | * the proc count. | |
1050 | */ | |
1051 | int | |
1052 | audit_session_setaia(proc_t p, auditinfo_addr_t *aia_p, int newprocess) | |
1053 | { | |
1054 | kauth_cred_t my_cred, my_new_cred; | |
1055 | struct au_session as; | |
1056 | struct au_session tmp_as; | |
1057 | auditinfo_addr_t caia; | |
1058 | ||
1059 | /* | |
1060 | * If this is going to modify an existing session then do some | |
1061 | * immutable checks. | |
1062 | */ | |
1063 | if (audit_session_lookup(aia_p->ai_asid, &caia) == 0) { | |
1064 | ||
1065 | /* | |
1066 | * If the current audit ID is not the default then it is | |
1067 | * immutable. | |
1068 | */ | |
1069 | if (caia.ai_auid != AU_DEFAUDITID && | |
1070 | caia.ai_auid != aia_p->ai_auid) | |
1071 | return (EINVAL); | |
1072 | ||
1073 | /* | |
1074 | * If the current termid is not the default then it is | |
1075 | * immutable. | |
1076 | */ | |
1077 | if ((caia.ai_termid.at_type != AU_IPv4 || | |
1078 | caia.ai_termid.at_port != 0 || | |
1079 | caia.ai_termid.at_addr[0] != 0) && | |
1080 | (caia.ai_termid.at_port != aia_p->ai_termid.at_port || | |
1081 | caia.ai_termid.at_type != aia_p->ai_termid.at_type || | |
1082 | bcmp(&caia.ai_termid.at_addr, &aia_p->ai_termid.at_addr, | |
1083 | sizeof (caia.ai_termid.at_addr) )) ) | |
1084 | return (EINVAL); | |
1085 | ||
1086 | /* The audit flags are immutable. */ | |
1087 | if (caia.ai_flags != aia_p->ai_flags) | |
1088 | return (EINVAL); | |
1089 | ||
1090 | /* The audit masks are mutable. */ | |
1091 | } | |
1092 | ||
1093 | my_cred = kauth_cred_proc_ref(p); | |
1094 | bcopy(&aia_p->ai_mask, &as.as_mask, sizeof(as.as_mask)); | |
1095 | as.as_aia_p = audit_session_new(aia_p, newprocess); | |
1096 | ||
1097 | /* | |
1098 | * We are modifying the audit info in a credential so we need a new | |
1099 | * credential (or take another reference on an existing credential that | |
1100 | * matches our new one). We must do this because the audit info in the | |
1101 | * credential is used as part of our hash key. Get current credential | |
1102 | * in the target process and take a reference while we muck with it. | |
1103 | */ | |
1104 | for (;;) { | |
1105 | ||
1106 | /* | |
1107 | * Set the credential with new info. If there is no change, | |
1108 | * we get back the same credential we passed in; if there is | |
1109 | * a change, we drop the reference on the credential we | |
1110 | * passed in. The subsequent compare is safe, because it is | |
1111 | * a pointer compare rather than a contents compare. | |
1112 | */ | |
1113 | bcopy(&as, &tmp_as, sizeof(tmp_as)); | |
1114 | my_new_cred = kauth_cred_setauditinfo(my_cred, &tmp_as); | |
1115 | ||
1116 | if (my_cred != my_new_cred) { | |
1117 | proc_lock(p); | |
1118 | /* Need to protect for a race where another thread also | |
1119 | * changed the credential after we took our reference. | |
1120 | * If p_ucred has changed then we should restart this | |
1121 | * again with the new cred. | |
1122 | */ | |
1123 | if (p->p_ucred != my_cred) { | |
1124 | proc_unlock(p); | |
1125 | audit_session_unref(my_new_cred); | |
1126 | kauth_cred_unref(&my_new_cred); | |
1127 | /* try again */ | |
1128 | my_cred = kauth_cred_proc_ref(p); | |
1129 | continue; | |
1130 | } | |
1131 | p->p_ucred = my_new_cred; | |
1132 | proc_unlock(p); | |
1133 | } | |
1134 | /* | |
1135 | * Drop old proc reference or our extra reference. | |
1136 | */ | |
1137 | kauth_cred_unref(&my_cred); | |
1138 | break; | |
1139 | } | |
1140 | audit_session_unref(my_new_cred); | |
1141 | ||
1142 | /* | |
1143 | * Propagate the change from the process to the Mach task. | |
1144 | */ | |
1145 | set_security_token(p); | |
1146 | ||
1147 | return (0); | |
1148 | } | |
1149 | ||
1150 | /* | |
1151 | * audit_session_self (system call) | |
1152 | * | |
1153 | * Description: Obtain a Mach send right for the current session. | |
1154 | * | |
1155 | * Parameters: p Process calling audit_session_self(). | |
1156 | * | |
1157 | * Returns: *ret_port Named Mach send right, which may be | |
1158 | * MACH_PORT_NULL in the failure case. | |
1159 | * | |
1160 | * Errno: 0 Success | |
1161 | * EINVAL The calling process' session has not be set. | |
1162 | * ESRCH Bad process, can't get valid cred for process. | |
1163 | * ENOMEM Port allocation failed due to no free memory. | |
1164 | */ | |
1165 | int | |
1166 | audit_session_self(proc_t p, __unused struct audit_session_self_args *uap, | |
1167 | mach_port_name_t *ret_port) | |
1168 | { | |
1169 | ipc_port_t sendport = IPC_PORT_NULL; | |
1170 | kauth_cred_t cred = NULL; | |
1171 | auditinfo_addr_t *aia_p; | |
1172 | au_sentry_t *se; | |
1173 | int err = 0; | |
1174 | ||
1175 | cred = kauth_cred_proc_ref(p); | |
1176 | if (!IS_VALID_CRED(cred)) { | |
1177 | err = ESRCH; | |
1178 | goto done; | |
1179 | } | |
1180 | ||
1181 | aia_p = cred->cr_audit.as_aia_p; | |
1182 | if (!IS_VALID_SESSION(aia_p)) { | |
1183 | err = EINVAL; | |
1184 | goto done; | |
1185 | } | |
1186 | ||
1187 | se = AU_SENTRY_PTR(aia_p); | |
1188 | ||
1189 | /* | |
1190 | * Processes that join using this mach port will inherit this process' | |
1191 | * pre-selection masks. | |
1192 | */ | |
1193 | if (se->se_port == IPC_PORT_NULL) | |
1194 | bcopy(&cred->cr_audit.as_mask, &se->se_mask, | |
1195 | sizeof(se->se_mask)); | |
1196 | ||
1197 | if ((sendport = audit_session_mksend(aia_p, &se->se_port)) == NULL) { | |
1198 | /* failed to alloc new port */ | |
1199 | err = ENOMEM; | |
1200 | goto done; | |
1201 | } | |
1202 | ||
1203 | /* | |
1204 | * This reference on the session is unref'ed in | |
1205 | * audit_session_port_destory(). This reference is needed so the | |
1206 | * session doesn't get dropped until the session join is done. | |
1207 | */ | |
1208 | audit_ref_session(se); | |
1209 | ||
1210 | ||
1211 | done: | |
1212 | if (cred != NULL) | |
1213 | kauth_cred_unref(&cred); | |
1214 | if (err == 0) | |
1215 | *ret_port = ipc_port_copyout_send(sendport, | |
1216 | get_task_ipcspace(p->task)); | |
1217 | else | |
1218 | *ret_port = MACH_PORT_NULL; | |
1219 | ||
1220 | return (err); | |
1221 | } | |
1222 | ||
1223 | void | |
1224 | audit_session_portaiadestroy(struct auditinfo_addr *port_aia_p) | |
1225 | { | |
1226 | au_sentry_t *se; | |
1227 | ||
1228 | KASSERT(port_aia_p != NULL, | |
1229 | ("audit_session_infodestroy: port_aia_p = NULL")); | |
1230 | ||
1231 | se = AU_SENTRY_PTR(port_aia_p); | |
1232 | ||
1233 | /* | |
1234 | * Drop the reference added in audit_session_self(). | |
1235 | */ | |
1236 | if (se != NULL) { | |
1237 | se->se_port = IPC_PORT_NULL; | |
1238 | audit_unref_session(se); | |
1239 | } | |
1240 | ||
1241 | } | |
1242 | ||
1243 | static int | |
1244 | audit_session_join_internal(proc_t p, ipc_port_t port, au_asid_t *new_asid) | |
1245 | { | |
1246 | auditinfo_addr_t *port_aia_p, *old_aia_p; | |
1247 | kauth_cred_t cred = NULL; | |
1248 | au_asid_t old_asid; | |
1249 | int err = 0; | |
1250 | ||
1251 | *new_asid = AU_DEFAUDITSID; | |
1252 | ||
1253 | if ((port_aia_p = audit_session_porttoaia(port)) == NULL) { | |
1254 | err = EINVAL; | |
1255 | goto done; | |
1256 | } | |
1257 | *new_asid = port_aia_p->ai_asid; | |
1258 | ||
1259 | cred = kauth_cred_proc_ref(p); | |
1260 | if (!IS_VALID_CRED(cred)) { | |
1261 | kauth_cred_unref(&cred); | |
1262 | err = ESRCH; | |
1263 | goto done; | |
1264 | } | |
1265 | old_aia_p = cred->cr_audit.as_aia_p; | |
1266 | old_asid = old_aia_p->ai_asid; | |
1267 | ||
1268 | /* | |
1269 | * Add process in if not already in the session. | |
1270 | */ | |
1271 | if (*new_asid != old_asid) { | |
1272 | audit_session_setaia(p, port_aia_p, 1); | |
1273 | /* | |
1274 | * If this process was in a valid session before then we | |
1275 | * need to decrement the process count of the session it | |
1276 | * came from. | |
1277 | */ | |
1278 | if (IS_VALID_SESSION(old_aia_p)) | |
1279 | audit_dec_procount(AU_SENTRY_PTR(old_aia_p)); | |
1280 | } | |
1281 | kauth_cred_unref(&cred); | |
1282 | ||
1283 | done: | |
1284 | if (port != IPC_PORT_NULL) | |
1285 | ipc_port_release_send(port); | |
1286 | ||
1287 | return (err); | |
1288 | } | |
1289 | ||
1290 | /* | |
1291 | * audit_session_spawnjoin | |
1292 | * | |
1293 | * Description: posix_spawn() interface to audit_session_join_internal(). | |
1294 | * | |
1295 | * Returns: 0 Success | |
1296 | * EINVAL Invalid Mach port name. | |
1297 | * ESRCH Invalid calling process/cred. | |
1298 | */ | |
1299 | int | |
1300 | audit_session_spawnjoin(proc_t p, ipc_port_t port) | |
1301 | { | |
1302 | au_asid_t new_asid; | |
1303 | ||
1304 | return (audit_session_join_internal(p, port, &new_asid)); | |
1305 | } | |
1306 | ||
1307 | /* | |
1308 | * audit_session_join (system call) | |
1309 | * | |
1310 | * Description: Join the session for a given Mach port send right. | |
1311 | * | |
1312 | * Parameters: p Process calling session join. | |
1313 | * uap->port A Mach send right. | |
1314 | * | |
1315 | * Returns: *ret_asid Audit session ID of new session, which may | |
1316 | * be AU_DEFAUDITSID in the failure case. | |
1317 | * | |
1318 | * Errno: 0 Success | |
1319 | * EINVAL Invalid Mach port name. | |
1320 | * ESRCH Invalid calling process/cred. | |
1321 | */ | |
1322 | int | |
1323 | audit_session_join(proc_t p, struct audit_session_join_args *uap, | |
1324 | au_asid_t *ret_asid) | |
1325 | { | |
1326 | ipc_port_t port = IPC_PORT_NULL; | |
1327 | mach_port_name_t send = uap->port; | |
1328 | int err = 0; | |
1329 | ||
1330 | ||
1331 | if (ipc_object_copyin(get_task_ipcspace(p->task), send, | |
1332 | MACH_MSG_TYPE_COPY_SEND, &port) != KERN_SUCCESS) { | |
1333 | *ret_asid = AU_DEFAUDITSID; | |
1334 | err = EINVAL; | |
1335 | } else | |
1336 | err = audit_session_join_internal(p, port, ret_asid); | |
1337 | ||
1338 | return (err); | |
1339 | } | |
1340 | ||
1341 | #else | |
1342 | ||
1343 | int | |
1344 | audit_session_self(proc_t p, struct audit_session_self_args *uap, | |
1345 | mach_port_name_t *ret_port) | |
1346 | { | |
1347 | #pragma unused(p, uap, ret_port) | |
1348 | ||
1349 | return (ENOSYS); | |
1350 | } | |
1351 | ||
1352 | int | |
1353 | audit_session_join(proc_t p, struct audit_session_join_args *uap, | |
1354 | au_asid_t *ret_asid) | |
1355 | { | |
1356 | #pragma unused(p, uap, ret_asid) | |
1357 | ||
1358 | return (ENOSYS); | |
1359 | } | |
1360 | ||
1361 | #endif /* CONFIG_AUDIT */ |