1/*
2 * Copyright (c) 2006-2021 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 <sys/param.h>
30#include <sys/kernel.h>
31#include <sys/proc_internal.h>
32#include <sys/systm.h>
33#include <sys/systm.h>
34#include <sys/mount_internal.h>
35#include <sys/fsctl.h>
36#include <sys/filedesc.h>
37#include <sys/vnode_internal.h>
38#include <sys/imageboot.h>
39#include <kern/assert.h>
40
41#include <sys/namei.h>
42#include <sys/fcntl.h>
43#include <sys/vnode.h>
44#include <sys/xattr.h>
45#include <sys/sysproto.h>
46#include <sys/csr.h>
47#include <miscfs/devfs/devfsdefs.h>
48#include <libkern/crypto/sha2.h>
49#include <libkern/crypto/rsa.h>
50#include <libkern/OSKextLibPrivate.h>
51#include <sys/ubc_internal.h>
52
53#if CONFIG_IMAGEBOOT_IMG4
54#include <libkern/img4/interface.h>
55#include <img4/firmware.h>
56#endif
57
58#include <kern/kalloc.h>
59#include <os/overflow.h>
60#include <vm/vm_kern.h>
61
62#include <pexpert/pexpert.h>
63#include <kern/chunklist.h>
64
65extern int (*mountroot)(void);
66extern char rootdevice[DEVMAXNAMESIZE];
67
68#define DEBUG_IMAGEBOOT 0
69
70#if DEBUG_IMAGEBOOT
71#define DBG_TRACE(...) printf("imageboot: " __VA_ARGS__)
72#else
73#define DBG_TRACE(...) do {} while(0)
74#endif
75
76#define AUTHDBG(fmt, args...) do { printf("%s: " fmt "\n", __func__, ##args); } while (0)
77#define AUTHPRNT(fmt, args...) do { printf("%s: " fmt "\n", __func__, ##args); } while (0)
78
79extern int di_root_image_ext(const char *path, char *devname, size_t devsz, dev_t *dev_p, bool removable);
80extern int di_root_image(const char *path, char *devname, size_t devsz, dev_t *dev_p);
81extern int di_root_ramfile_buf(void *buf, size_t bufsz, char *devname, size_t devsz, dev_t *dev_p);
82
83static boolean_t imageboot_setup_new(imageboot_type_t type);
84
85void *ubc_getobject_from_filename(const char *filename, struct vnode **vpp, off_t *file_size);
86
87extern lck_rw_t rootvnode_rw_lock;
88
89#define kIBFilePrefix "file://"
90
91__private_extern__ int
92imageboot_format_is_valid(const char *root_path)
93{
94 return strncmp(s1: root_path, kIBFilePrefix,
95 n: strlen(kIBFilePrefix)) == 0;
96}
97
98static void
99vnode_get_and_drop_always(vnode_t vp)
100{
101 vnode_getalways(vp);
102 vnode_rele(vp);
103 vnode_put(vp);
104}
105
106__private_extern__ bool
107imageboot_desired(void)
108{
109 bool do_imageboot = false;
110
111 char *root_path = NULL;
112 root_path = zalloc(view: ZV_NAMEI);
113 /*
114 * Check for first layer DMG rooting.
115 *
116 * Note that here we are principally concerned with whether or not we
117 * SHOULD try to imageboot, not whether or not we are going to be able to.
118 *
119 * If NONE of the boot-args are present, then assume that image-rooting
120 * is not requested.
121 *
122 * [!! Note parens guard the entire logically OR'd set of statements, below. It validates
123 * that NONE of the below-mentioned boot-args is present...!!]
124 */
125 if (!(PE_parse_boot_argn(arg_string: "rp0", arg_ptr: root_path, MAXPATHLEN) ||
126#if CONFIG_IMAGEBOOT_IMG4
127 PE_parse_boot_argn("arp0", root_path, MAXPATHLEN) ||
128#endif
129 PE_parse_boot_argn(arg_string: "rp", arg_ptr: root_path, MAXPATHLEN) ||
130 PE_parse_boot_argn(IMAGEBOOT_ROOT_ARG, arg_ptr: root_path, MAXPATHLEN) ||
131 PE_parse_boot_argn(IMAGEBOOT_AUTHROOT_ARG, arg_ptr: root_path, MAXPATHLEN))) {
132 /* explicitly set to false */
133 do_imageboot = false;
134 } else {
135 /* now sanity check the file-path format */
136 if (imageboot_format_is_valid(root_path)) {
137 DBG_TRACE("%s: Found %s\n", __FUNCTION__, root_path);
138 /* root_path looks good and we have one of the aforementioned bootargs */
139 do_imageboot = true;
140 } else {
141 /* explicitly set to false */
142 do_imageboot = false;
143 }
144 }
145
146 zfree(ZV_NAMEI, root_path);
147 return do_imageboot;
148}
149
150__private_extern__ imageboot_type_t
151imageboot_needed(void)
152{
153 imageboot_type_t result = IMAGEBOOT_NONE;
154 char *root_path = NULL;
155
156 DBG_TRACE("%s: checking for presence of root path\n", __FUNCTION__);
157
158 if (!imageboot_desired()) {
159 goto out;
160 }
161
162 root_path = zalloc(view: ZV_NAMEI);
163 result = IMAGEBOOT_DMG;
164
165 /* Check for second layer */
166 if (!(PE_parse_boot_argn(arg_string: "rp1", arg_ptr: root_path, MAXPATHLEN) ||
167 PE_parse_boot_argn(IMAGEBOOT_CONTAINER_ARG, arg_ptr: root_path, MAXPATHLEN))) {
168 goto out;
169 }
170
171 /* Sanity-check second layer */
172 if (imageboot_format_is_valid(root_path)) {
173 DBG_TRACE("%s: Found %s\n", __FUNCTION__, root_path);
174 } else {
175 panic("%s: Invalid URL scheme for %s",
176 __FUNCTION__, root_path);
177 }
178
179out:
180 if (root_path != NULL) {
181 zfree(ZV_NAMEI, root_path);
182 }
183 return result;
184}
185
186extern bool IOBaseSystemARVRootHashAvailable(void);
187
188
189/*
190 * Mounts new filesystem based on image path, and pivots it to the root.
191 * The image to be mounted is located at image_path.
192 * It will be mounted at mount_path.
193 * The vfs_switch_root operation will be performed.
194 * After the pivot, the outgoing root filesystem (the filesystem at root when
195 * this function begins) will be at outgoing_root_path. If `skip_signature_check` is true,
196 * then ignore the chunklisted or authAPFS checks on this image
197 */
198__private_extern__ int
199imageboot_pivot_image(const char *image_path, imageboot_type_t type, const char *mount_path,
200 const char *outgoing_root_path, const bool rooted_dmg, const bool skip_signature_check)
201{
202 int error;
203 boolean_t authenticated_dmg_chunklist = false;
204 vnode_t mount_vp = NULLVP;
205 errno_t rootauth;
206
207
208 if (type != IMAGEBOOT_DMG) {
209 panic("not supported");
210 }
211
212 /*
213 * Check that the image file actually exists.
214 * We also need to find the mount it's on, to mark it as backing the
215 * root.
216 */
217 vnode_t imagevp = NULLVP;
218 error = vnode_lookup(path: image_path, flags: 0, vpp: &imagevp, ctx: vfs_context_kernel());
219 if (error) {
220 printf("%s: image file not found or couldn't be read: %d\n", __FUNCTION__, error);
221 /*
222 * bail out here to short-circuit out of panic logic below.
223 * Failure to find the pivot-image should not be a fatal condition (ENOENT)
224 * since it may result in natural consequences (ergo, cannot unlock filevault prompt).
225 */
226 return error;
227 }
228
229 /*
230 * load the disk image and obtain its device.
231 * di_root_image's name and the names of its arguments suggest it has
232 * to be mounted at the root, but that's not actually needed.
233 * We just need to obtain the device info.
234 */
235
236 dev_t dev;
237 char devname[DEVMAXNAMESIZE];
238
239 error = di_root_image_ext(path: image_path, devname, DEVMAXNAMESIZE, dev_p: &dev, true);
240 if (error) {
241 panic("%s: di_root_image failed: %d", __FUNCTION__, error);
242 }
243
244 printf("%s: attached disk image %s as %s\n", __FUNCTION__, image_path, devname);
245
246
247#if CONFIG_IMAGEBOOT_CHUNKLIST
248 if ((rooted_dmg == false) && !IOBaseSystemARVRootHashAvailable()) {
249 error = authenticate_root_with_chunklist(image_path, NULL);
250 if (error == 0) {
251 printf("authenticated root-dmg via chunklist...\n");
252 authenticated_dmg_chunklist = true;
253 } else {
254 /* root hash was not available, and image is NOT chunklisted? */
255 printf("failed to chunklist-authenticate root-dmg @ %s\n", image_path);
256 }
257 }
258#endif
259
260 char fulldevname[DEVMAXNAMESIZE + 5]; // "/dev/"
261 strlcpy(dst: fulldevname, src: "/dev/", n: sizeof(fulldevname));
262 strlcat(dst: fulldevname, src: devname, n: sizeof(fulldevname));
263
264 /*
265 * mount expects another layer of indirection (because it expects to
266 * be getting a user_addr_t of a char *.
267 * Make a pointer-to-pointer on our stack. It won't use this
268 * address after it returns so this should be safe.
269 */
270 char *fulldevnamep = &(fulldevname[0]);
271 char **fulldevnamepp = &fulldevnamep;
272
273#define PIVOTMNT "/System/Volumes/BaseSystem"
274
275
276 /* Attempt to mount as HFS; if it fails, then try as APFS */
277 printf("%s: attempting to mount as hfs...\n", __FUNCTION__);
278 error = kernel_mount("hfs", NULLVP, NULLVP, PIVOTMNT, fulldevnamepp, 0, (MNT_RDONLY | MNT_DONTBROWSE), (KERNEL_MOUNT_NOAUTH | KERNEL_MOUNT_BASESYSTEMROOT), vfs_context_kernel());
279 if (error) {
280 printf("mount failed: %d\n", error);
281 printf("%s: attempting to mount as apfs...\n", __FUNCTION__);
282 error = kernel_mount("apfs", NULLVP, NULLVP, PIVOTMNT, fulldevnamepp, 0, (MNT_RDONLY | MNT_DONTBROWSE), (KERNEL_MOUNT_NOAUTH | KERNEL_MOUNT_BASESYSTEMROOT), vfs_context_kernel());
283 }
284
285 /* If we didn't mount as either HFS or APFS, then bail out */
286 if (error) {
287 /*
288 * Note that for this particular failure case (failure to mount), the disk image
289 * being attached may have failed to quiesce within the alloted time out (20-30 sec).
290 * For example, it may be still probing, or APFS container enumeration may have not
291 * completed. If so, then we may have fallen into this particular error case. However,
292 * failure to complete matching should be an exceptional case as 30 sec. is quite a
293 * long time to wait for matching to complete (which would have occurred in
294 * di_root_image_ext).
295 */
296#if defined(__arm64__) && XNU_TARGET_OS_OSX
297 panic("%s: failed to mount pivot image(%d)!", __FUNCTION__, error);
298#endif
299 printf("%s: failed to mount pivot image(%d) !", __FUNCTION__, error);
300 goto done;
301 }
302
303 /* otherwise, if the mount succeeded, then assert that the DMG is authenticated (either chunklist or authapfs) */
304 error = vnode_lookup(PIVOTMNT, flags: 0, vpp: &mount_vp, ctx: vfs_context_kernel());
305 if (error) {
306#if defined(__arm64__) && XNU_TARGET_OS_OSX
307 panic("%s: failed to lookup pivot root (%d) !", __FUNCTION__, error);
308#endif
309 printf("%s: failed to lookup pivot root (%d)!", __FUNCTION__, error);
310 goto done;
311 }
312
313 /* the 0x1 implies base system */
314 rootauth = VNOP_IOCTL(vp: mount_vp, FSIOC_KERNEL_ROOTAUTH, data: (caddr_t)0x1, fflag: 0, ctx: vfs_context_kernel());
315 if (rootauth) {
316 printf("BS-DMG failed to authenticate intra-FS \n");
317 /*
318 * If we are using a custom rooted DMG, or if we have already authenticated
319 * the DMG via chunklist, then it is permissible to use.
320 * Or, if CSR_ALLOW_ANY_RECOVERY_OS is set on Development or Debug build variant.
321 */
322 if (rooted_dmg || authenticated_dmg_chunklist || skip_signature_check) {
323 rootauth = 0;
324 }
325 error = rootauth;
326 }
327 vnode_put(vp: mount_vp);
328 mount_vp = NULLVP;
329
330 if (error) {
331 /*
332 * Failure here exclusively means that the mount failed to authenticate.
333 * This means that the disk image either was not sealed (authapfs), or it was
334 * not hosted on a chunklisted DMG. Both scenarios may be fatal depending
335 * on the platform.
336 */
337#if defined(__arm64__) && XNU_TARGET_OS_OSX
338 panic("%s: could not authenticate the pivot image: %d. giving up.", __FUNCTION__, error);
339#endif
340 printf("%s: could not authenticate the pivot image: %d. giving up.\n", __FUNCTION__, error);
341 goto done;
342 }
343
344 if (rootvnode) {
345 mount_t root_mp = vnode_mount(vp: rootvnode);
346 if (root_mp && (root_mp->mnt_kern_flag & MNTK_SSD)) {
347 rootvp_is_ssd = true;
348 }
349 }
350 /*
351 * pivot the incoming and outgoing filesystems
352 */
353 error = vfs_switch_root(mount_path, outgoing_root_path, 0);
354 if (error) {
355 panic("%s: vfs_switch_root failed: %d", __FUNCTION__, error);
356 }
357
358 /*
359 * Mark the filesystem containing the image as backing root, so it
360 * won't be unmountable.
361 *
362 * vfs_switch_root() clears this flag, so we have to set it after
363 * the pivot call.
364 * If the system later pivots out of the image, vfs_switch_root
365 * will clear it again, so the backing filesystem can be unmounted.
366 */
367 mount_t imagemp = imagevp->v_mount;
368 lck_rw_lock_exclusive(lck: &imagemp->mnt_rwlock);
369 imagemp->mnt_kern_flag |= MNTK_BACKS_ROOT;
370 lck_rw_done(lck: &imagemp->mnt_rwlock);
371
372 error = 0;
373
374 /*
375 * Note that we do NOT change kern.bootuuid here -
376 * imageboot_mount_image() does, but imageboot_pivot_image() doesn't.
377 * imageboot_mount_image() is used when the root volume uuid was
378 * "always supposed to be" the one inside the dmg. imageboot_pivot_
379 * image() is used when the true root volume just needs to be
380 * obscured for a moment by the dmg.
381 */
382
383done:
384 if (imagevp != NULLVP) {
385 vnode_put(vp: imagevp);
386 }
387 return error;
388}
389
390/* kern_sysctl.c */
391extern uuid_string_t fake_bootuuid;
392
393static void
394set_fake_bootuuid(mount_t mp)
395{
396 struct vfs_attr va;
397 VFSATTR_INIT(&va);
398 VFSATTR_WANTED(&va, f_uuid);
399
400 if (vfs_getattr(mp, vfa: &va, ctx: vfs_context_current()) != 0) {
401 return;
402 }
403
404 if (!VFSATTR_IS_SUPPORTED(&va, f_uuid)) {
405 return;
406 }
407
408 uuid_unparse(uu: va.f_uuid, out: fake_bootuuid);
409}
410
411/*
412 * Swaps in new root filesystem based on image path.
413 * Current root filesystem is removed from mount list and
414 * tagged MNTK_BACKS_ROOT, MNT_ROOTFS is cleared on it, and
415 * "rootvnode" is reset. Root vnode of currentroot filesystem
416 * is returned with usecount (no iocount).
417 * kern.bootuuid is arranged to return the UUID of the mounted image. (If
418 * we did nothing here, it would be the UUID of the image source volume.)
419 */
420__private_extern__ int
421imageboot_mount_image(const char *root_path, int height, imageboot_type_t type)
422{
423 dev_t dev;
424 int error;
425 /*
426 * Need to stash this here since we may do a kernel_mount() on /, which will
427 * automatically update the rootvnode global. Note that vfs_mountroot() does
428 * not update that global, which is a bit weird.
429 */
430 vnode_t old_rootvnode = rootvnode;
431 vnode_t newdp;
432 mount_t new_rootfs;
433 boolean_t update_rootvnode = FALSE;
434
435 if (type == IMAGEBOOT_DMG) {
436 error = di_root_image(path: root_path, devname: rootdevice, DEVMAXNAMESIZE, dev_p: &dev);
437 if (error) {
438 panic("%s: di_root_image failed: %d", __FUNCTION__, error);
439 }
440
441 rootdev = dev;
442 mountroot = NULL;
443 printf("%s: root device 0x%x\n", __FUNCTION__, rootdev);
444 error = vfs_mountroot();
445 if (error != 0) {
446 panic("vfs_mountroot() failed.");
447 }
448
449 update_rootvnode = TRUE;
450 } else {
451 panic("invalid imageboot type: %d", type);
452 }
453
454 /*
455 * Get the vnode for '/'.
456 * Set fdp->fd_fd.fd_cdir to reference it.
457 */
458 if (VFS_ROOT(TAILQ_LAST(&mountlist, mntlist), &newdp, vfs_context_kernel())) {
459 panic("%s: cannot find root vnode", __FUNCTION__);
460 }
461 DBG_TRACE("%s: old root fsname: %s\n", __FUNCTION__, old_rootvnode->v_mount->mnt_vtable->vfc_name);
462
463 if (old_rootvnode != NULL) {
464 /* remember the old rootvnode, but remove it from mountlist */
465 mount_t old_rootfs = old_rootvnode->v_mount;
466
467 mount_list_remove(old_rootfs);
468 mount_lock(old_rootfs);
469 old_rootfs->mnt_kern_flag |= MNTK_BACKS_ROOT;
470 old_rootfs->mnt_flag &= ~MNT_ROOTFS;
471 mount_unlock(old_rootfs);
472 }
473
474 vnode_ref(vp: newdp);
475 vnode_put(vp: newdp);
476
477 lck_rw_lock_exclusive(lck: &rootvnode_rw_lock);
478 /* switch to the new rootvnode */
479 if (update_rootvnode) {
480 rootvnode = newdp;
481 set_fake_bootuuid(rootvnode->v_mount);
482 }
483
484 new_rootfs = rootvnode->v_mount;
485 mount_lock(new_rootfs);
486 new_rootfs->mnt_flag |= MNT_ROOTFS;
487 mount_unlock(new_rootfs);
488
489 kernproc->p_fd.fd_cdir = newdp;
490 lck_rw_unlock_exclusive(lck: &rootvnode_rw_lock);
491
492 DBG_TRACE("%s: root switched\n", __FUNCTION__);
493
494 if (old_rootvnode != NULL) {
495#ifdef CONFIG_IMGSRC_ACCESS
496 if (height >= 0) {
497 imgsrc_rootvnodes[height] = old_rootvnode;
498 } else {
499 vnode_get_and_drop_always(vp: old_rootvnode);
500 }
501#else
502#pragma unused(height)
503 vnode_get_and_drop_always(old_rootvnode);
504#endif /* CONFIG_IMGSRC_ACCESS */
505 }
506 return 0;
507}
508
509/*
510 * Return a memory object for given file path.
511 * Also returns a vnode reference for the given file path.
512 */
513void *
514ubc_getobject_from_filename(const char *filename, struct vnode **vpp, off_t *file_size)
515{
516 int err = 0;
517 struct nameidata ndp = {};
518 struct vnode *vp = NULL;
519 off_t fsize = 0;
520 vfs_context_t ctx = vfs_context_kernel();
521 void *control = NULL;
522
523 NDINIT(&ndp, LOOKUP, OP_OPEN, LOCKLEAF, UIO_SYSSPACE, CAST_USER_ADDR_T(filename), ctx);
524 if ((err = namei(ndp: &ndp)) != 0) {
525 goto errorout;
526 }
527 nameidone(&ndp);
528 vp = ndp.ni_vp;
529
530 if ((err = vnode_size(vp, &fsize, ctx)) != 0) {
531 goto errorout;
532 }
533
534 if (fsize < 0) {
535 goto errorout;
536 }
537
538 control = ubc_getobject(vp, UBC_FLAGS_NONE);
539 if (control == NULL) {
540 goto errorout;
541 }
542
543 *file_size = fsize;
544 *vpp = vp;
545 vp = NULL;
546
547errorout:
548 if (vp) {
549 vnode_put(vp);
550 }
551 return control;
552}
553
554static int
555imageboot_read_file_internal(const char *path, const off_t offset, const bool pageable, void **bufp, size_t *bufszp, off_t *fsizep)
556{
557 int err = 0;
558 struct nameidata ndp = {};
559 struct vnode *vp = NULL;
560 struct vnode *rsrc_vp = NULL;
561 char *readbuf = NULL;
562 off_t readsize = 0;
563 off_t readoff = 0;
564 off_t fsize = 0;
565 size_t maxsize = 0;
566 char *buf = NULL;
567 bool doclose = false;
568
569 vfs_context_t ctx = vfs_context_kernel();
570 proc_t p = vfs_context_proc(ctx);
571 kauth_cred_t kerncred = vfs_context_ucred(ctx);
572
573 NDINIT(&ndp, LOOKUP, OP_OPEN, LOCKLEAF | FOLLOW, UIO_SYSSPACE, CAST_USER_ADDR_T(path), ctx);
574 if ((err = namei(ndp: &ndp)) != 0) {
575 AUTHPRNT("namei failed (%s) - %d", path, err);
576 goto out;
577 }
578 nameidone(&ndp);
579 vp = ndp.ni_vp;
580
581 if ((err = vnode_size(vp, &fsize, ctx)) != 0) {
582 AUTHPRNT("failed to get vnode size of %s - %d", path, err);
583 goto out;
584 }
585 if (fsize < 0) {
586 panic("negative file size");
587 }
588 if (offset < 0) {
589 AUTHPRNT("negative file offset");
590 err = EINVAL;
591 goto out;
592 }
593
594 if (fsizep) {
595 *fsizep = fsize;
596 }
597
598 if ((err = VNOP_OPEN(vp, FREAD, ctx)) != 0) {
599 AUTHPRNT("failed to open %s - %d", path, err);
600 goto out;
601 }
602 doclose = true;
603
604 /* cap fsize to the amount that remains after offset */
605 if (os_sub_overflow(fsize, offset, &fsize)) {
606 fsize = 0;
607 } else if (fsize < 0) {
608 fsize = 0;
609 }
610
611 /* if bufsz is non-zero, cap the read at bufsz bytes */
612 maxsize = *bufszp;
613 if (maxsize && (maxsize < (size_t)fsize)) {
614 fsize = maxsize;
615 }
616
617 /* if fsize is larger than the specified limit (presently 2.5GB) or a NVRAM-configured limit, fail */
618 maxsize = IMAGEBOOT_MAX_FILESIZE;
619 PE_parse_boot_argn(arg_string: "rootdmg-maxsize", arg_ptr: &maxsize, max_arg: sizeof(maxsize));
620 if (maxsize && (maxsize < (size_t)fsize)) {
621 AUTHPRNT("file is too large (%lld > %lld)", (long long) fsize, (long long) maxsize);
622 err = ENOMEM;
623 goto out;
624 }
625
626 if (pageable) {
627 vm_offset_t addr = 0;
628 if (kmem_alloc(map: kernel_map, addrp: &addr, size: (vm_size_t)fsize,
629 flags: KMA_PAGEABLE | KMA_DATA, VM_KERN_MEMORY_FILE) == KERN_SUCCESS) {
630 buf = (char *)addr;
631 } else {
632 buf = NULL;
633 }
634 } else {
635 //limit kalloc data calls to only 2GB.
636 if (fsize > IMAGEBOOT_MAX_KALLOCSIZE) {
637 AUTHPRNT("file is too large for non-pageable (%lld)", (long long) fsize);
638 err = ENOMEM;
639 goto out;
640 }
641 buf = (char *)kalloc_data((vm_size_t)fsize, Z_WAITOK);
642 }
643 if (buf == NULL) {
644 err = ENOMEM;
645 goto out;
646 }
647
648#if NAMEDSTREAMS
649 /* find resource fork so we can evict cached decmpfs data */
650 if (VNOP_GETNAMEDSTREAM(vp, &rsrc_vp, XATTR_RESOURCEFORK_NAME, NS_OPEN, /*flags*/ 0, ctx) == 0) {
651 vnode_ref(vp: rsrc_vp);
652 vnode_put(vp: rsrc_vp);
653 AUTHDBG("Found resource fork for %s", path);
654 }
655#endif
656
657 /* read data in chunks to handle (fsize > INT_MAX) */
658 readbuf = buf;
659 readsize = fsize;
660 readoff = offset;
661 while (readsize > 0) {
662 const off_t chunksize_max = 16 * 1024 * 1024; /* 16 MiB */
663 const off_t chunksize = MIN(readsize, chunksize_max);
664
665 /* read next chunk, pass IO_NOCACHE to clarify our intent (even if ignored) */
666 if ((err = vn_rdwr(rw: UIO_READ, vp, base: (caddr_t)readbuf, len: (int)chunksize, offset: readoff, segflg: UIO_SYSSPACE, IO_NODELOCKED | IO_NOCACHE | IO_RAOFF, cred: kerncred, /*resid*/ NULL, p)) != 0) {
667 AUTHPRNT("Cannot read %lld bytes at offset %lld from %s - %d", (long long)chunksize, (long long)readoff, path, err);
668 goto out;
669 }
670
671 /* evict cached pages so they don't accumulate during early boot */
672 ubc_msync(vp, readoff, readoff + chunksize, NULL, UBC_INVALIDATE | UBC_PUSHALL);
673
674 /* evict potentially-cached decmpfs data if we have a resource fork */
675 if (rsrc_vp != NULL) {
676 if (vnode_getwithref(vp: rsrc_vp) == 0) {
677 ubc_msync(rsrc_vp, 0, ubc_getsize(rsrc_vp), NULL, UBC_INVALIDATE | UBC_PUSHALL);
678 vnode_put(vp: rsrc_vp);
679 }
680 }
681
682 readbuf = &readbuf[chunksize];
683 readsize -= chunksize;
684 readoff += chunksize;
685 }
686
687out:
688 if (doclose) {
689 VNOP_CLOSE(vp, FREAD, ctx);
690 }
691 if (rsrc_vp) {
692 vnode_rele(vp: rsrc_vp);
693 rsrc_vp = NULL;
694 }
695 if (vp) {
696 vnode_put(vp);
697 vp = NULL;
698 }
699
700 if (err) {
701 if (buf == NULL) {
702 /* nothing to free */
703 } else if (pageable) {
704 kmem_free(map: kernel_map, addr: (vm_offset_t)buf, size: (vm_size_t)fsize);
705 } else {
706 kfree_data(buf, (vm_size_t)fsize);
707 }
708 } else {
709 *bufp = buf;
710 *bufszp = (size_t)fsize;
711 }
712
713 return err;
714}
715
716int
717imageboot_read_file_pageable(const char *path, void **bufp, size_t *bufszp)
718{
719 return imageboot_read_file_internal(path, offset: 0, true, bufp, bufszp, NULL);
720}
721
722int
723imageboot_read_file_from_offset(const char *path, const off_t offset, void **bufp, size_t *bufszp)
724{
725 return imageboot_read_file_internal(path, offset, false, bufp, bufszp, NULL);
726}
727
728int
729imageboot_read_file(const char *path, void **bufp, size_t *bufszp, off_t *fsizep)
730{
731 return imageboot_read_file_internal(path, offset: 0, false, bufp, bufszp, fsizep);
732}
733
734#if CONFIG_IMAGEBOOT_IMG4 || CONFIG_IMAGEBOOT_CHUNKLIST
735vnode_t
736imgboot_get_image_file(const char *path, off_t *fsize, int *errp)
737{
738 struct nameidata ndp = {};
739 vnode_t vp = NULL;
740 vfs_context_t ctx = vfs_context_kernel();
741 int err;
742
743 NDINIT(&ndp, LOOKUP, OP_OPEN, LOCKLEAF, UIO_SYSSPACE, CAST_USER_ADDR_T(path), ctx);
744 if ((err = namei(&ndp)) != 0) {
745 AUTHPRNT("Cannot find %s - error %d", path, err);
746 } else {
747 nameidone(&ndp);
748 vp = ndp.ni_vp;
749
750 if (vp->v_type != VREG) {
751 err = EINVAL;
752 AUTHPRNT("%s it not a regular file", path);
753 } else if (fsize) {
754 if ((err = vnode_size(vp, fsize, ctx)) != 0) {
755 AUTHPRNT("Cannot get file size of %s - error %d", path, err);
756 }
757 }
758 }
759
760 if (err) {
761 if (vp) {
762 vnode_put(vp);
763 }
764 *errp = err;
765 vp = NULL;
766 }
767 return vp;
768}
769#endif /* CONFIG_IMAGEBOOT_CHUNKLIST || CONFIG_IMAGEBOOT_CHUNKLIST */
770
771#if CONFIG_IMAGEBOOT_IMG4
772
773#define APTICKET_NAME "apticket.der"
774
775static char *
776imgboot_get_apticket_path(const char *rootpath, size_t *sz)
777{
778 size_t plen = strlen(rootpath) + sizeof(APTICKET_NAME) + 1;
779 char *path = (char *)kalloc_data(plen, Z_WAITOK);
780
781 if (path) {
782 char *slash;
783
784 strlcpy(path, rootpath, plen);
785 slash = strrchr(path, '/');
786 if (slash == NULL) {
787 slash = path;
788 } else {
789 slash++;
790 }
791 strlcpy(slash, APTICKET_NAME, sizeof(APTICKET_NAME) + 1);
792 }
793
794 *sz = plen;
795 return path;
796}
797
798static int
799authenticate_root_with_img4(const char *rootpath)
800{
801 errno_t rv;
802 vnode_t vp = NULLVP;
803 size_t ticket_pathsz = 0;
804 char *ticket_path;
805 img4_buff_t tck = IMG4_BUFF_INIT;
806 img4_firmware_execution_context_t exec = {
807 .i4fex_version = IMG4_FIRMWARE_EXECUTION_CONTEXT_STRUCT_VERSION,
808 .i4fex_execute = NULL,
809 .i4fex_context = NULL,
810 };
811 img4_firmware_t fw = NULL;
812 img4_firmware_flags_t fw_flags = IMG4_FIRMWARE_FLAG_BARE |
813 IMG4_FIRMWARE_FLAG_SUBSEQUENT_STAGE;
814
815 DBG_TRACE("Check %s\n", rootpath);
816
817 ticket_path = imgboot_get_apticket_path(rootpath, &ticket_pathsz);
818 if (ticket_path == NULL) {
819 AUTHPRNT("Cannot construct ticket path - out of memory");
820 return ENOMEM;
821 }
822
823 rv = imageboot_read_file(ticket_path, (void **)&tck.i4b_bytes, &tck.i4b_len, NULL);
824 if (rv) {
825 AUTHPRNT("Cannot get a ticket from %s - %d\n", ticket_path, rv);
826 goto out_with_ticket_path;
827 }
828
829 DBG_TRACE("Got %lu bytes of manifest from %s\n", tck.i4b_len, ticket_path);
830
831 vp = imgboot_get_image_file(rootpath, NULL, &rv);
832 if (vp == NULL) {
833 /* Error message had been printed already */
834 rv = EIO;
835 goto out_with_ticket_bytes;
836 }
837
838 fw = img4_firmware_new_from_vnode_4xnu(IMG4_RUNTIME_DEFAULT, &exec, 'rosi',
839 vp, fw_flags);
840 if (!fw) {
841 AUTHPRNT("Could not allocate new firmware");
842 rv = ENOMEM;
843 goto out_with_ticket_bytes;
844 }
845
846 img4_firmware_attach_manifest(fw, &tck);
847 rv = img4_firmware_evaluate(fw, img4_chip_select_personalized_ap(), NULL);
848
849out_with_ticket_bytes:
850 kfree_data(tck.i4b_bytes, tck.i4b_len);
851out_with_ticket_path:
852 kfree_data(ticket_path, ticket_pathsz);
853
854 img4_firmware_destroy(&fw);
855
856 if (vp) {
857 vnode_put(vp);
858 }
859 return rv;
860}
861#endif /* CONFIG_IMAGEBOOT_IMG4 */
862
863
864/*
865 * Attach the image at 'path' as a ramdisk and mount it as our new rootfs.
866 * All existing mounts are first umounted.
867 */
868static int
869imageboot_mount_ramdisk(const char *path)
870{
871 int err = 0;
872 size_t bufsz = 0;
873 void *buf = NULL;
874 dev_t dev;
875 vnode_t newdp;
876 vnode_t tvp;
877 mount_t new_rootfs;
878
879 /* Read our target image from disk */
880 err = imageboot_read_file_pageable(path, bufp: &buf, bufszp: &bufsz);
881 if (err) {
882 printf("%s: failed: imageboot_read_file_pageable() = %d\n", __func__, err);
883 goto out;
884 }
885 DBG_TRACE("%s: read '%s' sz = %lu\n", __func__, path, bufsz);
886
887#if CONFIG_IMGSRC_ACCESS
888 /* Re-add all root mounts to the mount list in the correct order... */
889 mount_list_remove(rootvnode->v_mount);
890 for (int i = 0; i < MAX_IMAGEBOOT_NESTING; i++) {
891 struct vnode *vn = imgsrc_rootvnodes[i];
892 if (vn) {
893 vnode_getalways(vn);
894 imgsrc_rootvnodes[i] = NULLVP;
895
896 mount_t mnt = vn->v_mount;
897 mount_lock(mnt);
898 mnt->mnt_flag |= MNT_ROOTFS;
899 mount_list_add(mnt);
900 mount_unlock(mnt);
901
902 vnode_rele(vp: vn);
903 vnode_put(vp: vn);
904 }
905 }
906 mount_list_add(rootvnode->v_mount);
907#endif
908
909 /* ... and unmount everything */
910 vfs_unmountall(FALSE);
911
912 lck_rw_lock_exclusive(lck: &rootvnode_rw_lock);
913 kernproc->p_fd.fd_cdir = NULL;
914 tvp = rootvnode;
915 rootvnode = NULL;
916 rootvp = NULLVP;
917 rootdev = NODEV;
918 lck_rw_unlock_exclusive(lck: &rootvnode_rw_lock);
919 vnode_get_and_drop_always(vp: tvp);
920
921 /* Attach the ramfs image ... */
922 err = di_root_ramfile_buf(buf, bufsz, devname: rootdevice, DEVMAXNAMESIZE, dev_p: &dev);
923 if (err) {
924 printf("%s: failed: di_root_ramfile_buf() = %d\n", __func__, err);
925 goto out;
926 }
927
928 /* ... and mount it */
929 rootdev = dev;
930 mountroot = NULL;
931 err = vfs_mountroot();
932 if (err) {
933 printf("%s: failed: vfs_mountroot() = %d\n", __func__, err);
934 goto out;
935 }
936
937 /* Switch to new root vnode */
938 if (VFS_ROOT(TAILQ_LAST(&mountlist, mntlist), &newdp, vfs_context_kernel())) {
939 panic("%s: cannot find root vnode", __func__);
940 }
941 vnode_ref(vp: newdp);
942
943 lck_rw_lock_exclusive(lck: &rootvnode_rw_lock);
944 rootvnode = newdp;
945 rootvnode->v_flag |= VROOT;
946 new_rootfs = rootvnode->v_mount;
947 mount_lock(new_rootfs);
948 new_rootfs->mnt_flag |= MNT_ROOTFS;
949 mount_unlock(new_rootfs);
950
951 set_fake_bootuuid(new_rootfs);
952
953 kernproc->p_fd.fd_cdir = newdp;
954 lck_rw_unlock_exclusive(lck: &rootvnode_rw_lock);
955
956 vnode_put(vp: newdp);
957
958 DBG_TRACE("%s: root switched\n", __func__);
959
960out:
961 if (err && (buf != NULL)) {
962 kmem_free(map: kernel_map, addr: (vm_offset_t)buf, size: (vm_size_t)bufsz);
963 }
964 return err;
965}
966
967/*
968 * If the path is in <file://> URL format then we allocate memory and decode it,
969 * otherwise return the same pointer.
970 *
971 * Caller is expected to check if the pointers are different.
972 */
973static char *
974url_to_path(char *url_path, size_t *sz)
975{
976 char *path = url_path;
977 size_t len = strlen(kIBFilePrefix);
978
979 if (strncmp(kIBFilePrefix, s2: url_path, n: len) == 0) {
980 /* its a URL - remove the file:// prefix and percent-decode */
981 url_path += len;
982
983 len = strlen(s: url_path);
984 if (len) {
985 /* Make a copy of the path to URL-decode */
986 path = (char *)kalloc_data(len + 1, Z_WAITOK);
987 if (path == NULL) {
988 panic("imageboot path allocation failed - cannot allocate %d bytes", (int)len);
989 }
990
991 strlcpy(dst: path, src: url_path, n: len + 1);
992 *sz = len + 1;
993 url_decode(str: path);
994 } else {
995 panic("Bogus imageboot path URL - missing path");
996 }
997
998 DBG_TRACE("%s: root image URL <%s> becomes %s\n", __func__, url_path, path);
999 }
1000
1001 return path;
1002}
1003
1004static boolean_t
1005imageboot_setup_new(imageboot_type_t type)
1006{
1007 int error;
1008 char *root_path = NULL;
1009 int height = 0;
1010 boolean_t done = FALSE;
1011 boolean_t auth_root = TRUE;
1012 boolean_t ramdisk_root = FALSE;
1013
1014 root_path = zalloc(view: ZV_NAMEI);
1015 assert(root_path != NULL);
1016
1017 unsigned imgboot_arg;
1018 if (PE_parse_boot_argn(arg_string: "-rootdmg-ramdisk", arg_ptr: &imgboot_arg, max_arg: sizeof(imgboot_arg))) {
1019 ramdisk_root = TRUE;
1020 }
1021
1022 if (PE_parse_boot_argn(IMAGEBOOT_CONTAINER_ARG, arg_ptr: root_path, MAXPATHLEN) == TRUE) {
1023 printf("%s: container image url is %s\n", __FUNCTION__, root_path);
1024 error = imageboot_mount_image(root_path, height, type);
1025 if (error != 0) {
1026 panic("Failed to mount container image.");
1027 }
1028
1029 height++;
1030 }
1031
1032 if (PE_parse_boot_argn(IMAGEBOOT_AUTHROOT_ARG, arg_ptr: root_path, MAXPATHLEN) == FALSE &&
1033 PE_parse_boot_argn(IMAGEBOOT_ROOT_ARG, arg_ptr: root_path, MAXPATHLEN) == FALSE) {
1034 if (height > 0) {
1035 panic("%s specified without %s or %s?", IMAGEBOOT_CONTAINER_ARG, IMAGEBOOT_AUTHROOT_ARG, IMAGEBOOT_ROOT_ARG);
1036 }
1037 goto out;
1038 }
1039
1040 printf("%s: root image URL is '%s'\n", __func__, root_path);
1041
1042 /* Make a copy of the path to URL-decode */
1043 size_t pathsz;
1044 char *path = url_to_path(url_path: root_path, sz: &pathsz);
1045 assert(path);
1046
1047#if CONFIG_IMAGEBOOT_CHUNKLIST
1048 if (auth_root) {
1049 /*
1050 * This updates auth_root to reflect whether chunklist was
1051 * actually enforced. In effect, this clears auth_root if
1052 * CSR_ALLOW_ANY_RECOVERY_OS allowed an invalid image.
1053 */
1054 AUTHDBG("authenticating root image at %s", path);
1055 error = authenticate_root_with_chunklist(path, &auth_root);
1056 if (error) {
1057 panic("root image authentication failed (err = %d)", error);
1058 }
1059 AUTHDBG("successfully authenticated %s", path);
1060 }
1061#endif
1062
1063 if (ramdisk_root) {
1064 error = imageboot_mount_ramdisk(path);
1065 } else {
1066 error = imageboot_mount_image(root_path, height, type);
1067 }
1068
1069 if (path != root_path) {
1070 kfree_data(path, pathsz);
1071 }
1072
1073 if (error) {
1074 panic("Failed to mount root image (err=%d, auth=%d, ramdisk=%d)",
1075 error, auth_root, ramdisk_root);
1076 }
1077
1078#if CONFIG_IMAGEBOOT_CHUNKLIST
1079 if (auth_root) {
1080 /* check that the image version matches the running kernel */
1081 AUTHDBG("checking root image version");
1082 error = authenticate_root_version_check();
1083 if (error) {
1084 panic("root image version check failed");
1085 } else {
1086 AUTHDBG("root image version matches kernel");
1087 }
1088 }
1089#endif
1090
1091 done = TRUE;
1092
1093out:
1094 zfree(ZV_NAMEI, root_path);
1095 return done;
1096}
1097
1098__private_extern__ void
1099imageboot_setup(imageboot_type_t type)
1100{
1101 int error = 0;
1102 char *root_path = NULL;
1103
1104 DBG_TRACE("%s: entry\n", __FUNCTION__);
1105
1106 if (rootvnode == NULL) {
1107 panic("imageboot_setup: rootvnode is NULL.");
1108 }
1109
1110 /*
1111 * New boot-arg scheme:
1112 * root-dmg : the dmg that will be the root filesystem, authenticated by default.
1113 * auth-root-dmg : same as root-dmg.
1114 * container-dmg : an optional dmg that contains the root-dmg.
1115 * locker : the locker that will be the root filesystem -- mutually
1116 * exclusive with any other boot-arg.
1117 */
1118 if (imageboot_setup_new(type)) {
1119 return;
1120 }
1121
1122 root_path = zalloc(view: ZV_NAMEI);
1123 assert(root_path != NULL);
1124
1125 /*
1126 * Look for outermost disk image to root from. If we're doing a nested boot,
1127 * there's some sense in which the outer image never needs to be the root filesystem,
1128 * but it does need very similar treatment: it must not be unmounted, needs a fake
1129 * device vnode created for it, and should not show up in getfsstat() until exposed
1130 * with MNT_IMGSRC. We just make it the temporary root.
1131 */
1132#if CONFIG_IMAGEBOOT_IMG4
1133 if (PE_parse_boot_argn("arp0", root_path, MAXPATHLEN)) {
1134 size_t pathsz;
1135 char *path = url_to_path(root_path, &pathsz);
1136
1137 assert(path);
1138
1139 if (authenticate_root_with_img4(path)) {
1140 panic("Root image %s does not match the manifest", root_path);
1141 }
1142 if (path != root_path) {
1143 kfree_data(path, pathsz);
1144 }
1145 } else
1146#endif /* CONFIG_IMAGEBOOT_IMG4 */
1147 if ((PE_parse_boot_argn(arg_string: "rp", arg_ptr: root_path, MAXPATHLEN) == FALSE) &&
1148 (PE_parse_boot_argn(arg_string: "rp0", arg_ptr: root_path, MAXPATHLEN) == FALSE)) {
1149 panic("%s: no valid path to image.", __FUNCTION__);
1150 }
1151
1152 DBG_TRACE("%s: root image url is %s\n", __FUNCTION__, root_path);
1153
1154 error = imageboot_mount_image(root_path, height: 0, type);
1155 if (error) {
1156 panic("Failed on first stage of imageboot.");
1157 }
1158
1159 /*
1160 * See if we are rooting from a nested image
1161 */
1162 if (PE_parse_boot_argn(arg_string: "rp1", arg_ptr: root_path, MAXPATHLEN) == FALSE) {
1163 goto done;
1164 }
1165
1166 printf("%s: second level root image url is %s\n", __FUNCTION__, root_path);
1167
1168 /*
1169 * If we fail to set up second image, it's not a given that we
1170 * can safely root off the first.
1171 */
1172 error = imageboot_mount_image(root_path, height: 1, type);
1173 if (error) {
1174 panic("Failed on second stage of imageboot.");
1175 }
1176
1177done:
1178 zfree(ZV_NAMEI, root_path);
1179
1180 DBG_TRACE("%s: exit\n", __FUNCTION__);
1181
1182 return;
1183}
1184