xref: /xnu-11417.101.15/bsd/vfs/vfs_exclave_fs.c (revision e3723e1f17661b24996789d8afc084c0c3303b26)
1 /*
2  * Copyright (c) 2022-2024 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 #include <string.h>
30 #include <sys/fcntl.h>
31 #include <sys/fsctl.h>
32 #include <sys/namei.h>
33 #include <sys/stat.h>
34 #include <sys/vnode.h>
35 #include <sys/vnode_internal.h>
36 #include <sys/uio_internal.h>
37 #include <sys/fsevents.h>
38 #include <kern/kalloc.h>
39 #include <vfs/vfs_exclave_fs.h>
40 #include <miscfs/devfs/devfs.h>
41 #include <pexpert/pexpert.h>
42 
43 __private_extern__ int unlink1(vfs_context_t, vnode_t, user_addr_t,
44     enum uio_seg, int);
45 
46 // Flags for open vnodes, currently used only in DEVELOPMENT or DEBUG builds
47 #define OV_EXCLAVE_BASE 1
48 #define OV_FORCE_ENOSPC 2 // When this flag is set, writes fail with ENOSPC
49 
50 struct open_vnode {
51 	LIST_ENTRY(open_vnode) chain;
52 	vnode_t vp;
53 	dev_t dev;
54 	uint64_t file_id;
55 	uint32_t open_count;
56 #if (DEVELOPMENT || DEBUG)
57 	uint32_t flags;
58 #endif
59 };
60 
61 #define ROOT_DIR_INO_NUM 2
62 
63 #define VFS_EXCLAVE_FS_BASE_DIR_GRAFT 1
64 #define VFS_EXCLAVE_FS_BASE_DIR_SEALED 2
65 
66 typedef struct registered_fs_tag {
67 	LIST_ENTRY(registered_fs_tag) link;
68 	uint32_t fstag;
69 	uint32_t flags;
70 	vnode_t vp;
71 	dev_t dev;
72 	fsioc_graft_info_t graft_info;
73 } registered_fs_tag_t;
74 
75 /* hash table that maps from file_id to a vnode and its open count */
76 typedef LIST_HEAD(open_vnode_head, open_vnode) open_vnodes_list_head_t;
77 static open_vnodes_list_head_t *open_vnodes_hashtbl = NULL;
78 static u_long open_vnodes_hashmask = 0;
79 static int open_vnodes_hashsize = 0;
80 static uint32_t num_open_vnodes = 0;
81 
82 /* registered base directories */
83 typedef LIST_HEAD(registered_tags_head, registered_fs_tag) registered_tags_head_t;
84 static registered_tags_head_t *registered_tags_hash = NULL;
85 static uint32_t num_tags_registered = 0;
86 static u_long rft_hashmask;
87 
88 #define REGFSTAG_HASH_WIDTH 32
89 
90 static LCK_GRP_DECLARE(vfs_exclave_lck_grp, "vfs_exclave");
91 
92 /* protects registered_tags list and num_tags_registered counter */
93 static lck_mtx_t regtag_mtx;
94 
95 /* protects open vnodes hash table */
96 static lck_mtx_t open_vnodes_mtx;
97 
98 #define HASHFUNC(dev, file_id) (((dev) + (file_id)) & open_vnodes_hashmask)
99 #define OPEN_VNODES_HASH(dev, file_id) (&open_vnodes_hashtbl[HASHFUNC(dev, file_id)])
100 
101 #if (DEVELOPMENT || DEBUG)
102 static bool integrity_checks_disabled = false;
103 #define EXCLAVE_INTEGRITY_CHECKS_DISABLED_BOOTARG "disable_integrity_checks"
104 static bool vfs_exclave_is_enospc_exclave(const char *exclave_id);
105 #endif
106 
107 static int exclave_fs_open_internal(uint32_t fs_tag, uint64_t root_id,
108     const char *path, int flags, uint32_t ov_flags, uint64_t *file_id);
109 static int vfs_exclave_fs_unregister_internal(vnode_t vp, bool take_basedir_lock);
110 
111 static uint32_t
hash_fstag(uint32_t tag)112 hash_fstag(uint32_t tag)
113 {
114 	return tag % (rft_hashmask + 1);
115 }
116 
117 static registered_tags_head_t *
get_registered_tags_chain(uint32_t tag)118 get_registered_tags_chain(uint32_t tag)
119 {
120 	return registered_tags_hash + hash_fstag(tag);
121 }
122 
123 /*
124  * Get the fsid and fileid attributes of the given vnode.
125  */
126 static int
get_vnode_info(vnode_t vp,dev_t * dev,fsid_t * fsid,uint64_t * file_id)127 get_vnode_info(vnode_t vp, dev_t *dev, fsid_t *fsid, uint64_t *file_id)
128 {
129 	struct vnode_attr va;
130 	int error;
131 
132 	memset(&va, 0, sizeof(va));
133 	VATTR_INIT(&va);
134 	if (dev) {
135 		VATTR_WANTED(&va, va_fsid);
136 	}
137 	if (fsid) {
138 		VATTR_WANTED(&va, va_fsid64);
139 	}
140 	if (file_id) {
141 		VATTR_WANTED(&va, va_fileid);
142 	}
143 
144 	error = vnode_getattr(vp, &va, vfs_context_kernel());
145 	if (error) {
146 		return error;
147 	}
148 
149 	if (dev) {
150 		if (!VATTR_IS_SUPPORTED(&va, va_fsid)) {
151 			return ENOTSUP;
152 		}
153 		*dev = va.va_fsid;
154 	}
155 
156 	if (fsid) {
157 		if (!VATTR_IS_SUPPORTED(&va, va_fsid64)) {
158 			return ENOTSUP;
159 		}
160 		*fsid = va.va_fsid64;
161 	}
162 
163 	if (file_id) {
164 		if (!VATTR_IS_SUPPORTED(&va, va_fileid)) {
165 			return ENOTSUP;
166 		}
167 		*file_id = va.va_fileid;
168 	}
169 
170 	return 0;
171 }
172 
173 static inline bool
is_graft(registered_fs_tag_t * rft)174 is_graft(registered_fs_tag_t *rft)
175 {
176 	return rft->flags & VFS_EXCLAVE_FS_BASE_DIR_GRAFT;
177 }
178 
179 static inline bool
is_sealed(registered_fs_tag_t * rft)180 is_sealed(registered_fs_tag_t *rft)
181 {
182 	return rft->flags & VFS_EXCLAVE_FS_BASE_DIR_SEALED;
183 }
184 
185 static int
graft_to_host_inum(fsioc_graft_info_t * gi,uint64_t graft_inum,uint64_t * host_inum)186 graft_to_host_inum(fsioc_graft_info_t *gi, uint64_t graft_inum, uint64_t *host_inum)
187 {
188 	if (graft_inum == ROOT_DIR_INO_NUM) {
189 		*host_inum = gi->gi_graft_dir;
190 	} else if (graft_inum < gi->gi_inum_len) {
191 		*host_inum = gi->gi_inum_base + graft_inum;
192 	} else {
193 		return ERANGE;
194 	}
195 
196 	return 0;
197 }
198 
199 static int
host_to_graft_inum(fsioc_graft_info_t * gi,uint64_t host_inum,uint64_t * graft_inum)200 host_to_graft_inum(fsioc_graft_info_t *gi, uint64_t host_inum, uint64_t *graft_inum)
201 {
202 	if (host_inum == gi->gi_graft_dir) {
203 		*graft_inum = ROOT_DIR_INO_NUM;
204 	} else if ((host_inum >= gi->gi_inum_base) && (host_inum < gi->gi_inum_base + gi->gi_inum_len)) {
205 		*graft_inum = host_inum - gi->gi_inum_base;
206 	} else {
207 		return ERANGE;
208 	}
209 
210 	return 0;
211 }
212 
213 /*
214  * Check if a vnode is in an APFS graft and if so obtain information about the graft.
215  */
216 static int
get_graft_info(vnode_t vp,bool * is_graft,fsioc_graft_info_t * graft_info)217 get_graft_info(vnode_t vp, bool *is_graft, fsioc_graft_info_t *graft_info)
218 {
219 	fsioc_get_graft_info_t ggi = {0};
220 	uint16_t alloc_count;
221 	fsioc_graft_info_t *graft_infos = NULL;
222 	int error = 0;
223 
224 	*is_graft = false;
225 
226 	error = VNOP_IOCTL(vp, FSIOC_GET_GRAFT_INFO, (caddr_t)&ggi, 0, vfs_context_kernel());
227 	if (error) {
228 		return error;
229 	}
230 
231 	if (!ggi.ggi_is_in_graft) {
232 		return 0;
233 	}
234 
235 	if (ggi.ggi_count == 0) {
236 		return EINVAL;
237 	}
238 
239 	alloc_count = ggi.ggi_count;
240 
241 	graft_infos = kalloc_type(fsioc_graft_info_t, alloc_count, Z_WAITOK | Z_ZERO);
242 	if (!graft_infos) {
243 		return ENOMEM;
244 	}
245 
246 	memset(&ggi, 0, sizeof(ggi));
247 	ggi.ggi_count = alloc_count;
248 	ggi.ggi_buffer = (user64_addr_t)graft_infos;
249 
250 	error = VNOP_IOCTL(vp, FSIOC_GET_GRAFT_INFO, (caddr_t)&ggi, 0, vfs_context_kernel());
251 	if (error) {
252 		goto out;
253 	}
254 
255 	if (!ggi.ggi_is_in_graft) {
256 		error = EAGAIN;
257 		goto out;
258 	}
259 
260 	if (ggi.ggi_graft_index >= alloc_count) {
261 		error = ERANGE;
262 		goto out;
263 	}
264 
265 	*graft_info = graft_infos[ggi.ggi_graft_index];
266 	*is_graft = true;
267 
268 out:
269 	if (graft_infos) {
270 		kfree_type(fsioc_graft_info_t, alloc_count, graft_infos);
271 	}
272 
273 	return error;
274 }
275 
276 static bool
is_fs_writeable(uint32_t fs_tag)277 is_fs_writeable(uint32_t fs_tag)
278 {
279 	return (fs_tag == EFT_EXCLAVE) || (fs_tag == EFT_EXCLAVE_MAIN);
280 }
281 
282 /*
283  * Set a base directory for the given fs tag.
284  */
285 static int
set_base_dir(uint32_t fs_tag,vnode_t vp,fsioc_graft_info_t * graft_info,bool is_sealed)286 set_base_dir(uint32_t fs_tag, vnode_t vp, fsioc_graft_info_t *graft_info, bool is_sealed)
287 {
288 	dev_t dev;
289 	int error = 0;
290 	registered_fs_tag_t *rft;
291 
292 	lck_mtx_lock(&regtag_mtx);
293 
294 	registered_tags_head_t *rfthead = get_registered_tags_chain(fs_tag);
295 
296 	LIST_FOREACH(rft, rfthead, link) {
297 		if (rft->fstag == fs_tag) {
298 			// Check if the registered vp is DEAD, it can be the case in edu mode where the original location was unmounted
299 			// if the vnode is dead unregister it, and continue with setting new base_dir
300 			if (vnode_vtype(rft->vp) == VBAD) {
301 				vfs_exclave_fs_unregister_internal(rft->vp, false);
302 				break;
303 			}
304 
305 			error = (rft->vp == vp) ? EALREADY : EBUSY;
306 			goto out;
307 		}
308 	}
309 
310 	error = get_vnode_info(vp, &dev, NULL, NULL);
311 	if (error) {
312 		goto out;
313 	}
314 
315 	/*
316 	 * make sure that a writable fs does not share a dev_t with another non writable fs (and vice versa)
317 	 * since writable vnodes are opened RW whereas non writable fs vnodes
318 	 * are opened RO
319 	 */
320 	int i;
321 	bool is_writable_fs_tag = is_fs_writeable(fs_tag);
322 	for (i = 0; i <= rft_hashmask; i++) {
323 		registered_tags_head_t *head = registered_tags_hash + i;
324 		LIST_FOREACH(rft, head, link) {
325 			if ((is_fs_writeable(rft->fstag) != is_writable_fs_tag) && rft->dev == dev) {
326 				printf("tag %u has same device 0x%x as tag %u\n", fs_tag, rft->fstag, dev);
327 				error = EBUSY;
328 				goto out;
329 			}
330 		}
331 	}
332 
333 	rft = kalloc_type(registered_fs_tag_t, Z_WAITOK | Z_ZERO);
334 	if (rft == NULL) {
335 		error = ENOMEM;
336 		goto out;
337 	}
338 
339 	if (graft_info) {
340 		rft->flags |= VFS_EXCLAVE_FS_BASE_DIR_GRAFT;
341 		if (is_sealed) {
342 			rft->flags |= VFS_EXCLAVE_FS_BASE_DIR_SEALED;
343 		}
344 		rft->graft_info = *graft_info;
345 	}
346 
347 	rft->fstag = fs_tag;
348 	rft->vp = vp;
349 	rft->dev = dev;
350 	LIST_INSERT_HEAD(rfthead, rft, link);
351 
352 	num_tags_registered++;
353 
354 out:
355 	lck_mtx_unlock(&regtag_mtx);
356 	return error;
357 }
358 
359 /*
360  * Get the base directory entry for the given fs tag. If vpp is passed, return
361  * with an iocount taken on the vnode.
362  */
363 static int
get_base_dir(uint32_t fs_tag,registered_fs_tag_t * base_dir,vnode_t * vpp)364 get_base_dir(uint32_t fs_tag, registered_fs_tag_t *base_dir, vnode_t *vpp)
365 {
366 	int error = ENOENT;
367 	registered_fs_tag_t *rft;
368 
369 	if (!base_dir && !vpp) {
370 		return EINVAL;
371 	}
372 
373 	lck_mtx_lock(&regtag_mtx);
374 
375 	registered_tags_head_t *rfthead = get_registered_tags_chain(fs_tag);
376 
377 	LIST_FOREACH(rft, rfthead, link) {
378 		if (rft->fstag == fs_tag) {
379 			if (vpp) {
380 				vnode_t base_vp = rft->vp;
381 				error = vnode_getwithref(base_vp);
382 				if (error) {
383 					break;
384 				}
385 				*vpp = base_vp;
386 			}
387 
388 			if (base_dir) {
389 				*base_dir = *rft;
390 			}
391 			error = 0;
392 			break;
393 		}
394 	}
395 
396 	lck_mtx_unlock(&regtag_mtx);
397 	return error;
398 }
399 
400 int
vfs_exclave_fs_start(void)401 vfs_exclave_fs_start(void)
402 {
403 	lck_mtx_init(&regtag_mtx, &vfs_exclave_lck_grp, LCK_ATTR_NULL);
404 	lck_mtx_init(&open_vnodes_mtx, &vfs_exclave_lck_grp, LCK_ATTR_NULL);
405 
406 	assert(open_vnodes_hashtbl == NULL);
407 
408 	open_vnodes_hashsize = desiredvnodes / 16;
409 	open_vnodes_hashtbl = hashinit(open_vnodes_hashsize, M_VNODE, &open_vnodes_hashmask);
410 	if (open_vnodes_hashtbl == NULL) {
411 		open_vnodes_hashsize = open_vnodes_hashmask = 0;
412 		return ENOMEM;
413 	}
414 
415 	registered_tags_hash = hashinit(REGFSTAG_HASH_WIDTH, M_VNODE /*unused*/, &rft_hashmask);
416 	if (registered_tags_hash == NULL) {
417 		hashdestroy(open_vnodes_hashtbl, M_VNODE, open_vnodes_hashmask);
418 		open_vnodes_hashtbl = NULL;
419 		open_vnodes_hashmask = open_vnodes_hashsize = 0;
420 		return ENOMEM;
421 	}
422 
423 #if (DEVELOPMENT || DEBUG)
424 	uint32_t bootarg_val;
425 	if (PE_parse_boot_argn(EXCLAVE_INTEGRITY_CHECKS_DISABLED_BOOTARG, &bootarg_val, sizeof(bootarg_val))) {
426 		if (bootarg_val) {
427 			integrity_checks_disabled = true;
428 		}
429 	}
430 #endif
431 
432 	return 0;
433 }
434 
435 static bool
exclave_fs_started(void)436 exclave_fs_started(void)
437 {
438 	return open_vnodes_hashtbl != NULL;
439 }
440 
441 static void release_open_vnodes(registered_fs_tag_t *);
442 
443 static void
drop_registered_tag(registered_fs_tag_t * rft)444 drop_registered_tag(registered_fs_tag_t *rft)
445 {
446 	release_open_vnodes(rft);
447 
448 	vnode_rele(rft->vp);
449 	LIST_REMOVE(rft, link);
450 	kfree_type(registered_fs_tag_t, rft);
451 	num_tags_registered--;
452 }
453 
454 void
vfs_exclave_fs_stop(void)455 vfs_exclave_fs_stop(void)
456 {
457 	registered_fs_tag_t *rft, *nxt;
458 	int i;
459 
460 	if (!exclave_fs_started()) {
461 		return;
462 	}
463 
464 	/* No need to lock regtag_mtx - this function assumes
465 	 * single-threaded context */
466 	for (i = 0; i <= rft_hashmask; i++) {
467 		registered_tags_head_t *rfthead = registered_tags_hash + i;
468 
469 		LIST_FOREACH_SAFE(rft, rfthead, link, nxt) {
470 			drop_registered_tag(rft);
471 		}
472 	}
473 
474 	hashdestroy(registered_tags_hash, M_VNODE, rft_hashmask);
475 
476 	assert(num_open_vnodes == 0);
477 	assert(open_vnodes_hashtbl);
478 
479 	hashdestroy(open_vnodes_hashtbl, M_VNODE, open_vnodes_hashmask);
480 	open_vnodes_hashtbl = NULL;
481 	open_vnodes_hashmask = open_vnodes_hashsize = 0;
482 
483 	lck_mtx_destroy(&regtag_mtx, &vfs_exclave_lck_grp);
484 	lck_mtx_destroy(&open_vnodes_mtx, &vfs_exclave_lck_grp);
485 
486 #if (DEVELOPMENT || DEBUG)
487 	integrity_checks_disabled = false;
488 #endif
489 }
490 
491 int
vfs_exclave_fs_register(uint32_t fs_tag,vnode_t vp)492 vfs_exclave_fs_register(uint32_t fs_tag, vnode_t vp)
493 {
494 	char vfs_name[MFSNAMELEN];
495 	bool is_graft;
496 	fsioc_graft_info_t graft_info;
497 	int error;
498 
499 	if (!exclave_fs_started()) {
500 		return ENXIO;
501 	}
502 
503 #if !defined(XNU_TARGET_OS_OSX)
504 	if (fs_tag == EFT_EXCLAVE_MAIN) {
505 		return ENOTSUP;
506 	}
507 #endif
508 
509 	vnode_vfsname(vp, vfs_name);
510 	if (strcmp(vfs_name, "apfs")) {
511 		return ENOTSUP;
512 	}
513 
514 	if (!vnode_isdir(vp)) {
515 		return ENOTDIR;
516 	}
517 
518 	error = get_graft_info(vp, &is_graft, &graft_info);
519 	if (error) {
520 		return error;
521 	}
522 
523 	if (is_graft && is_fs_writeable(fs_tag)) {
524 		return EROFS;
525 	}
526 
527 	error = vnode_ref(vp);
528 	if (error) {
529 		return error;
530 	}
531 
532 	// Check if tag is sealed, RW tags are always not sealed
533 	bool is_sealed = false;
534 	if (!is_fs_writeable(fs_tag)) {
535 		error = VNOP_IOCTL(vp, FSIOC_EVAL_ROOTAUTH, NULL, 0, vfs_context_kernel());
536 		if (!error) {
537 			is_sealed = true;
538 		}
539 	}
540 
541 	error = set_base_dir(fs_tag, vp, is_graft ? &graft_info : NULL, is_sealed);
542 	if (error) {
543 		vnode_rele(vp);
544 		// if this directory is already registered in this tag do not consider it as an error
545 		if (error == EALREADY) {
546 			error = 0;
547 		}
548 		return error;
549 	}
550 
551 	return 0;
552 }
553 
554 int
vfs_exclave_fs_register_path(uint32_t fs_tag,const char * base_path)555 vfs_exclave_fs_register_path(uint32_t fs_tag, const char *base_path)
556 {
557 	struct nameidata nd;
558 	int error;
559 
560 	if (!exclave_fs_started()) {
561 		return ENXIO;
562 	}
563 
564 	NDINIT(&nd, LOOKUP, OP_LOOKUP, FOLLOW, UIO_SYSSPACE,
565 	    CAST_USER_ADDR_T(base_path), vfs_context_kernel());
566 
567 	error = namei(&nd);
568 	if (error) {
569 		return error;
570 	}
571 
572 	error = vfs_exclave_fs_register(fs_tag, nd.ni_vp);
573 
574 	vnode_put(nd.ni_vp);
575 	nameidone(&nd);
576 
577 	return error;
578 }
579 
580 /*
581  * Release open vnodes for the given fs_tag.
582  * regtag_mtx must be locked by caller.
583  */
584 static void
release_open_vnodes(registered_fs_tag_t * base_dir)585 release_open_vnodes(registered_fs_tag_t *base_dir)
586 {
587 	dev_t dev;
588 	int i;
589 
590 	lck_mtx_lock(&open_vnodes_mtx);
591 
592 	if (num_open_vnodes == 0) {
593 		goto done;
594 	}
595 
596 	dev = base_dir->dev;
597 
598 	if (num_tags_registered > 1) {
599 		/* skip release if another base dir has the same device */
600 		for (i = 0; i <= rft_hashmask; i++) {
601 			registered_tags_head_t *rfthead = registered_tags_hash + i;
602 			registered_fs_tag_t *rft;
603 
604 			LIST_FOREACH(rft, rfthead, link) {
605 				if ((rft != base_dir) && (rft->dev == dev)) {
606 					goto done;
607 				}
608 			}
609 		}
610 	}
611 
612 	for (i = 0; i < open_vnodes_hashmask + 1; i++) {
613 		struct open_vnode *entry, *temp_entry;
614 
615 		LIST_FOREACH_SAFE(entry, &open_vnodes_hashtbl[i], chain, temp_entry) {
616 			if (entry->dev != dev) {
617 				continue;
618 			}
619 			while (entry->open_count) {
620 				vnode_rele(entry->vp);
621 				entry->open_count--;
622 			}
623 			LIST_REMOVE(entry, chain);
624 			kfree_type(struct open_vnode, entry);
625 			num_open_vnodes--;
626 		}
627 	}
628 
629 done:
630 	lck_mtx_unlock(&open_vnodes_mtx);
631 }
632 
633 static int
vfs_exclave_fs_unregister_internal(vnode_t vp,bool take_basedir_lock)634 vfs_exclave_fs_unregister_internal(vnode_t vp, bool take_basedir_lock)
635 {
636 	int error = ENOENT;
637 	int i;
638 
639 	if (!exclave_fs_started()) {
640 		return ENXIO;
641 	}
642 
643 	if (take_basedir_lock) {
644 		lck_mtx_lock(&regtag_mtx);
645 	}
646 
647 	for (i = 0; i <= rft_hashmask; i++) {
648 		registered_tags_head_t *rfthead = registered_tags_hash + i;
649 		registered_fs_tag_t *rft, *nxt;
650 
651 		LIST_FOREACH_SAFE(rft, rfthead, link, nxt) {
652 			if (rft->vp == vp) {
653 				drop_registered_tag(rft);
654 				error = 0;
655 				goto done;
656 			}
657 		}
658 	}
659 
660 done:
661 
662 	if (take_basedir_lock) {
663 		lck_mtx_unlock(&regtag_mtx);
664 	}
665 
666 	return error;
667 }
668 
669 int
vfs_exclave_fs_unregister(vnode_t vp)670 vfs_exclave_fs_unregister(vnode_t vp)
671 {
672 	return vfs_exclave_fs_unregister_internal(vp, true);
673 }
674 
675 int
vfs_exclave_fs_get_base_dirs(void * buf,uint32_t * count)676 vfs_exclave_fs_get_base_dirs(void *buf, uint32_t *count)
677 {
678 	int error = 0;
679 	uint32_t num_copied = 0;
680 	exclave_fs_base_dir_t *dirs = (exclave_fs_base_dir_t *)buf;
681 	int i;
682 
683 	if (!count || (dirs && !*count)) {
684 		return EINVAL;
685 	}
686 
687 	lck_mtx_lock(&regtag_mtx);
688 
689 	if (!dirs) {
690 		*count = num_tags_registered;
691 		goto out;
692 	} else if (*count < num_tags_registered) {
693 		error = ENOSPC;
694 		goto out;
695 	}
696 
697 	for (i = 0; i <= rft_hashmask; i++) {
698 		registered_tags_head_t *rfthead = registered_tags_hash + i;
699 		registered_fs_tag_t *base_dir;
700 
701 		LIST_FOREACH(base_dir, rfthead, link) {
702 			exclave_fs_base_dir_t *out_dir = &dirs[num_copied];
703 
704 			memset(out_dir, 0, sizeof(exclave_fs_base_dir_t));
705 
706 			error = get_vnode_info(base_dir->vp, NULL, &out_dir->fsid, &out_dir->base_dir);
707 			if (error) {
708 				goto out;
709 			}
710 
711 			out_dir->fs_tag = base_dir->fstag;
712 			out_dir->graft_file = is_graft(base_dir) ? base_dir->graft_info.gi_graft_file : 0;
713 			num_copied++;
714 		}
715 	}
716 
717 	*count = num_copied;
718 
719 out:
720 	lck_mtx_unlock(&regtag_mtx);
721 	return error;
722 }
723 
724 static int
create_exclave_dir(vnode_t base_vp,const char * exclave_id)725 create_exclave_dir(vnode_t base_vp, const char *exclave_id)
726 {
727 	vnode_t vp = NULLVP, dvp = NULLVP;
728 	vfs_context_t ctx;
729 	struct vnode_attr va, *vap = &va;
730 	struct nameidata nd;
731 	int update_flags = 0;
732 	int error;
733 
734 	ctx = vfs_context_kernel();
735 
736 	NDINIT(&nd, CREATE, OP_MKDIR, LOCKPARENT | AUDITVNPATH1, UIO_SYSSPACE,
737 	    CAST_USER_ADDR_T(exclave_id), ctx);
738 	nd.ni_cnd.cn_flags |= WILLBEDIR;
739 
740 continue_lookup:
741 	nd.ni_dvp = base_vp;
742 	nd.ni_cnd.cn_flags |= USEDVP;
743 
744 	error = namei(&nd);
745 	if (error) {
746 		return error;
747 	}
748 
749 	dvp = nd.ni_dvp;
750 	vp = nd.ni_vp;
751 
752 	if (vp != NULLVP) {
753 		error = EEXIST;
754 		goto out;
755 	}
756 
757 	nd.ni_cnd.cn_flags &= ~USEDVP;
758 
759 	VATTR_INIT(vap);
760 	VATTR_SET(vap, va_mode, S_IRWXU | S_IRWXG);
761 	VATTR_SET(vap, va_type, VDIR);
762 
763 	error = vn_authorize_mkdir(dvp, &nd.ni_cnd, vap, ctx, NULL);
764 	if (error) {
765 		goto out;
766 	}
767 
768 	error = vn_create(dvp, &vp, &nd, vap, 0, 0, NULL, ctx);
769 	if (error == EKEEPLOOKING) {
770 		nd.ni_vp = vp;
771 		goto continue_lookup;
772 	}
773 
774 	if (error) {
775 		goto out;
776 	}
777 
778 	if (vp->v_name == NULL) {
779 		update_flags |= VNODE_UPDATE_NAME;
780 	}
781 	if (vp->v_parent == NULLVP) {
782 		update_flags |= VNODE_UPDATE_PARENT;
783 	}
784 
785 	if (update_flags) {
786 		vnode_update_identity(vp, dvp, nd.ni_cnd.cn_nameptr,
787 		    nd.ni_cnd.cn_namelen, nd.ni_cnd.cn_hash, update_flags);
788 	}
789 
790 out:
791 	nameidone(&nd);
792 	if (vp) {
793 		vnode_put(vp);
794 	}
795 	if (dvp) {
796 		vnode_put(dvp);
797 	}
798 
799 	return error;
800 }
801 
802 int
vfs_exclave_fs_root(const char * exclave_id,uint64_t * root_id)803 vfs_exclave_fs_root(const char *exclave_id, uint64_t *root_id)
804 {
805 	return vfs_exclave_fs_root_ex(EFT_EXCLAVE, exclave_id, root_id);
806 }
807 
808 int
vfs_exclave_fs_root_ex(uint32_t fs_tag,const char * exclave_id,uint64_t * root_id)809 vfs_exclave_fs_root_ex(uint32_t fs_tag, const char *exclave_id, uint64_t *root_id)
810 {
811 	int error;
812 	uint32_t ov_flags = 0;
813 
814 	if (!exclave_fs_started()) {
815 		return ENXIO;
816 	}
817 
818 	if (!is_fs_writeable(fs_tag)) {
819 		/* root is valid only on RW tags */
820 		return EINVAL;
821 	}
822 
823 	if (strchr(exclave_id, '/') || !strcmp(exclave_id, ".") || !strcmp(exclave_id, "..")) {
824 		/* don't allow an exclave_id that looks like a path */
825 		return EINVAL;
826 	}
827 
828 #if (DEVELOPMENT || DEBUG)
829 	if (vfs_exclave_is_enospc_exclave(exclave_id)) {
830 		ov_flags = OV_EXCLAVE_BASE | OV_FORCE_ENOSPC;
831 	}
832 #endif
833 
834 	error = exclave_fs_open_internal(fs_tag, EXCLAVE_FS_BASEDIR_ROOT_ID,
835 	    exclave_id, O_DIRECTORY, ov_flags, root_id);
836 
837 	if (error == ENOENT) {
838 		vnode_t base_vp;
839 
840 		error = get_base_dir(fs_tag, NULL, &base_vp);
841 		if (error) {
842 			return error;
843 		}
844 
845 		error = create_exclave_dir(base_vp, exclave_id);
846 		if (!error) {
847 			error = exclave_fs_open_internal(fs_tag, EXCLAVE_FS_BASEDIR_ROOT_ID,
848 			    exclave_id, O_DIRECTORY, ov_flags, root_id);
849 		}
850 
851 		vnode_put(base_vp);
852 	}
853 
854 	return error;
855 }
856 
857 /*
858  * Find a vnode in the open vnodes hash table with the given file_id
859  * under a base dir, take an iocount on it and return it.
860  * If base dir is a graft, file_id should be the graft inode number.
861  */
862 static int
get_open_vnode(registered_fs_tag_t * base_dir,uint64_t file_id,vnode_t * vpp,uint32_t * ov_flags)863 get_open_vnode(registered_fs_tag_t *base_dir, uint64_t file_id, vnode_t *vpp, uint32_t *ov_flags)
864 {
865 	uint64_t vp_file_id;
866 	struct open_vnode *entry;
867 	int error;
868 
869 	if (is_graft(base_dir)) {
870 		error = graft_to_host_inum(&base_dir->graft_info, file_id, &vp_file_id);
871 		if (error) {
872 			return error;
873 		}
874 	} else {
875 		vp_file_id = file_id;
876 	}
877 
878 	error = ENOENT;
879 
880 	lck_mtx_lock(&open_vnodes_mtx);
881 
882 	LIST_FOREACH(entry, OPEN_VNODES_HASH(base_dir->dev, vp_file_id), chain) {
883 		if ((entry->dev == base_dir->dev) && (entry->file_id == vp_file_id)) {
884 			error = vnode_getwithref(entry->vp);
885 			if (!error) {
886 				*vpp = entry->vp;
887 				if (ov_flags) {
888 #if (DEVELOPMENT || DEBUG)
889 					*ov_flags = entry->flags;
890 #else
891 					*ov_flags = 0;
892 #endif
893 				}
894 			}
895 			break;
896 		}
897 	}
898 
899 	lck_mtx_unlock(&open_vnodes_mtx);
900 	return error;
901 }
902 
903 /*
904  * Increment a vnode open count in the open vnodes hash table.
905  * If base dir is a graft, file_id should be the host inode number.
906  * Also update entry's flags
907  */
908 static int
increment_vnode_open_count(vnode_t vp,registered_fs_tag_t * base_dir,uint64_t file_id,uint32_t flags)909 increment_vnode_open_count(vnode_t vp, registered_fs_tag_t *base_dir, uint64_t file_id, uint32_t flags)
910 {
911 	struct open_vnode *entry;
912 	open_vnodes_list_head_t *list;
913 	int error = 0;
914 
915 	lck_mtx_lock(&open_vnodes_mtx);
916 
917 	list = OPEN_VNODES_HASH(base_dir->dev, file_id);
918 
919 	LIST_FOREACH(entry, list, chain) {
920 		if ((entry->dev == base_dir->dev) && (entry->file_id == file_id)) {
921 			break;
922 		}
923 	}
924 
925 	if (!entry) {
926 		entry = kalloc_type(struct open_vnode, Z_WAITOK | Z_ZERO);
927 		if (!entry) {
928 			error = ENOMEM;
929 			goto out;
930 		}
931 		entry->vp = vp;
932 		entry->dev = base_dir->dev;
933 		entry->file_id = file_id;
934 		LIST_INSERT_HEAD(list, entry, chain);
935 		num_open_vnodes++;
936 	}
937 
938 	entry->open_count++;
939 #if (DEVELOPMENT || DEBUG)
940 	entry->flags |= flags;
941 #else
942 #pragma unused(flags)
943 #endif
944 
945 out:
946 	lck_mtx_unlock(&open_vnodes_mtx);
947 	return error;
948 }
949 
950 /*
951  * Decrement a vnode open count in the open vnodes hash table and
952  * return it with an iocount taken on it.
953  * If base dir is a graft, file_id should be the graft inode number.
954  */
955 static int
decrement_vnode_open_count(registered_fs_tag_t * base_dir,uint64_t file_id,vnode_t * vpp)956 decrement_vnode_open_count(registered_fs_tag_t *base_dir, uint64_t file_id, vnode_t *vpp)
957 {
958 	struct open_vnode *entry;
959 	vnode_t vp;
960 	uint64_t vp_file_id;
961 	int error = 0;
962 
963 	if (is_graft(base_dir)) {
964 		error = graft_to_host_inum(&base_dir->graft_info, file_id, &vp_file_id);
965 		if (error) {
966 			return error;
967 		}
968 	} else {
969 		vp_file_id = file_id;
970 	}
971 
972 	lck_mtx_lock(&open_vnodes_mtx);
973 
974 	LIST_FOREACH(entry, OPEN_VNODES_HASH(base_dir->dev, vp_file_id), chain) {
975 		if ((entry->dev == base_dir->dev) && (entry->file_id == vp_file_id)) {
976 			break;
977 		}
978 	}
979 
980 	if (!entry) {
981 		error = ENOENT;
982 		goto out;
983 	}
984 
985 	vp = entry->vp;
986 	entry->open_count--;
987 
988 	if (entry->open_count == 0) {
989 		LIST_REMOVE(entry, chain);
990 		kfree_type(struct open_vnode, entry);
991 		num_open_vnodes--;
992 	}
993 
994 	error = vnode_getwithref(vp);
995 	if (!error) {
996 		*vpp = vp;
997 	}
998 
999 out:
1000 	lck_mtx_unlock(&open_vnodes_mtx);
1001 	return error;
1002 }
1003 
1004 static int
exclave_fs_open_internal(uint32_t fs_tag,uint64_t root_id,const char * path,int flags,uint32_t ov_flags,uint64_t * file_id)1005 exclave_fs_open_internal(uint32_t fs_tag, uint64_t root_id, const char *path,
1006     int flags, uint32_t ov_flags, uint64_t *file_id)
1007 {
1008 	vnode_t dvp = NULLVP, vp = NULLVP;
1009 	registered_fs_tag_t base_dir;
1010 	vfs_context_t ctx;
1011 	struct nameidata *ndp = NULL;
1012 	struct vnode_attr *vap = NULL;
1013 	uint64_t vp_file_id;
1014 	int error;
1015 	uint32_t ndflags = NOCROSSMOUNT;
1016 	uint32_t root_ov_flags = 0;
1017 
1018 	if (flags & ~(O_CREAT | O_DIRECTORY)) {
1019 		return EINVAL;
1020 	}
1021 
1022 	if (is_fs_writeable(fs_tag)) {
1023 		ndflags |= NOFOLLOW;
1024 	} else {
1025 		ndflags |= FOLLOW;
1026 	}
1027 
1028 	if ((flags & O_CREAT) && !is_fs_writeable(fs_tag)) {
1029 		return EROFS;
1030 	}
1031 
1032 	if (root_id == EXCLAVE_FS_BASEDIR_ROOT_ID) {
1033 		error = get_base_dir(fs_tag, &base_dir, &dvp);
1034 	} else {
1035 		error = get_base_dir(fs_tag, &base_dir, NULL);
1036 		if (!error) {
1037 			error = get_open_vnode(&base_dir, root_id, &dvp, &root_ov_flags);
1038 		}
1039 	}
1040 
1041 #if (DEVELOPMENT || DEBUG)
1042 	// inherit the ENOSPC flag from the root
1043 	ov_flags |= (root_ov_flags & OV_FORCE_ENOSPC);
1044 #endif
1045 
1046 	if (error) {
1047 		return error;
1048 	}
1049 
1050 	// if we need to create the file, then delete it first (so that we won't reuse the same inode number)
1051 	if ((flags & O_CREAT) && !(flags & O_DIRECTORY)) {
1052 		error = unlink1(vfs_context_kernel(), dvp, CAST_USER_ADDR_T(path), UIO_SYSSPACE, 0);
1053 		if (error) {
1054 			if (error == ENOENT) {
1055 				error = 0;
1056 			} else {
1057 				goto out;
1058 			}
1059 		}
1060 
1061 		// Add an O_EXCL flag so that create will fail if the file is already there after delete (a possible attack)
1062 		flags |= O_EXCL;
1063 	}
1064 
1065 	ndp = kalloc_type(struct nameidata, Z_WAITOK);
1066 	if (!ndp) {
1067 		error = ENOMEM;
1068 		goto out;
1069 	}
1070 
1071 	ctx = vfs_context_kernel();
1072 
1073 	NDINIT(ndp, LOOKUP, OP_OPEN, ndflags, UIO_SYSSPACE,
1074 	    CAST_USER_ADDR_T(path), ctx);
1075 
1076 	ndp->ni_rootdir = dvp;
1077 	ndp->ni_flag = NAMEI_ROOTDIR;
1078 	ndp->ni_dvp = dvp;
1079 	ndp->ni_cnd.cn_flags |= USEDVP;
1080 
1081 	vap = kalloc_type(struct vnode_attr, Z_WAITOK);
1082 	if (!vap) {
1083 		error = ENOMEM;
1084 		goto out;
1085 	}
1086 
1087 	VATTR_INIT(vap);
1088 	VATTR_SET(vap, va_mode, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP);
1089 
1090 	flags |= FREAD;
1091 
1092 	if (is_fs_writeable(fs_tag) && (root_id != EXCLAVE_FS_BASEDIR_ROOT_ID)) {
1093 		flags |= FWRITE;
1094 	}
1095 
1096 	error = vn_open_auth(ndp, &flags, vap, NULLVP);
1097 	if (error) {
1098 		goto out;
1099 	}
1100 
1101 	vp = ndp->ni_vp;
1102 
1103 	error = get_vnode_info(vp, NULL, NULL, &vp_file_id);
1104 	if (error) {
1105 		goto out;
1106 	}
1107 
1108 	if (is_graft(&base_dir)) {
1109 		error = host_to_graft_inum(&base_dir.graft_info, vp_file_id, file_id);
1110 		if (error) {
1111 			goto out;
1112 		}
1113 	} else {
1114 		*file_id = vp_file_id;
1115 	}
1116 
1117 	error = increment_vnode_open_count(vp, &base_dir, vp_file_id, ov_flags);
1118 
1119 out:
1120 	if (dvp) {
1121 		vnode_put(dvp);
1122 	}
1123 	if (vp) {
1124 		vnode_put(vp);
1125 	}
1126 	if (ndp) {
1127 		kfree_type(struct nameidata, ndp);
1128 	}
1129 	if (vap) {
1130 		kfree_type(struct vnode_attr, vap);
1131 	}
1132 
1133 	return error;
1134 }
1135 
1136 int
vfs_exclave_fs_open(uint32_t fs_tag,uint64_t root_id,const char * name,uint64_t * file_id)1137 vfs_exclave_fs_open(uint32_t fs_tag, uint64_t root_id, const char *name, uint64_t *file_id)
1138 {
1139 	if (!exclave_fs_started()) {
1140 		return ENXIO;
1141 	}
1142 
1143 	if (is_fs_writeable(fs_tag) && (root_id == EXCLAVE_FS_BASEDIR_ROOT_ID)) {
1144 		return EINVAL;
1145 	}
1146 
1147 	return exclave_fs_open_internal(fs_tag, root_id, name, 0, 0, file_id);
1148 }
1149 
1150 int
vfs_exclave_fs_create(uint32_t fs_tag,uint64_t root_id,const char * name,uint64_t * file_id)1151 vfs_exclave_fs_create(uint32_t fs_tag, uint64_t root_id, const char *name, uint64_t *file_id)
1152 {
1153 	if (!exclave_fs_started()) {
1154 		return ENXIO;
1155 	}
1156 
1157 	if (is_fs_writeable(fs_tag) && (root_id == EXCLAVE_FS_BASEDIR_ROOT_ID)) {
1158 		return EINVAL;
1159 	}
1160 
1161 	return exclave_fs_open_internal(fs_tag, root_id, name, O_CREAT, 0, file_id);
1162 }
1163 
1164 int
vfs_exclave_fs_close(uint32_t fs_tag,uint64_t file_id)1165 vfs_exclave_fs_close(uint32_t fs_tag, uint64_t file_id)
1166 {
1167 	vnode_t vp = NULLVP;
1168 	registered_fs_tag_t base_dir;
1169 	int flags = FREAD;
1170 	int error;
1171 
1172 	if (!exclave_fs_started()) {
1173 		return ENXIO;
1174 	}
1175 
1176 	error = get_base_dir(fs_tag, &base_dir, NULL);
1177 	if (error) {
1178 		return error;
1179 	}
1180 
1181 	error = decrement_vnode_open_count(&base_dir, file_id, &vp);
1182 	if (error) {
1183 		goto out;
1184 	}
1185 
1186 	if (is_fs_writeable(fs_tag) && !vnode_isdir(vp)) {
1187 		flags |= FWRITE;
1188 	}
1189 
1190 	error = vn_close(vp, flags, vfs_context_kernel());
1191 
1192 out:
1193 	if (vp) {
1194 		vnode_put(vp);
1195 	}
1196 
1197 	return error;
1198 }
1199 
1200 static int
exclave_fs_io(uint32_t fs_tag,uint64_t file_id,uint64_t offset,uint64_t length,uint8_t * data,bool read)1201 exclave_fs_io(uint32_t fs_tag, uint64_t file_id, uint64_t offset, uint64_t length, uint8_t *data, bool read)
1202 {
1203 	vnode_t vp = NULLVP;
1204 	registered_fs_tag_t base_dir;
1205 	UIO_STACKBUF(uio_buf, 1);
1206 	uio_t auio = NULL;
1207 	int error = 0;
1208 	uint32_t ov_flags = 0;
1209 
1210 	if (!read && !is_fs_writeable(fs_tag)) {
1211 		return EROFS;
1212 	}
1213 
1214 	error = get_base_dir(fs_tag, &base_dir, NULL);
1215 	if (error) {
1216 		return error;
1217 	}
1218 
1219 	error = get_open_vnode(&base_dir, file_id, &vp, &ov_flags);
1220 	if (error) {
1221 		goto out;
1222 	}
1223 
1224 	if (!read && (ov_flags & OV_FORCE_ENOSPC)) {
1225 		error = ENOSPC;
1226 		goto out;
1227 	}
1228 
1229 	auio = uio_createwithbuffer(1, offset, UIO_SYSSPACE, read ? UIO_READ : UIO_WRITE,
1230 	    &uio_buf[0], sizeof(uio_buf));
1231 	if (!auio) {
1232 		error = ENOMEM;
1233 		goto out;
1234 	}
1235 
1236 	error = uio_addiov(auio, (uintptr_t)data, length);
1237 	if (error) {
1238 		goto out;
1239 	}
1240 
1241 	if (read) {
1242 		error = VNOP_READ(vp, auio, 0, vfs_context_kernel());
1243 	} else {
1244 		error = VNOP_WRITE(vp, auio, 0, vfs_context_kernel());
1245 	}
1246 
1247 	if (!error && uio_resid(auio)) {
1248 		error = EIO;
1249 	}
1250 
1251 out:
1252 	if (vp) {
1253 		vnode_put(vp);
1254 	}
1255 
1256 	return error;
1257 }
1258 
1259 int
vfs_exclave_fs_read(uint32_t fs_tag,uint64_t file_id,uint64_t file_offset,uint64_t length,void * data)1260 vfs_exclave_fs_read(uint32_t fs_tag, uint64_t file_id, uint64_t file_offset, uint64_t length, void *data)
1261 {
1262 	if (!exclave_fs_started()) {
1263 		return ENXIO;
1264 	}
1265 
1266 	return exclave_fs_io(fs_tag, file_id, file_offset, length, data, true);
1267 }
1268 
1269 int
vfs_exclave_fs_write(uint32_t fs_tag,uint64_t file_id,uint64_t file_offset,uint64_t length,void * data)1270 vfs_exclave_fs_write(uint32_t fs_tag, uint64_t file_id, uint64_t file_offset, uint64_t length, void *data)
1271 {
1272 	if (!exclave_fs_started()) {
1273 		return ENXIO;
1274 	}
1275 
1276 	return exclave_fs_io(fs_tag, file_id, file_offset, length, (void *)data, false);
1277 }
1278 
1279 int
vfs_exclave_fs_remove(uint32_t fs_tag,uint64_t root_id,const char * name)1280 vfs_exclave_fs_remove(uint32_t fs_tag, uint64_t root_id, const char *name)
1281 {
1282 	vnode_t rvp = NULLVP;
1283 	registered_fs_tag_t base_dir;
1284 	int error;
1285 
1286 	if (!exclave_fs_started()) {
1287 		return ENXIO;
1288 	}
1289 
1290 	if (!is_fs_writeable(fs_tag)) {
1291 		return EROFS;
1292 	}
1293 
1294 	error = get_base_dir(fs_tag, &base_dir, NULL);
1295 	if (error) {
1296 		return error;
1297 	}
1298 
1299 	error = get_open_vnode(&base_dir, root_id, &rvp, NULL);
1300 	if (error) {
1301 		return error;
1302 	}
1303 
1304 	error = unlink1(vfs_context_kernel(), rvp, CAST_USER_ADDR_T(name), UIO_SYSSPACE, 0);
1305 
1306 	if (rvp) {
1307 		vnode_put(rvp);
1308 	}
1309 
1310 	return error;
1311 }
1312 
1313 int
vfs_exclave_fs_sync(uint32_t fs_tag,uint64_t file_id,uint64_t sync_op)1314 vfs_exclave_fs_sync(uint32_t fs_tag, uint64_t file_id, uint64_t sync_op)
1315 {
1316 	vnode_t vp = NULLVP;
1317 	registered_fs_tag_t base_dir;
1318 	u_long command;
1319 	int error;
1320 
1321 	if (!exclave_fs_started()) {
1322 		return ENXIO;
1323 	}
1324 
1325 	if (!is_fs_writeable(fs_tag)) {
1326 		return EROFS;
1327 	}
1328 
1329 	if (sync_op == EXCLAVE_FS_SYNC_OP_BARRIER) {
1330 		command = F_BARRIERFSYNC;
1331 	} else if (sync_op == EXCLAVE_FS_SYNC_OP_FULL) {
1332 		command = F_FULLFSYNC;
1333 	} else if (sync_op != EXCLAVE_FS_SYNC_OP_UBC) {
1334 		return EINVAL;
1335 	}
1336 
1337 	error = get_base_dir(fs_tag, &base_dir, NULL);
1338 	if (error) {
1339 		return error;
1340 	}
1341 
1342 	error = get_open_vnode(&base_dir, file_id, &vp, NULL);
1343 	if (error) {
1344 		goto out;
1345 	}
1346 
1347 	if (sync_op == EXCLAVE_FS_SYNC_OP_UBC) {
1348 		error = VNOP_FSYNC(vp, MNT_WAIT, vfs_context_kernel());
1349 	} else {
1350 		error = VNOP_IOCTL(vp, command, (caddr_t)NULL, 0, vfs_context_kernel());
1351 	}
1352 
1353 out:
1354 	if (vp) {
1355 		vnode_put(vp);
1356 	}
1357 
1358 	return error;
1359 }
1360 
1361 static int
map_graft_dirents(fsioc_graft_info_t * graft_info,void * dirent_buf,int32_t count)1362 map_graft_dirents(fsioc_graft_info_t *graft_info, void *dirent_buf, int32_t count)
1363 {
1364 	int i, error = 0;
1365 
1366 	for (i = 0; i < count; i++) {
1367 		exclave_fs_dirent_t *dirent = (exclave_fs_dirent_t *)dirent_buf;
1368 		uint64_t mapped_file_id;
1369 
1370 		error = host_to_graft_inum(graft_info, dirent->file_id, &mapped_file_id);
1371 		if (error) {
1372 			return error;
1373 		}
1374 		dirent->file_id = mapped_file_id;
1375 		dirent_buf = (char *)dirent_buf + dirent->length;
1376 	}
1377 
1378 	return 0;
1379 }
1380 
1381 int
vfs_exclave_fs_readdir(uint32_t fs_tag,uint64_t file_id,void * dirent_buf,uint32_t buf_size,int32_t * count)1382 vfs_exclave_fs_readdir(uint32_t fs_tag, uint64_t file_id, void *dirent_buf,
1383     uint32_t buf_size, int32_t *count)
1384 {
1385 	vnode_t dvp = NULLVP;
1386 	registered_fs_tag_t base_dir;
1387 	UIO_STACKBUF(uio_buf, 1);
1388 	uio_t auio = NULL;
1389 	vfs_context_t ctx;
1390 	uthread_t ut;
1391 	struct attrlist al;
1392 	struct vnode_attr *vap = NULL;
1393 	char *va_name = NULL;
1394 	int32_t eofflag;
1395 	int error;
1396 
1397 	if (!exclave_fs_started()) {
1398 		return ENXIO;
1399 	}
1400 
1401 	error = get_base_dir(fs_tag, &base_dir, NULL);
1402 	if (error) {
1403 		return error;
1404 	}
1405 
1406 	/*
1407 	 * For ExclaveOS readdir through VFS is not permitted in RELEASE xnu
1408 	 * variants. Directory enumeration should be based on the data in the
1409 	 * integrity catalogue. Error out here if a request is routed here
1410 	 * in this circumstance.
1411 	 */
1412 	if (fs_tag == EFT_SYSTEM) {
1413 #if (DEVELOPMENT || DEBUG)
1414 		/*
1415 		 * For non-RELEASE xnu variants, we allow readdir to
1416 		 * be routed through VFS if the relevant integrity checks
1417 		 * are disabled, or if the underlying volume is not sealed.
1418 		 */
1419 		if (!integrity_checks_disabled && is_sealed(&base_dir)) {
1420 			return ENOTSUP;
1421 		}
1422 #else
1423 		// This is the RELEASE xnu case above
1424 		return ENOTSUP;
1425 #endif
1426 	}
1427 
1428 	error = get_open_vnode(&base_dir, file_id, &dvp, NULL);
1429 	if (error) {
1430 		goto out;
1431 	}
1432 
1433 	if (!vnode_isdir(dvp)) {
1434 		error = ENOTDIR;
1435 		goto out;
1436 	}
1437 
1438 	auio = uio_createwithbuffer(1, 0, UIO_SYSSPACE, UIO_READ,
1439 	    &uio_buf[0], sizeof(uio_buf));
1440 	if (!auio) {
1441 		error = ENOMEM;
1442 		goto out;
1443 	}
1444 
1445 	error = uio_addiov(auio, (uintptr_t)dirent_buf, buf_size);
1446 	if (error) {
1447 		goto out;
1448 	}
1449 
1450 	al.bitmapcount = ATTR_BIT_MAP_COUNT;
1451 	al.commonattr  = ATTR_CMN_RETURNED_ATTRS | ATTR_CMN_NAME | ATTR_CMN_OBJTYPE | ATTR_CMN_FILEID;
1452 	al.fileattr = ATTR_FILE_DATALENGTH;
1453 
1454 	vap = kalloc_type(struct vnode_attr, Z_WAITOK);
1455 	if (!vap) {
1456 		error = ENOMEM;
1457 		goto out;
1458 	}
1459 
1460 	VATTR_INIT(vap);
1461 	va_name = zalloc_flags(ZV_NAMEI, Z_WAITOK | Z_ZERO);
1462 	if (!va_name) {
1463 		error = ENOMEM;
1464 		goto out;
1465 	}
1466 	vap->va_name = va_name;
1467 
1468 	VATTR_SET_ACTIVE(vap, va_name);
1469 	VATTR_SET_ACTIVE(vap, va_objtype);
1470 	VATTR_SET_ACTIVE(vap, va_fileid);
1471 	VATTR_SET_ACTIVE(vap, va_total_size);
1472 	VATTR_SET_ACTIVE(vap, va_data_size);
1473 
1474 	ctx = vfs_context_kernel();
1475 	ut = current_uthread();
1476 
1477 	ut->uu_flag |= UT_KERN_RAGE_VNODES;
1478 	error = VNOP_GETATTRLISTBULK(dvp, &al, vap, auio, NULL,
1479 	    0, &eofflag, count, ctx);
1480 	ut->uu_flag &= ~UT_KERN_RAGE_VNODES;
1481 
1482 	if (!error && !eofflag) {
1483 		return ENOBUFS;
1484 	}
1485 
1486 	if (is_graft(&base_dir)) {
1487 		error = map_graft_dirents(&base_dir.graft_info, dirent_buf, *count);
1488 		if (error) {
1489 			goto out;
1490 		}
1491 	}
1492 
1493 out:
1494 	if (va_name) {
1495 		zfree(ZV_NAMEI, va_name);
1496 	}
1497 	if (vap) {
1498 		kfree_type(struct vnode_attr, vap);
1499 	}
1500 	if (dvp) {
1501 		vnode_put(dvp);
1502 	}
1503 
1504 	return error;
1505 }
1506 
1507 int
vfs_exclave_fs_getsize(uint32_t fs_tag,uint64_t file_id,uint64_t * size)1508 vfs_exclave_fs_getsize(uint32_t fs_tag, uint64_t file_id, uint64_t *size)
1509 {
1510 	vnode_t vp = NULLVP;
1511 	registered_fs_tag_t base_dir;
1512 	vfs_context_t ctx;
1513 	struct vnode_attr *vap = NULL;
1514 	int error;
1515 
1516 	if (!exclave_fs_started()) {
1517 		return ENXIO;
1518 	}
1519 
1520 	error = get_base_dir(fs_tag, &base_dir, NULL);
1521 	if (error) {
1522 		return error;
1523 	}
1524 
1525 	error = get_open_vnode(&base_dir, file_id, &vp, NULL);
1526 	if (error) {
1527 		goto out;
1528 	}
1529 
1530 	if (vnode_isdir(vp)) {
1531 		error = EISDIR;
1532 		goto out;
1533 	}
1534 
1535 	vap = kalloc_type(struct vnode_attr, Z_WAITOK);
1536 	if (!vap) {
1537 		error = ENOMEM;
1538 		goto out;
1539 	}
1540 
1541 	VATTR_INIT(vap);
1542 	VATTR_WANTED(vap, va_data_size);
1543 
1544 	ctx = vfs_context_kernel();
1545 
1546 	error = VNOP_GETATTR(vp, vap, ctx);
1547 	if (error) {
1548 		goto out;
1549 	}
1550 
1551 	if (!VATTR_IS_SUPPORTED(vap, va_data_size)) {
1552 		error = ENOTSUP;
1553 		goto out;
1554 	}
1555 
1556 	*size = vap->va_data_size;
1557 
1558 out:
1559 	if (vap) {
1560 		kfree_type(struct vnode_attr, vap);
1561 	}
1562 	if (vp) {
1563 		vnode_put(vp);
1564 	}
1565 
1566 	return error;
1567 }
1568 
1569 int
vfs_exclave_fs_sealstate(uint32_t fs_tag,bool * sealed)1570 vfs_exclave_fs_sealstate(uint32_t fs_tag, bool *sealed)
1571 {
1572 	registered_fs_tag_t base_dir;
1573 	int error;
1574 
1575 	if (!exclave_fs_started()) {
1576 		return ENXIO;
1577 	}
1578 
1579 	error = get_base_dir(fs_tag, &base_dir, NULL);
1580 	if (error) {
1581 		return error;
1582 	}
1583 
1584 	*sealed = is_sealed(&base_dir);
1585 
1586 	return 0;
1587 }
1588 
1589 #if DEVELOPMENT || DEBUG
1590 
1591 #define ENOSPC_EXCLAVES_LEN 256
1592 static char enospc_exclaves[ENOSPC_EXCLAVES_LEN];
1593 
1594 static bool
vfs_exclave_is_enospc_exclave(const char * exclave_id)1595 vfs_exclave_is_enospc_exclave(const char *exclave_id)
1596 {
1597 	char *element;
1598 	char *scratch_base;
1599 	char *scratch;
1600 	size_t buf_len = strlen(enospc_exclaves) + 1;
1601 	bool is_enospc_exclave = false;
1602 
1603 	/* allocate a scratch buffer the size of the string */
1604 	scratch_base = kalloc_data(buf_len, Z_WAITOK);
1605 	if (scratch_base == NULL) {
1606 		goto out;
1607 	}
1608 
1609 	/* copy the elementlist to the scratch buffer */
1610 	strlcpy(scratch_base, enospc_exclaves, buf_len);
1611 
1612 	/*
1613 	 * set up a temporary pointer that can be used to iterate the
1614 	 * scratch buffer without losing the allocation address
1615 	 */
1616 	scratch = scratch_base;
1617 
1618 	/* iterate the scratch buffer; NOTE: buffer contents modified! */
1619 	while ((element = strsep(&scratch, ",")) != NULL) {
1620 		if (strcmp(element, exclave_id) == 0) {
1621 			printf("%s is enospc exclave\n", exclave_id);
1622 			is_enospc_exclave = true;
1623 			goto out;
1624 		}
1625 	}
1626 
1627 out:
1628 	if (scratch_base != NULL) {
1629 		kfree_data(scratch_base, buf_len);
1630 	}
1631 
1632 	return is_enospc_exclave;
1633 }
1634 
1635 SYSCTL_STRING(_kern, OID_AUTO, enospc_exclaves, CTLFLAG_RW | CTLFLAG_LOCKED, enospc_exclaves, sizeof(enospc_exclaves), "List of comma-separated exclave_ids for writing immediately returns ENOSPC");
1636 
1637 #endif /* DEVELOPMENT || DEBUG */
1638