1 /*
2 * Copyright (c) 2000-2007 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 /*
29 * Copyright (c) 1990, 1996-1998 Apple Computer, Inc.
30 * All Rights Reserved.
31 */
32 /*
33 * posix_shm.c : Support for POSIX shared memory APIs
34 *
35 * File: posix_shm.c
36 * Author: Ananthakrishna Ramesh
37 *
38 * HISTORY
39 * 2-Sep-1999 A.Ramesh
40 * Created for MacOSX
41 *
42 */
43 /*
44 * NOTICE: This file was modified by SPARTA, Inc. in 2005 to introduce
45 * support for mandatory and extensible security protections. This notice
46 * is included in support of clause 2.2 (b) of the Apple Public License,
47 * Version 2.0.
48 */
49
50 #include <sys/cdefs.h>
51 #include <sys/param.h>
52 #include <sys/systm.h>
53 #include <sys/kernel.h>
54 #include <sys/file_internal.h>
55 #include <sys/filedesc.h>
56 #include <sys/stat.h>
57 #include <sys/proc_internal.h>
58 #include <sys/kauth.h>
59 #include <sys/mount.h>
60 #include <sys/namei.h>
61 #include <sys/vnode.h>
62 #include <sys/vnode_internal.h>
63 #include <sys/ioctl.h>
64 #include <sys/tty.h>
65 #include <sys/malloc.h>
66 #include <sys/mman.h>
67 #include <sys/stat.h>
68 #include <sys/sysproto.h>
69 #include <sys/proc_info.h>
70 #include <sys/posix_shm.h>
71 #include <security/audit/audit.h>
72 #include <stdbool.h>
73
74 #if CONFIG_MACF
75 #include <security/mac_framework.h>
76 #endif
77
78 #include <mach/mach_types.h>
79 #include <mach/mach_vm.h>
80 #include <mach/vm_map.h>
81 #include <mach/vm_prot.h>
82 #include <mach/vm_inherit.h>
83 #include <mach/kern_return.h>
84
85 #include <vm/vm_map_xnu.h>
86 #include <vm/vm_kern_xnu.h>
87 #include <vm/vm_protos.h>
88 #include <vm/vm_memory_entry_xnu.h>
89
90 #define f_flag fp_glob->fg_flag
91 #define f_ops fp_glob->fg_ops
92
93 /*
94 * Used to construct the list of memory objects
95 * assigned to a populated shared memory segment.
96 */
97 typedef struct pshm_mobj {
98 void *pshmo_memobject;
99 memory_object_size_t pshmo_size;
100 SLIST_ENTRY(pshm_mobj) pshmo_next;
101 } pshm_mobj_t;
102
103 /*
104 * This represents an existing Posix shared memory object.
105 *
106 * It comes into existence with a shm_open(...O_CREAT...)
107 * call and goes away only after it has been shm_unlink()ed
108 * and the last remaining shm_open() file reference is closed.
109 *
110 * To keep track of that lifetime, pshm_usecount is used as a reference
111 * counter. It's incremented for every successful shm_open() and
112 * one extra time for the shm_unlink() to release. Internally
113 * you can temporarily use an additional reference whenever the
114 * subsystem lock has to be dropped for other reasons.
115 */
116 typedef struct internal_pshminfo {
117 struct pshminfo pshm_hdr;
118 SLIST_HEAD(pshm_mobjhead, pshm_mobj) pshm_mobjs;
119 RB_ENTRY(internal_pshminfo) pshm_links; /* links for red/black tree */
120 } pshm_info_t;
121 #define pshm_flags pshm_hdr.pshm_flags
122 #define pshm_usecount pshm_hdr.pshm_usecount
123 #define pshm_length pshm_hdr.pshm_length
124 #define pshm_mode pshm_hdr.pshm_mode
125 #define pshm_uid pshm_hdr.pshm_uid
126 #define pshm_gid pshm_hdr.pshm_gid
127 #define pshm_label pshm_hdr.pshm_label
128
129 /* Values for pshm_flags that are still used */
130 #define PSHM_ALLOCATED 0x004 /* backing storage is allocated */
131 #define PSHM_MAPPED 0x008 /* mapped at least once */
132 #define PSHM_INUSE 0x010 /* mapped at least once */
133 #define PSHM_REMOVED 0x020 /* no longer in the name cache due to shm_unlink() */
134 #define PSHM_ALLOCATING 0x100 /* storage is being allocated */
135
136 /*
137 * These handle reference counting pshm_info_t structs using pshm_usecount.
138 */
139 static int pshm_ref(pshm_info_t *pinfo);
140 static void pshm_deref(pshm_info_t *pinfo);
141 #define PSHM_MAXCOUNT UINT_MAX
142
143 /*
144 * For every shm_open, we get a new one of these.
145 * The only reason we don't just use pshm_info directly is that
146 * you can query the mapped memory objects via proc_pidinfo to
147 * query the mapped address. Note that even this is a hack. If
148 * you mmap() the same fd multiple times, we only save/report
149 * one address.
150 */
151 typedef struct pshmnode {
152 off_t mapp_addr;
153 pshm_info_t *pinfo;
154 } pshmnode_t;
155
156
157 /* compare function for the red black tree */
158 static int
pshm_compare(pshm_info_t * a,pshm_info_t * b)159 pshm_compare(pshm_info_t *a, pshm_info_t *b)
160 {
161 int cmp = strncmp(a->pshm_hdr.pshm_name, b->pshm_hdr.pshm_name, PSHMNAMLEN + 1);
162
163 if (cmp < 0) {
164 return -1;
165 }
166 if (cmp > 0) {
167 return 1;
168 }
169 return 0;
170 }
171
172
173 /*
174 * shared memory "paths" are stored in a red black tree for lookup
175 */
176 u_long pshmnument; /* count of entries allocated in the red black tree */
177 RB_HEAD(pshmhead, internal_pshminfo) pshm_head;
178 RB_PROTOTYPE(pshmhead, internal_pshminfo, pshm_links, pshm_compare)
179 RB_GENERATE(pshmhead, internal_pshminfo, pshm_links, pshm_compare)
180
181 /* lookup, add, remove functions */
182 static pshm_info_t *pshm_cache_search(pshm_info_t * look);
183 static void pshm_cache_add(pshm_info_t *entry);
184 static void pshm_cache_delete(pshm_info_t *entry);
185
186 static int pshm_closefile(struct fileglob *fg, vfs_context_t ctx);
187
188 static int pshm_access(pshm_info_t *pinfo, int mode, kauth_cred_t cred, proc_t p);
189 int pshm_cache_purge_all(void);
190 int pshm_cache_purge_uid(uid_t uid);
191
192 static void pshm_unlink_internal(pshm_info_t *pinfo);
193
194 static const struct fileops pshmops = {
195 .fo_type = DTYPE_PSXSHM,
196 .fo_read = fo_no_read,
197 .fo_write = fo_no_write,
198 .fo_ioctl = fo_no_ioctl,
199 .fo_select = fo_no_select,
200 .fo_close = pshm_closefile,
201 .fo_drain = fo_no_drain,
202 .fo_kqfilter = fo_no_kqfilter,
203 };
204
205 /*
206 * Everything here is protected by a single mutex.
207 */
208 static LCK_GRP_DECLARE(psx_shm_subsys_lck_grp, "posix shared memory");
209 static LCK_MTX_DECLARE(psx_shm_subsys_mutex, &psx_shm_subsys_lck_grp);
210
211 #define PSHM_SUBSYS_LOCK() lck_mtx_lock(& psx_shm_subsys_mutex)
212 #define PSHM_SUBSYS_UNLOCK() lck_mtx_unlock(& psx_shm_subsys_mutex)
213 #define PSHM_SUBSYS_ASSERT_HELD() LCK_MTX_ASSERT(&psx_shm_subsys_mutex, LCK_MTX_ASSERT_OWNED)
214
215 /*
216 * Lookup an entry in the cache. Only the name is used from "look".
217 */
218 static pshm_info_t *
pshm_cache_search(pshm_info_t * look)219 pshm_cache_search(pshm_info_t *look)
220 {
221 PSHM_SUBSYS_ASSERT_HELD();
222 return RB_FIND(pshmhead, &pshm_head, look);
223 }
224
225 /*
226 * Add a new entry to the cache.
227 */
228 static void
pshm_cache_add(pshm_info_t * entry)229 pshm_cache_add(pshm_info_t *entry)
230 {
231 pshm_info_t *conflict;
232
233 PSHM_SUBSYS_ASSERT_HELD();
234 conflict = RB_INSERT(pshmhead, &pshm_head, entry);
235 if (conflict != NULL) {
236 panic("pshm_cache_add() found %p", conflict);
237 }
238 pshmnument++;
239 }
240
241 /*
242 * Remove the given entry from the red black tree.
243 */
244 static void
pshm_cache_delete(pshm_info_t * entry)245 pshm_cache_delete(pshm_info_t *entry)
246 {
247 PSHM_SUBSYS_ASSERT_HELD();
248 assert(!(entry->pshm_flags & PSHM_REMOVED));
249 RB_REMOVE(pshmhead, &pshm_head, entry);
250 pshmnument--;
251 }
252
253 /*
254 * Initialize the red black tree.
255 */
256 void
pshm_cache_init(void)257 pshm_cache_init(void)
258 {
259 RB_INIT(&pshm_head);
260 }
261
262 /*
263 * Invalidate all entries and delete all objects associated with them
264 * XXX - due to the reference counting, this only works if all userland
265 * references to it via file descriptors are also closed already. Is this
266 * known to be called after all user processes are killed?
267 */
268 int
pshm_cache_purge_all(void)269 pshm_cache_purge_all(void)
270 {
271 pshm_info_t *p;
272 pshm_info_t *tmp;
273
274 if (kauth_cred_issuser(kauth_cred_get()) == 0) {
275 return EPERM;
276 }
277
278 PSHM_SUBSYS_LOCK();
279 RB_FOREACH_SAFE(p, pshmhead, &pshm_head, tmp) {
280 pshm_unlink_internal(p);
281 }
282 assert(pshmnument == 0);
283 PSHM_SUBSYS_UNLOCK();
284
285 return 0;
286 }
287
288 int
pshm_cache_purge_uid(uid_t uid)289 pshm_cache_purge_uid(uid_t uid)
290 {
291 pshm_info_t *p;
292 pshm_info_t *tmp;
293
294 if (kauth_cred_issuser(kauth_cred_get()) == 0) {
295 return EPERM;
296 }
297
298 PSHM_SUBSYS_LOCK();
299 RB_FOREACH_SAFE(p, pshmhead, &pshm_head, tmp) {
300 if (p->pshm_uid == uid) {
301 pshm_unlink_internal(p);
302 }
303 }
304 PSHM_SUBSYS_UNLOCK();
305
306 return 0;
307 }
308
309 /*
310 * Utility to get the shared memory name from userspace and
311 * populate a pshm_info_t with it. If there's a problem
312 * reading the name or it's malformed, will return an error code.
313 */
314 static int
pshm_get_name(pshm_info_t * pinfo,const user_addr_t user_addr)315 pshm_get_name(pshm_info_t *pinfo, const user_addr_t user_addr)
316 {
317 size_t bytes_copied = 0;
318 int error;
319
320
321 error = copyinstr(user_addr, &pinfo->pshm_hdr.pshm_name[0], PSHMNAMLEN + 1, &bytes_copied);
322 if (error != 0) {
323 return error;
324 }
325 assert(bytes_copied <= PSHMNAMLEN + 1);
326 assert(pinfo->pshm_hdr.pshm_name[bytes_copied - 1] == 0);
327 if (bytes_copied < 2) { /* 2: expect at least one character and terminating zero */
328 return EINVAL;
329 }
330 AUDIT_ARG(text, &pinfo->pshm_hdr.pshm_name[0]);
331 return 0;
332 }
333
334 /*
335 * Process a shm_open() system call.
336 */
337 int
shm_open(proc_t p,struct shm_open_args * uap,int32_t * retval)338 shm_open(proc_t p, struct shm_open_args *uap, int32_t *retval)
339 {
340 int indx;
341 int error = 0;
342 pshm_info_t *pinfo = NULL;
343 pshm_info_t *new_pinfo = NULL;
344 pshmnode_t *new_pnode = NULL;
345 struct fileproc *fp = NULL;
346 int fmode;
347 mode_t cmode = (mode_t)uap->mode;
348 bool incache = false;
349 bool have_label = false;
350
351 AUDIT_ARG(fflags, uap->oflag);
352 AUDIT_ARG(mode, cmode);
353
354 /*
355 * Allocate data structures we need. We parse the userspace name into
356 * a pshm_info_t, even when we don't need to O_CREAT.
357 */
358 new_pinfo = kalloc_type(pshm_info_t, Z_WAITOK | Z_ZERO | Z_NOFAIL);
359
360 /*
361 * Get and check the name.
362 */
363 error = pshm_get_name(new_pinfo, uap->name);
364 if (error != 0) {
365 goto bad;
366 }
367
368 /*
369 * Attempt to allocate a new fp. If unsuccessful, the fp will be
370 * left unmodified (NULL).
371 */
372 error = falloc(p, &fp, &indx);
373 if (error) {
374 goto bad;
375 }
376
377 cmode &= ALLPERMS;
378
379 fmode = FFLAGS(uap->oflag);
380 if ((fmode & (FREAD | FWRITE)) == 0) {
381 error = EINVAL;
382 goto bad;
383 }
384
385 /*
386 * Will need a new pnode for the file pointer
387 */
388 new_pnode = kalloc_type(pshmnode_t, Z_WAITOK | Z_ZERO);
389 if (new_pnode == NULL) {
390 error = ENOSPC;
391 goto bad;
392 }
393
394 /*
395 * If creating a new segment, fill in its information.
396 * If we find a pre-exisitng one in cache lookup we'll just toss this one later.
397 */
398 if (fmode & O_CREAT) {
399 new_pinfo->pshm_usecount = 2; /* one each for: file pointer, shm_unlink */
400 new_pinfo->pshm_length = 0;
401 new_pinfo->pshm_mode = cmode;
402 new_pinfo->pshm_uid = kauth_getuid();
403 new_pinfo->pshm_gid = kauth_getgid();
404 SLIST_INIT(&new_pinfo->pshm_mobjs);
405 #if CONFIG_MACF
406 mac_posixshm_label_init(&new_pinfo->pshm_hdr);
407 have_label = true;
408 error = mac_posixshm_check_create(kauth_cred_get(), new_pinfo->pshm_hdr.pshm_name);
409 if (error) {
410 goto bad;
411 }
412 #endif
413 }
414
415 /*
416 * Look up the named shared memory segment in the cache, possibly adding
417 * it for O_CREAT.
418 */
419 PSHM_SUBSYS_LOCK();
420
421 pinfo = pshm_cache_search(new_pinfo);
422 if (pinfo != NULL) {
423 incache = true;
424
425 /* Get a new reference to go with the file pointer.*/
426 error = pshm_ref(pinfo);
427 if (error) {
428 pinfo = NULL; /* so cleanup code doesn't deref */
429 goto bad_locked;
430 }
431
432 /* can't have pre-existing if O_EXCL */
433 if ((fmode & (O_CREAT | O_EXCL)) == (O_CREAT | O_EXCL)) {
434 error = EEXIST;
435 goto bad_locked;
436 }
437
438 /* O_TRUNC is only valid while length is not yet set */
439 if ((fmode & O_TRUNC) &&
440 (pinfo->pshm_flags & (PSHM_ALLOCATING | PSHM_ALLOCATED))) {
441 error = EINVAL;
442 goto bad_locked;
443 }
444 } else {
445 incache = false;
446
447 /* if it wasn't found, must have O_CREAT */
448 if (!(fmode & O_CREAT)) {
449 error = ENOENT;
450 goto bad_locked;
451 }
452
453 /* Add the new region to the cache. */
454 pinfo = new_pinfo;
455 pshm_cache_add(pinfo);
456 new_pinfo = NULL; /* so that it doesn't get free'd */
457 }
458
459 PSHM_SUBSYS_UNLOCK();
460
461 /*
462 * Check we have permission to access any pre-existing segment
463 */
464 if (incache) {
465 if (fmode & O_CREAT) {
466 AUDIT_ARG(posix_ipc_perm, pinfo->pshm_uid,
467 pinfo->pshm_gid, pinfo->pshm_mode);
468 }
469 #if CONFIG_MACF
470 if ((error = mac_posixshm_check_open(kauth_cred_get(), &pinfo->pshm_hdr, fmode))) {
471 goto bad;
472 }
473 #endif
474 if ((error = pshm_access(pinfo, fmode, kauth_cred_get(), p))) {
475 goto bad;
476 }
477 } else {
478 #if CONFIG_MACF
479 mac_posixshm_label_associate(kauth_cred_get(), &pinfo->pshm_hdr, pinfo->pshm_hdr.pshm_name);
480 #endif
481 }
482
483 fp->fp_flags |= FP_CLOEXEC;
484 fp->f_flag = fmode & FMASK;
485 fp->f_ops = &pshmops;
486 new_pnode->pinfo = pinfo;
487 fp_set_data(fp, new_pnode);
488
489 proc_fdlock(p);
490 procfdtbl_releasefd(p, indx, NULL);
491 fp_drop(p, indx, fp, 1);
492 proc_fdunlock(p);
493
494 *retval = indx;
495 error = 0;
496 goto done;
497
498 bad_locked:
499 PSHM_SUBSYS_UNLOCK();
500 bad:
501 /*
502 * Drop any new reference to a pre-existing shared memory region.
503 */
504 if (incache && pinfo != NULL) {
505 PSHM_SUBSYS_LOCK();
506 pshm_deref(pinfo);
507 PSHM_SUBSYS_UNLOCK();
508 }
509
510 /*
511 * Delete any allocated unused data structures.
512 */
513 kfree_type(pshmnode_t, new_pnode);
514
515 if (fp != NULL) {
516 fp_free(p, indx, fp);
517 }
518
519 done:
520 if (new_pinfo != NULL) {
521 #if CONFIG_MACF
522 if (have_label) {
523 mac_posixshm_label_destroy(&new_pinfo->pshm_hdr);
524 }
525 #endif
526 kfree_type(pshm_info_t, new_pinfo);
527 }
528 return error;
529 }
530
531
532 /*
533 * The truncate call associates memory with shared memory region. It can
534 * only be succesfully done with a non-zero length once per shared memory region.
535 */
536 int
pshm_truncate(__unused proc_t p,struct fileproc * fp,__unused int fd,off_t length,__unused int32_t * retval)537 pshm_truncate(
538 __unused proc_t p,
539 struct fileproc *fp,
540 __unused int fd,
541 off_t length,
542 __unused int32_t *retval)
543 {
544 pshm_info_t *pinfo;
545 pshmnode_t *pnode;
546 kern_return_t kret;
547 mem_entry_name_port_t mem_object;
548 mach_vm_size_t total_size, alloc_size;
549 memory_object_size_t mosize;
550 pshm_mobj_t *pshmobj, *pshmobj_last;
551 vm_map_t user_map;
552 int error;
553
554 user_map = current_map();
555
556 if (FILEGLOB_DTYPE(fp->fp_glob) != DTYPE_PSXSHM) {
557 return EINVAL;
558 }
559
560 #if 0
561 /*
562 * Can't enforce this yet, some third party tools don't
563 * specify O_RDWR like they ought to. See radar 48692182
564 */
565 /* ftruncate() requires write permission */
566 if (!(fp->f_flag & FWRITE)) {
567 return EINVAL;
568 }
569 #endif
570
571 PSHM_SUBSYS_LOCK();
572 if (((pnode = (pshmnode_t *)fp_get_data(fp))) == NULL) {
573 PSHM_SUBSYS_UNLOCK();
574 return EINVAL;
575 }
576
577 if ((pinfo = pnode->pinfo) == NULL) {
578 PSHM_SUBSYS_UNLOCK();
579 return EINVAL;
580 }
581
582 /* We only allow one ftruncate() per lifetime of the shm object. */
583 if (pinfo->pshm_flags & (PSHM_ALLOCATING | PSHM_ALLOCATED)) {
584 PSHM_SUBSYS_UNLOCK();
585 return EINVAL;
586 }
587
588 #if CONFIG_MACF
589 error = mac_posixshm_check_truncate(kauth_cred_get(), &pinfo->pshm_hdr, length);
590 if (error) {
591 PSHM_SUBSYS_UNLOCK();
592 return error;
593 }
594 #endif
595 /*
596 * Grab an extra reference, so we can drop the lock while allocating and
597 * ensure the objects don't disappear.
598 */
599 error = pshm_ref(pinfo);
600 if (error) {
601 PSHM_SUBSYS_UNLOCK();
602 return error;
603 }
604
605 /* set ALLOCATING, so another truncate can't start */
606 pinfo->pshm_flags |= PSHM_ALLOCATING;
607 total_size = vm_map_round_page(length, vm_map_page_mask(user_map));
608
609 pshmobj_last = NULL;
610 for (alloc_size = 0; alloc_size < total_size; alloc_size += mosize) {
611 PSHM_SUBSYS_UNLOCK();
612
613 /* get a memory object back some of the shared memory */
614 mosize = MIN(total_size - alloc_size, ANON_MAX_SIZE);
615 kret = mach_make_memory_entry_64(VM_MAP_NULL, &mosize, 0,
616 MAP_MEM_NAMED_CREATE | VM_PROT_DEFAULT, &mem_object, 0);
617
618 if (kret != KERN_SUCCESS) {
619 goto out;
620 }
621
622 /* get a list entry to track the memory object */
623 pshmobj = kalloc_type(pshm_mobj_t, Z_WAITOK | Z_NOFAIL);
624
625 PSHM_SUBSYS_LOCK();
626
627 /* link in the new entry */
628 pshmobj->pshmo_memobject = (void *)mem_object;
629 pshmobj->pshmo_size = mosize;
630 SLIST_NEXT(pshmobj, pshmo_next) = NULL;
631
632 if (pshmobj_last == NULL) {
633 SLIST_FIRST(&pinfo->pshm_mobjs) = pshmobj;
634 } else {
635 SLIST_INSERT_AFTER(pshmobj_last, pshmobj, pshmo_next);
636 }
637 pshmobj_last = pshmobj;
638 }
639
640 /* all done, change flags to ALLOCATED and return success */
641 pinfo->pshm_flags |= PSHM_ALLOCATED;
642 pinfo->pshm_flags &= ~(PSHM_ALLOCATING);
643 pinfo->pshm_length = total_size;
644 pshm_deref(pinfo); /* drop the "allocating" reference */
645 PSHM_SUBSYS_UNLOCK();
646 return 0;
647
648 out:
649 /* clean up any partially allocated objects */
650 PSHM_SUBSYS_LOCK();
651 while ((pshmobj = SLIST_FIRST(&pinfo->pshm_mobjs)) != NULL) {
652 SLIST_REMOVE_HEAD(&pinfo->pshm_mobjs, pshmo_next);
653 PSHM_SUBSYS_UNLOCK();
654 mach_memory_entry_port_release(pshmobj->pshmo_memobject);
655 kfree_type(pshm_mobj_t, pshmobj);
656 PSHM_SUBSYS_LOCK();
657 }
658 pinfo->pshm_flags &= ~PSHM_ALLOCATING;
659 pshm_deref(pinfo); /* drop the "allocating" reference */
660 PSHM_SUBSYS_UNLOCK();
661
662 switch (kret) {
663 case KERN_INVALID_ADDRESS:
664 case KERN_NO_SPACE:
665 return ENOMEM;
666 case KERN_PROTECTION_FAILURE:
667 return EACCES;
668 default:
669 return EINVAL;
670 }
671 }
672
673 int
pshm_stat(pshmnode_t * pnode,void * ub,int isstat64)674 pshm_stat(pshmnode_t *pnode, void *ub, int isstat64)
675 {
676 struct stat *sb = (struct stat *)0; /* warning avoidance ; protected by isstat64 */
677 struct stat64 * sb64 = (struct stat64 *)0; /* warning avoidance ; protected by isstat64 */
678 pshm_info_t *pinfo;
679 #if CONFIG_MACF
680 int error;
681 #endif
682
683 PSHM_SUBSYS_LOCK();
684 if ((pinfo = pnode->pinfo) == NULL) {
685 PSHM_SUBSYS_UNLOCK();
686 return EINVAL;
687 }
688
689 #if CONFIG_MACF
690 error = mac_posixshm_check_stat(kauth_cred_get(), &pinfo->pshm_hdr);
691 if (error) {
692 PSHM_SUBSYS_UNLOCK();
693 return error;
694 }
695 #endif
696
697 if (isstat64 != 0) {
698 sb64 = (struct stat64 *)ub;
699 bzero(sb64, sizeof(struct stat64));
700 sb64->st_mode = pinfo->pshm_mode;
701 sb64->st_uid = pinfo->pshm_uid;
702 sb64->st_gid = pinfo->pshm_gid;
703 sb64->st_size = pinfo->pshm_length;
704 } else {
705 sb = (struct stat *)ub;
706 bzero(sb, sizeof(struct stat));
707 sb->st_mode = pinfo->pshm_mode;
708 sb->st_uid = pinfo->pshm_uid;
709 sb->st_gid = pinfo->pshm_gid;
710 sb->st_size = pinfo->pshm_length;
711 }
712 PSHM_SUBSYS_UNLOCK();
713
714 return 0;
715 }
716
717 /*
718 * Verify access to a shared memory region.
719 */
720 static int
pshm_access(pshm_info_t * pinfo,int mode,kauth_cred_t cred,__unused proc_t p)721 pshm_access(pshm_info_t *pinfo, int mode, kauth_cred_t cred, __unused proc_t p)
722 {
723 mode_t mode_req = ((mode & FREAD) ? S_IRUSR : 0) |
724 ((mode & FWRITE) ? S_IWUSR : 0);
725
726 /* Otherwise, user id 0 always gets access. */
727 if (!suser(cred, NULL)) {
728 return 0;
729 }
730
731 return posix_cred_access(cred, pinfo->pshm_uid, pinfo->pshm_gid, pinfo->pshm_mode, mode_req);
732 }
733
734 int
pshm_mmap(__unused proc_t p,vm_map_offset_t user_addr,vm_map_size_t user_size,int prot,int flags,struct fileproc * fp,off_t file_pos,off_t pageoff,user_addr_t * retval)735 pshm_mmap(
736 __unused proc_t p,
737 vm_map_offset_t user_addr,
738 vm_map_size_t user_size,
739 int prot,
740 int flags,
741 struct fileproc *fp,
742 off_t file_pos,
743 off_t pageoff,
744 user_addr_t *retval)
745 {
746 vm_map_offset_t user_start_addr = 0;
747 vm_map_size_t map_size, mapped_size, pshm_size;
748 int max_prot = VM_PROT_DEFAULT;
749 vm_object_offset_t map_pos;
750 vm_map_t user_map;
751 vm_map_kernel_flags_t vmk_flags;
752 bool docow;
753 kern_return_t kret = KERN_SUCCESS;
754 pshm_info_t *pinfo;
755 pshmnode_t *pnode;
756 pshm_mobj_t *pshmobj;
757 int error;
758
759 if (user_size == 0) {
760 return 0;
761 }
762
763 if (!(flags & MAP_SHARED)) {
764 return EINVAL;
765 }
766
767 /* Can't allow write permission if the shm_open() didn't allow them. */
768 if (!(fp->f_flag & FWRITE)) {
769 if (prot & VM_PROT_WRITE) {
770 return EPERM;
771 }
772 max_prot &= ~VM_PROT_WRITE;
773 }
774
775 user_map = current_map();
776
777 PSHM_SUBSYS_LOCK();
778 pnode = (pshmnode_t *)fp_get_data(fp);
779 if (pnode == NULL) {
780 PSHM_SUBSYS_UNLOCK();
781 return EINVAL;
782 }
783
784 pinfo = pnode->pinfo;
785 if (pinfo == NULL) {
786 PSHM_SUBSYS_UNLOCK();
787 return EINVAL;
788 }
789
790 if (!(pinfo->pshm_flags & PSHM_ALLOCATED)) {
791 PSHM_SUBSYS_UNLOCK();
792 return EINVAL;
793 }
794
795 pshm_size = vm_map_round_page((vm_map_size_t)pinfo->pshm_length, vm_map_page_mask(user_map));
796
797 if (user_size > pshm_size) {
798 PSHM_SUBSYS_UNLOCK();
799 return EINVAL;
800 }
801
802 vm_map_size_t end_pos = 0;
803 if (os_add_overflow(user_size, file_pos, &end_pos)) {
804 PSHM_SUBSYS_UNLOCK();
805 return EINVAL;
806 }
807 if (end_pos > pshm_size) {
808 PSHM_SUBSYS_UNLOCK();
809 return EINVAL;
810 }
811
812 pshmobj = SLIST_FIRST(&pinfo->pshm_mobjs);
813 if (pshmobj == NULL) {
814 PSHM_SUBSYS_UNLOCK();
815 return EINVAL;
816 }
817
818 #if CONFIG_MACF
819 error = mac_posixshm_check_mmap(kauth_cred_get(), &pinfo->pshm_hdr, prot, flags);
820 if (error) {
821 PSHM_SUBSYS_UNLOCK();
822 return error;
823 }
824 #endif
825 /* Grab an extra reference, so we can drop the lock while mapping. */
826 error = pshm_ref(pinfo);
827 if (error) {
828 PSHM_SUBSYS_UNLOCK();
829 return error;
830 }
831
832 PSHM_SUBSYS_UNLOCK();
833
834 if (!(flags & MAP_FIXED)) {
835 user_addr = vm_map_round_page(user_addr,
836 vm_map_page_mask(user_map));
837 vmk_flags = VM_MAP_KERNEL_FLAGS_ANYWHERE();
838 } else {
839 if (user_addr != vm_map_round_page(user_addr,
840 vm_map_page_mask(user_map))) {
841 error = EINVAL;
842 goto out_deref;
843 }
844
845 /*
846 * We do not get rid of the existing mappings here because
847 * it wouldn't be atomic (see comment in mmap()). We let
848 * Mach VM know that we want it to replace any existing
849 * mapping with the new one.
850 */
851 vmk_flags = VM_MAP_KERNEL_FLAGS_FIXED(.vmf_overwrite = true);
852 }
853 docow = false;
854
855 mapped_size = 0;
856 /* reserve the entire space first... */
857 kret = mach_vm_map_kernel(user_map,
858 &user_addr,
859 user_size,
860 0,
861 vmk_flags,
862 IPC_PORT_NULL,
863 0,
864 false,
865 VM_PROT_NONE,
866 VM_PROT_NONE,
867 VM_INHERIT_NONE);
868 user_start_addr = user_addr;
869 if (kret != KERN_SUCCESS) {
870 goto out_deref;
871 }
872
873 /* Now overwrite with the real mappings. */
874 for (map_pos = 0, pshmobj = SLIST_FIRST(&pinfo->pshm_mobjs);
875 user_size != 0;
876 map_pos += pshmobj->pshmo_size, pshmobj = SLIST_NEXT(pshmobj, pshmo_next)) {
877 if (pshmobj == NULL) {
878 /* nothing there to map !? */
879 goto out_deref;
880 }
881 if (file_pos >= map_pos + pshmobj->pshmo_size) {
882 continue;
883 }
884 map_size = (vm_map_size_t)(pshmobj->pshmo_size - (file_pos - map_pos));
885 if (map_size > user_size) {
886 map_size = user_size;
887 }
888
889 kret = mach_vm_map_kernel(
890 user_map,
891 &user_addr,
892 map_size,
893 0,
894 VM_MAP_KERNEL_FLAGS_FIXED(.vmf_overwrite = true),
895 pshmobj->pshmo_memobject,
896 file_pos - map_pos,
897 docow,
898 prot,
899 max_prot,
900 VM_INHERIT_SHARE);
901 if (kret != KERN_SUCCESS) {
902 goto out_deref;
903 }
904
905 user_addr += map_size;
906 user_size -= map_size;
907 mapped_size += map_size;
908 file_pos += map_size;
909 }
910
911 PSHM_SUBSYS_LOCK();
912 pnode->mapp_addr = user_start_addr;
913 pinfo->pshm_flags |= (PSHM_MAPPED | PSHM_INUSE);
914 PSHM_SUBSYS_UNLOCK();
915 out_deref:
916 PSHM_SUBSYS_LOCK();
917 pshm_deref(pinfo); /* drop the extra reference we had while mapping. */
918 PSHM_SUBSYS_UNLOCK();
919 if (kret != KERN_SUCCESS) {
920 if (mapped_size != 0) {
921 (void) mach_vm_deallocate(current_map(),
922 user_start_addr,
923 mapped_size);
924 }
925 }
926
927 switch (kret) {
928 case KERN_SUCCESS:
929 *retval = (user_addr_t)(user_start_addr + pageoff);
930 return 0;
931 case KERN_INVALID_ADDRESS:
932 case KERN_NO_SPACE:
933 return ENOMEM;
934 case KERN_PROTECTION_FAILURE:
935 return EACCES;
936 default:
937 return EINVAL;
938 }
939 }
940
941 /*
942 * Remove a shared memory region name from the name lookup cache.
943 */
944 static void
pshm_unlink_internal(pshm_info_t * pinfo)945 pshm_unlink_internal(pshm_info_t *pinfo)
946 {
947 PSHM_SUBSYS_ASSERT_HELD();
948
949 pshm_cache_delete(pinfo);
950 pinfo->pshm_flags |= PSHM_REMOVED;
951
952 /* release the "unlink" reference */
953 pshm_deref(pinfo);
954 }
955
956 int
shm_unlink(proc_t p,struct shm_unlink_args * uap,__unused int32_t * retval)957 shm_unlink(proc_t p, struct shm_unlink_args *uap, __unused int32_t *retval)
958 {
959 int error = 0;
960 pshm_info_t *pinfo = NULL;
961 pshm_info_t *name_pinfo = NULL;
962
963 /*
964 * Get the name from user args.
965 */
966 name_pinfo = kalloc_type(pshm_info_t,
967 Z_WAITOK | Z_ZERO | Z_NOFAIL);
968 error = pshm_get_name(name_pinfo, uap->name);
969 if (error != 0) {
970 error = EINVAL;
971 goto bad;
972 }
973
974 PSHM_SUBSYS_LOCK();
975 pinfo = pshm_cache_search(name_pinfo);
976
977 if (pinfo == NULL) {
978 error = ENOENT;
979 goto bad_unlock;
980 }
981
982 #if CONFIG_MACF
983 error = mac_posixshm_check_unlink(kauth_cred_get(), &pinfo->pshm_hdr, name_pinfo->pshm_hdr.pshm_name);
984 if (error) {
985 goto bad_unlock;
986 }
987 #endif
988
989 AUDIT_ARG(posix_ipc_perm, pinfo->pshm_uid, pinfo->pshm_gid, pinfo->pshm_mode);
990
991 /*
992 * Following file semantics, unlink should normally be allowed
993 * for users with write permission only. We also allow the creator
994 * of a segment to be able to delete, even w/o write permission.
995 * That's because there's no equivalent of write permission for the
996 * directory containing a file.
997 */
998 error = pshm_access(pinfo, FWRITE, kauth_cred_get(), p);
999 if (error != 0 && pinfo->pshm_uid != kauth_getuid()) {
1000 goto bad_unlock;
1001 }
1002
1003 pshm_unlink_internal(pinfo);
1004 error = 0;
1005 bad_unlock:
1006 PSHM_SUBSYS_UNLOCK();
1007 bad:
1008 kfree_type(pshm_info_t, name_pinfo);
1009 return error;
1010 }
1011
1012 /*
1013 * Add a new reference to a shared memory region.
1014 * Fails if we will overflow the reference counter.
1015 */
1016 static int
pshm_ref(pshm_info_t * pinfo)1017 pshm_ref(pshm_info_t *pinfo)
1018 {
1019 PSHM_SUBSYS_ASSERT_HELD();
1020
1021 if (pinfo->pshm_usecount == PSHM_MAXCOUNT) {
1022 return EMFILE;
1023 }
1024 pinfo->pshm_usecount++;
1025 return 0;
1026 }
1027
1028 /*
1029 * Dereference a pshm_info_t. Delete the region if
1030 * this was the final reference count.
1031 */
1032 static void
pshm_deref(pshm_info_t * pinfo)1033 pshm_deref(pshm_info_t *pinfo)
1034 {
1035 pshm_mobj_t *pshmobj;
1036
1037 PSHM_SUBSYS_ASSERT_HELD();
1038 if (pinfo->pshm_usecount == 0) {
1039 panic("negative usecount in pshm_close");
1040 }
1041 pinfo->pshm_usecount--; /* release this fd's reference */
1042
1043 if (pinfo->pshm_usecount == 0) {
1044 #if CONFIG_MACF
1045 mac_posixshm_label_destroy(&pinfo->pshm_hdr);
1046 #endif
1047 PSHM_SUBSYS_UNLOCK();
1048
1049 /*
1050 * Release references to any backing objects.
1051 */
1052 while ((pshmobj = SLIST_FIRST(&pinfo->pshm_mobjs)) != NULL) {
1053 SLIST_REMOVE_HEAD(&pinfo->pshm_mobjs, pshmo_next);
1054 mach_memory_entry_port_release(pshmobj->pshmo_memobject);
1055 kfree_type(pshm_mobj_t, pshmobj);
1056 }
1057
1058 /* free the pinfo itself */
1059 kfree_type(pshm_info_t, pinfo);
1060
1061 PSHM_SUBSYS_LOCK();
1062 }
1063 }
1064
1065 /* vfs_context_t passed to match prototype for struct fileops */
1066 static int
pshm_closefile(struct fileglob * fg,__unused vfs_context_t ctx)1067 pshm_closefile(struct fileglob *fg, __unused vfs_context_t ctx)
1068 {
1069 int error = EINVAL;
1070 pshmnode_t *pnode;
1071
1072 PSHM_SUBSYS_LOCK();
1073
1074 pnode = (pshmnode_t *)fg_get_data(fg);
1075 if (pnode != NULL) {
1076 error = 0;
1077 fg_set_data(fg, NULL); /* set fg_data to NULL to avoid racing close()es */
1078 if (pnode->pinfo != NULL) {
1079 pshm_deref(pnode->pinfo);
1080 pnode->pinfo = NULL;
1081 }
1082 }
1083
1084 PSHM_SUBSYS_UNLOCK();
1085 kfree_type(pshmnode_t, pnode);
1086
1087 return error;
1088 }
1089
1090 int
fill_pshminfo(pshmnode_t * pshm,struct pshm_info * info)1091 fill_pshminfo(pshmnode_t * pshm, struct pshm_info * info)
1092 {
1093 pshm_info_t *pinfo;
1094 struct vinfo_stat *sb;
1095
1096 PSHM_SUBSYS_LOCK();
1097 if ((pinfo = pshm->pinfo) == NULL) {
1098 PSHM_SUBSYS_UNLOCK();
1099 return EINVAL;
1100 }
1101
1102 sb = &info->pshm_stat;
1103
1104 bzero(sb, sizeof(struct vinfo_stat));
1105 sb->vst_mode = pinfo->pshm_mode;
1106 sb->vst_uid = pinfo->pshm_uid;
1107 sb->vst_gid = pinfo->pshm_gid;
1108 sb->vst_size = pinfo->pshm_length;
1109
1110 info->pshm_mappaddr = pshm->mapp_addr;
1111 bcopy(&pinfo->pshm_hdr.pshm_name[0], &info->pshm_name[0], PSHMNAMLEN + 1);
1112
1113 PSHM_SUBSYS_UNLOCK();
1114 return 0;
1115 }
1116
1117 #if CONFIG_MACF
1118 void
pshm_label_associate(struct fileproc * fp,struct vnode * vp,vfs_context_t ctx)1119 pshm_label_associate(struct fileproc *fp, struct vnode *vp, vfs_context_t ctx)
1120 {
1121 pshmnode_t *pnode;
1122 pshm_info_t *pshm;
1123
1124 PSHM_SUBSYS_LOCK();
1125 pnode = (pshmnode_t *)fp_get_data(fp);
1126 if (pnode != NULL) {
1127 pshm = pnode->pinfo;
1128 if (pshm != NULL) {
1129 mac_posixshm_vnode_label_associate(
1130 vfs_context_ucred(ctx), &pshm->pshm_hdr,
1131 mac_posixshm_label(&pshm->pshm_hdr), vp, mac_vnode_label(vp));
1132 }
1133 }
1134 PSHM_SUBSYS_UNLOCK();
1135 }
1136 #endif
1137