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