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