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