]>
Commit | Line | Data |
---|---|---|
1 | /* | |
2 | * Copyright (c) 2006 Apple Computer, 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/filedesc.h> | |
36 | #include <sys/vnode_internal.h> | |
37 | #include <sys/imageboot.h> | |
38 | #include <kern/assert.h> | |
39 | ||
40 | #include <sys/namei.h> | |
41 | #include <sys/fcntl.h> | |
42 | #include <sys/vnode.h> | |
43 | #include <sys/sysproto.h> | |
44 | #include <sys/csr.h> | |
45 | #include <miscfs/devfs/devfsdefs.h> | |
46 | #include <libkern/crypto/sha2.h> | |
47 | #include <libkern/crypto/rsa.h> | |
48 | #include <libkern/OSKextLibPrivate.h> | |
49 | ||
50 | #if CONFIG_IMAGEBOOT_IMG4 | |
51 | #include <libkern/img4/interface.h> | |
52 | #include <img4/img4.h> | |
53 | #endif | |
54 | ||
55 | #include <kern/kalloc.h> | |
56 | ||
57 | #include <pexpert/pexpert.h> | |
58 | #include <kern/chunklist.h> | |
59 | ||
60 | extern struct filedesc filedesc0; | |
61 | ||
62 | extern int (*mountroot)(void); | |
63 | extern char rootdevice[DEVMAXNAMESIZE]; | |
64 | ||
65 | #if CONFIG_LOCKERBOOT | |
66 | typedef struct _locker_mount_args { | |
67 | char lmnt_path[PATH_MAX]; | |
68 | uint16_t lmnt_preferred_hash; | |
69 | } locker_mount_args_t; | |
70 | #endif | |
71 | ||
72 | #define DEBUG_IMAGEBOOT 0 | |
73 | ||
74 | #if DEBUG_IMAGEBOOT | |
75 | #define DBG_TRACE(...) printf("imageboot: " __VA_ARGS__) | |
76 | #else | |
77 | #define DBG_TRACE(...) do {} while(0) | |
78 | #endif | |
79 | ||
80 | #define AUTHDBG(fmt, args...) do { printf("%s: " fmt "\n", __func__, ##args); } while (0) | |
81 | #define AUTHPRNT(fmt, args...) do { printf("%s: " fmt "\n", __func__, ##args); } while (0) | |
82 | #define kfree_safe(x) do { if ((x)) { kfree_addr((x)); (x) = NULL; } } while (0) | |
83 | ||
84 | extern int di_root_image(const char *path, char *devname, size_t devsz, dev_t *dev_p); | |
85 | extern int di_root_ramfile_buf(void *buf, size_t bufsz, char *devname, size_t devsz, dev_t *dev_p); | |
86 | ||
87 | static boolean_t imageboot_setup_new(imageboot_type_t type); | |
88 | ||
89 | vnode_t imgboot_get_image_file(const char *path, off_t *fsize, int *errp); /* may be required by chunklist.c */ | |
90 | int read_file(const char *path, void **bufp, size_t *bufszp); /* may be required by chunklist.c */ | |
91 | ||
92 | #define kIBFilePrefix "file://" | |
93 | ||
94 | __private_extern__ int | |
95 | imageboot_format_is_valid(const char *root_path) | |
96 | { | |
97 | return strncmp(root_path, kIBFilePrefix, | |
98 | strlen(kIBFilePrefix)) == 0; | |
99 | } | |
100 | ||
101 | static void | |
102 | vnode_get_and_drop_always(vnode_t vp) | |
103 | { | |
104 | vnode_getalways(vp); | |
105 | vnode_rele(vp); | |
106 | vnode_put(vp); | |
107 | } | |
108 | ||
109 | __private_extern__ imageboot_type_t | |
110 | imageboot_needed(void) | |
111 | { | |
112 | imageboot_type_t result = IMAGEBOOT_NONE; | |
113 | char *root_path = NULL; | |
114 | ||
115 | DBG_TRACE("%s: checking for presence of root path\n", __FUNCTION__); | |
116 | ||
117 | MALLOC_ZONE(root_path, caddr_t, MAXPATHLEN, M_NAMEI, M_WAITOK); | |
118 | if (root_path == NULL) { | |
119 | panic("%s: M_NAMEI zone exhausted", __FUNCTION__); | |
120 | } | |
121 | ||
122 | #if CONFIG_LOCKERBOOT | |
123 | if (PE_parse_boot_argn(IMAGEBOOT_LOCKER_ARG, root_path, MAXPATHLEN)) { | |
124 | result = IMAGEBOOT_LOCKER; | |
125 | goto out; | |
126 | } | |
127 | #endif | |
128 | ||
129 | /* Check for first layer */ | |
130 | if (!(PE_parse_boot_argn("rp0", root_path, MAXPATHLEN) || | |
131 | #if CONFIG_IMAGEBOOT_IMG4 | |
132 | PE_parse_boot_argn("arp0", root_path, MAXPATHLEN) || | |
133 | #endif | |
134 | PE_parse_boot_argn("rp", root_path, MAXPATHLEN) || | |
135 | PE_parse_boot_argn(IMAGEBOOT_ROOT_ARG, root_path, MAXPATHLEN) || | |
136 | PE_parse_boot_argn(IMAGEBOOT_AUTHROOT_ARG, root_path, MAXPATHLEN))) { | |
137 | goto out; | |
138 | } | |
139 | ||
140 | /* Sanity-check first layer */ | |
141 | if (imageboot_format_is_valid(root_path)) { | |
142 | DBG_TRACE("%s: Found %s\n", __FUNCTION__, root_path); | |
143 | } else { | |
144 | goto out; | |
145 | } | |
146 | ||
147 | result = IMAGEBOOT_DMG; | |
148 | ||
149 | /* Check for second layer */ | |
150 | if (!(PE_parse_boot_argn("rp1", root_path, MAXPATHLEN) || | |
151 | PE_parse_boot_argn(IMAGEBOOT_CONTAINER_ARG, root_path, MAXPATHLEN))) { | |
152 | goto out; | |
153 | } | |
154 | ||
155 | /* Sanity-check second layer */ | |
156 | if (imageboot_format_is_valid(root_path)) { | |
157 | DBG_TRACE("%s: Found %s\n", __FUNCTION__, root_path); | |
158 | } else { | |
159 | panic("%s: Invalid URL scheme for %s\n", | |
160 | __FUNCTION__, root_path); | |
161 | } | |
162 | ||
163 | out: | |
164 | FREE_ZONE(root_path, MAXPATHLEN, M_NAMEI); | |
165 | ||
166 | return result; | |
167 | } | |
168 | ||
169 | ||
170 | /* | |
171 | * Swaps in new root filesystem based on image path. | |
172 | * Current root filesystem is removed from mount list and | |
173 | * tagged MNTK_BACKS_ROOT, MNT_ROOTFS is cleared on it, and | |
174 | * "rootvnode" is reset. Root vnode of currentroot filesystem | |
175 | * is returned with usecount (no iocount). | |
176 | */ | |
177 | __private_extern__ int | |
178 | imageboot_mount_image(const char *root_path, int height, imageboot_type_t type) | |
179 | { | |
180 | dev_t dev; | |
181 | int error; | |
182 | /* | |
183 | * Need to stash this here since we may do a kernel_mount() on /, which will | |
184 | * automatically update the rootvnode global. Note that vfs_mountroot() does | |
185 | * not update that global, which is a bit weird. | |
186 | */ | |
187 | vnode_t old_rootvnode = rootvnode; | |
188 | vnode_t newdp; | |
189 | mount_t new_rootfs; | |
190 | boolean_t update_rootvnode = FALSE; | |
191 | ||
192 | if (type == IMAGEBOOT_DMG) { | |
193 | error = di_root_image(root_path, rootdevice, DEVMAXNAMESIZE, &dev); | |
194 | if (error) { | |
195 | panic("%s: di_root_image failed: %d\n", __FUNCTION__, error); | |
196 | } | |
197 | ||
198 | rootdev = dev; | |
199 | mountroot = NULL; | |
200 | printf("%s: root device 0x%x\n", __FUNCTION__, rootdev); | |
201 | error = vfs_mountroot(); | |
202 | if (error != 0) { | |
203 | panic("vfs_mountroot() failed.\n"); | |
204 | } | |
205 | ||
206 | update_rootvnode = TRUE; | |
207 | } | |
208 | #if CONFIG_LOCKERBOOT | |
209 | else if (type == IMAGEBOOT_LOCKER) { | |
210 | locker_mount_args_t *mntargs = kalloc(sizeof(*mntargs)); | |
211 | if (!mntargs) { | |
212 | panic("could not alloc mount args"); | |
213 | } | |
214 | ||
215 | strlcpy(mntargs->lmnt_path, root_path, sizeof(mntargs->lmnt_path)); | |
216 | mntargs->lmnt_preferred_hash = 0; | |
217 | ||
218 | DBG_TRACE("%s: mounting locker: %s\n", __FUNCTION__, root_path); | |
219 | error = kernel_mount(LOCKERFS_NAME, NULLVP, NULLVP, "/", | |
220 | mntargs, sizeof(*mntargs), 0, 0, vfs_context_kernel()); | |
221 | if (error) { | |
222 | panic("failed to mount locker: %d", error); | |
223 | } | |
224 | kfree(mntargs, sizeof(*mntargs)); | |
225 | ||
226 | /* Clear the old mount association. */ | |
227 | old_rootvnode->v_mountedhere = NULL; | |
228 | rootvnode->v_mount->mnt_vnodecovered = NULL; | |
229 | } | |
230 | #endif | |
231 | else { | |
232 | panic("invalid imageboot type: %d", type); | |
233 | } | |
234 | ||
235 | /* | |
236 | * Get the vnode for '/'. | |
237 | * Set fdp->fd_fd.fd_cdir to reference it. | |
238 | */ | |
239 | if (VFS_ROOT(TAILQ_LAST(&mountlist, mntlist), &newdp, vfs_context_kernel())) { | |
240 | panic("%s: cannot find root vnode", __FUNCTION__); | |
241 | } | |
242 | DBG_TRACE("%s: old root fsname: %s\n", __FUNCTION__, old_rootvnode->v_mount->mnt_vtable->vfc_name); | |
243 | ||
244 | if (old_rootvnode != NULL) { | |
245 | /* remember the old rootvnode, but remove it from mountlist */ | |
246 | mount_t old_rootfs = old_rootvnode->v_mount; | |
247 | ||
248 | mount_list_remove(old_rootfs); | |
249 | mount_lock(old_rootfs); | |
250 | #ifdef CONFIG_IMGSRC_ACCESS | |
251 | old_rootfs->mnt_kern_flag |= MNTK_BACKS_ROOT; | |
252 | #endif /* CONFIG_IMGSRC_ACCESS */ | |
253 | old_rootfs->mnt_flag &= ~MNT_ROOTFS; | |
254 | mount_unlock(old_rootfs); | |
255 | } | |
256 | ||
257 | /* switch to the new rootvnode */ | |
258 | if (update_rootvnode) { | |
259 | rootvnode = newdp; | |
260 | } | |
261 | ||
262 | new_rootfs = rootvnode->v_mount; | |
263 | mount_lock(new_rootfs); | |
264 | new_rootfs->mnt_flag |= MNT_ROOTFS; | |
265 | mount_unlock(new_rootfs); | |
266 | ||
267 | vnode_ref(newdp); | |
268 | vnode_put(newdp); | |
269 | filedesc0.fd_cdir = newdp; | |
270 | DBG_TRACE("%s: root switched\n", __FUNCTION__); | |
271 | ||
272 | if (old_rootvnode != NULL) { | |
273 | #ifdef CONFIG_IMGSRC_ACCESS | |
274 | if (height >= 0 && PE_imgsrc_mount_supported()) { | |
275 | imgsrc_rootvnodes[height] = old_rootvnode; | |
276 | } else { | |
277 | vnode_get_and_drop_always(old_rootvnode); | |
278 | } | |
279 | #else | |
280 | #pragma unused(height) | |
281 | vnode_get_and_drop_always(old_rootvnode); | |
282 | #endif /* CONFIG_IMGSRC_ACCESS */ | |
283 | } | |
284 | return 0; | |
285 | } | |
286 | ||
287 | int | |
288 | read_file(const char *path, void **bufp, size_t *bufszp) | |
289 | { | |
290 | int err = 0; | |
291 | struct nameidata ndp = {}; | |
292 | struct vnode *vp = NULL; | |
293 | off_t fsize = 0; | |
294 | int resid = 0; | |
295 | char *buf = NULL; | |
296 | bool doclose = false; | |
297 | ||
298 | vfs_context_t ctx = vfs_context_kernel(); | |
299 | proc_t p = vfs_context_proc(ctx); | |
300 | kauth_cred_t kerncred = vfs_context_ucred(ctx); | |
301 | ||
302 | NDINIT(&ndp, LOOKUP, OP_OPEN, LOCKLEAF, UIO_SYSSPACE, CAST_USER_ADDR_T(path), ctx); | |
303 | if ((err = namei(&ndp)) != 0) { | |
304 | AUTHPRNT("namei failed (%s) - %d", path, err); | |
305 | goto out; | |
306 | } | |
307 | nameidone(&ndp); | |
308 | vp = ndp.ni_vp; | |
309 | ||
310 | if ((err = vnode_size(vp, &fsize, ctx)) != 0) { | |
311 | AUTHPRNT("failed to get vnode size of %s - %d", path, err); | |
312 | goto out; | |
313 | } | |
314 | if (fsize < 0) { | |
315 | panic("negative file size"); | |
316 | } | |
317 | ||
318 | if ((err = VNOP_OPEN(vp, FREAD, ctx)) != 0) { | |
319 | AUTHPRNT("failed to open %s - %d", path, err); | |
320 | goto out; | |
321 | } | |
322 | doclose = true; | |
323 | ||
324 | /* if bufsz is non-zero, cap the read at bufsz bytes */ | |
325 | if (*bufszp && *bufszp < (size_t)fsize) { | |
326 | fsize = *bufszp; | |
327 | } | |
328 | ||
329 | buf = kalloc(fsize); | |
330 | if (buf == NULL) { | |
331 | err = ENOMEM; | |
332 | goto out; | |
333 | } | |
334 | ||
335 | if ((err = vn_rdwr(UIO_READ, vp, (caddr_t)buf, fsize, 0, UIO_SYSSPACE, IO_NODELOCKED, kerncred, &resid, p)) != 0) { | |
336 | AUTHPRNT("Cannot read %d bytes from %s - %d", (int)fsize, path, err); | |
337 | goto out; | |
338 | } | |
339 | ||
340 | if (resid) { | |
341 | /* didnt get everything we wanted */ | |
342 | AUTHPRNT("Short read of %d bytes from %s - %d", (int)fsize, path, resid); | |
343 | err = EINVAL; | |
344 | goto out; | |
345 | } | |
346 | ||
347 | out: | |
348 | if (doclose) { | |
349 | VNOP_CLOSE(vp, FREAD, ctx); | |
350 | } | |
351 | if (vp) { | |
352 | vnode_put(vp); | |
353 | vp = NULL; | |
354 | } | |
355 | ||
356 | if (err) { | |
357 | kfree_safe(buf); | |
358 | } else { | |
359 | *bufp = buf; | |
360 | *bufszp = fsize; | |
361 | } | |
362 | ||
363 | return err; | |
364 | } | |
365 | ||
366 | #if CONFIG_IMAGEBOOT_IMG4 || CONFIG_IMAGEBOOT_CHUNKLIST | |
367 | vnode_t | |
368 | imgboot_get_image_file(const char *path, off_t *fsize, int *errp) | |
369 | { | |
370 | struct nameidata ndp = {}; | |
371 | vnode_t vp = NULL; | |
372 | vfs_context_t ctx = vfs_context_kernel(); | |
373 | int err; | |
374 | ||
375 | NDINIT(&ndp, LOOKUP, OP_OPEN, LOCKLEAF, UIO_SYSSPACE, CAST_USER_ADDR_T(path), ctx); | |
376 | if ((err = namei(&ndp)) != 0) { | |
377 | AUTHPRNT("Cannot find %s - error %d", path, err); | |
378 | } else { | |
379 | nameidone(&ndp); | |
380 | vp = ndp.ni_vp; | |
381 | ||
382 | if (vp->v_type != VREG) { | |
383 | err = EINVAL; | |
384 | AUTHPRNT("%s it not a regular file", path); | |
385 | } else if (fsize) { | |
386 | if ((err = vnode_size(vp, fsize, ctx)) != 0) { | |
387 | AUTHPRNT("Cannot get file size of %s - error %d", path, err); | |
388 | } | |
389 | } | |
390 | } | |
391 | ||
392 | if (err) { | |
393 | *errp = err; | |
394 | vp = NULL; | |
395 | } | |
396 | return vp; | |
397 | } | |
398 | #endif /* CONFIG_IMAGEBOOT_CHUNKLIST || CONFIG_IMAGEBOOT_CHUNKLIST */ | |
399 | ||
400 | #if CONFIG_IMAGEBOOT_IMG4 | |
401 | ||
402 | #define APTICKET_NAME "apticket.der" | |
403 | ||
404 | static char * | |
405 | imgboot_get_apticket_path(const char *rootpath) | |
406 | { | |
407 | size_t plen = strlen(rootpath) + sizeof(APTICKET_NAME); | |
408 | char *path = kalloc(plen); | |
409 | ||
410 | if (path) { | |
411 | char *slash; | |
412 | ||
413 | strlcpy(path, rootpath, plen); | |
414 | slash = strrchr(path, '/'); | |
415 | if (slash == NULL) { | |
416 | slash = path; | |
417 | } else { | |
418 | slash++; | |
419 | } | |
420 | strlcpy(slash, APTICKET_NAME, sizeof(APTICKET_NAME) + 1); | |
421 | } | |
422 | return path; | |
423 | } | |
424 | ||
425 | static int | |
426 | authenticate_root_with_img4(const char *rootpath) | |
427 | { | |
428 | errno_t rv; | |
429 | img4_t i4; | |
430 | img4_payload_t i4pl; | |
431 | vnode_t vp; | |
432 | char *ticket_path; | |
433 | size_t tcksz = 0; | |
434 | void *tckbuf = NULL; | |
435 | ||
436 | DBG_TRACE("Check %s\n", rootpath); | |
437 | ||
438 | if (img4if == NULL) { | |
439 | AUTHPRNT("AppleImage4 is not ready"); | |
440 | return EAGAIN; | |
441 | } | |
442 | ||
443 | ticket_path = imgboot_get_apticket_path(rootpath); | |
444 | if (ticket_path == NULL) { | |
445 | AUTHPRNT("Cannot construct ticket path - out of memory"); | |
446 | return ENOMEM; | |
447 | } | |
448 | ||
449 | rv = read_file(ticket_path, &tckbuf, &tcksz); | |
450 | if (rv) { | |
451 | AUTHPRNT("Cannot get a ticket from %s - %d\n", ticket_path, rv); | |
452 | goto out_with_ticket_path; | |
453 | } | |
454 | ||
455 | DBG_TRACE("Got %d bytes of manifest from %s\n", (int)tcksz, ticket_path); | |
456 | ||
457 | rv = img4_init(&i4, 0, tckbuf, tcksz, NULL); | |
458 | if (rv) { | |
459 | AUTHPRNT("Cannot initialise verification handle - error %d", rv); | |
460 | goto out_with_ticket_bytes; | |
461 | } | |
462 | ||
463 | vp = imgboot_get_image_file(rootpath, NULL, &rv); | |
464 | if (vp == NULL) { | |
465 | /* Error message had been printed already */ | |
466 | goto out; | |
467 | } | |
468 | ||
469 | rv = img4_payload_init_with_vnode_4xnu(&i4pl, 'rosi', vp, I4PLF_UNWRAPPED); | |
470 | if (rv) { | |
471 | AUTHPRNT("failed to init payload: %d", rv); | |
472 | goto out; | |
473 | } | |
474 | ||
475 | rv = img4_get_trusted_external_payload(&i4, &i4pl, IMG4_ENVIRONMENT_PPL, NULL, NULL); | |
476 | if (rv) { | |
477 | AUTHPRNT("failed to validate root image %s: %d", rootpath, rv); | |
478 | } | |
479 | ||
480 | img4_payload_destroy(&i4pl); | |
481 | out: | |
482 | img4_destroy(&i4); | |
483 | out_with_ticket_bytes: | |
484 | kfree_safe(tckbuf); | |
485 | out_with_ticket_path: | |
486 | kfree_safe(ticket_path); | |
487 | return rv; | |
488 | } | |
489 | #endif /* CONFIG_IMAGEBOOT_IMG4 */ | |
490 | ||
491 | ||
492 | /* | |
493 | * Attach the image at 'path' as a ramdisk and mount it as our new rootfs. | |
494 | * All existing mounts are first umounted. | |
495 | */ | |
496 | static int | |
497 | imageboot_mount_ramdisk(const char *path) | |
498 | { | |
499 | int err = 0; | |
500 | size_t bufsz = 0; | |
501 | void *buf = NULL; | |
502 | dev_t dev; | |
503 | vnode_t newdp; | |
504 | mount_t new_rootfs; | |
505 | ||
506 | /* Read our target image from disk */ | |
507 | err = read_file(path, &buf, &bufsz); | |
508 | if (err) { | |
509 | printf("%s: failed: read_file() = %d\n", __func__, err); | |
510 | goto out; | |
511 | } | |
512 | DBG_TRACE("%s: read '%s' sz = %lu\n", __func__, path, bufsz); | |
513 | ||
514 | #if CONFIG_IMGSRC_ACCESS | |
515 | /* Re-add all root mounts to the mount list in the correct order... */ | |
516 | mount_list_remove(rootvnode->v_mount); | |
517 | for (int i = 0; i < MAX_IMAGEBOOT_NESTING; i++) { | |
518 | struct vnode *vn = imgsrc_rootvnodes[i]; | |
519 | if (vn) { | |
520 | vnode_getalways(vn); | |
521 | imgsrc_rootvnodes[i] = NULLVP; | |
522 | ||
523 | mount_t mnt = vn->v_mount; | |
524 | mount_lock(mnt); | |
525 | mnt->mnt_flag |= MNT_ROOTFS; | |
526 | mount_list_add(mnt); | |
527 | mount_unlock(mnt); | |
528 | ||
529 | vnode_rele(vn); | |
530 | vnode_put(vn); | |
531 | } | |
532 | } | |
533 | mount_list_add(rootvnode->v_mount); | |
534 | #endif | |
535 | ||
536 | /* ... and unmount everything */ | |
537 | vnode_get_and_drop_always(rootvnode); | |
538 | filedesc0.fd_cdir = NULL; | |
539 | rootvnode = NULL; | |
540 | vfs_unmountall(); | |
541 | ||
542 | /* Attach the ramfs image ... */ | |
543 | err = di_root_ramfile_buf(buf, bufsz, rootdevice, DEVMAXNAMESIZE, &dev); | |
544 | if (err) { | |
545 | printf("%s: failed: di_root_ramfile_buf() = %d\n", __func__, err); | |
546 | goto out; | |
547 | } | |
548 | ||
549 | /* ... and mount it */ | |
550 | rootdev = dev; | |
551 | mountroot = NULL; | |
552 | err = vfs_mountroot(); | |
553 | if (err) { | |
554 | printf("%s: failed: vfs_mountroot() = %d\n", __func__, err); | |
555 | goto out; | |
556 | } | |
557 | ||
558 | /* Switch to new root vnode */ | |
559 | if (VFS_ROOT(TAILQ_LAST(&mountlist, mntlist), &newdp, vfs_context_kernel())) { | |
560 | panic("%s: cannot find root vnode", __func__); | |
561 | } | |
562 | rootvnode = newdp; | |
563 | rootvnode->v_flag |= VROOT; | |
564 | new_rootfs = rootvnode->v_mount; | |
565 | mount_lock(new_rootfs); | |
566 | new_rootfs->mnt_flag |= MNT_ROOTFS; | |
567 | mount_unlock(new_rootfs); | |
568 | ||
569 | vnode_ref(newdp); | |
570 | vnode_put(newdp); | |
571 | filedesc0.fd_cdir = newdp; | |
572 | ||
573 | DBG_TRACE("%s: root switched\n", __func__); | |
574 | ||
575 | out: | |
576 | if (err) { | |
577 | kfree_safe(buf); | |
578 | } | |
579 | return err; | |
580 | } | |
581 | ||
582 | /* | |
583 | * If the path is in <file://> URL format then we allocate memory and decode it, | |
584 | * otherwise return the same pointer. | |
585 | * | |
586 | * Caller is expected to check if the pointers are different. | |
587 | */ | |
588 | static char * | |
589 | url_to_path(char *url_path) | |
590 | { | |
591 | char *path = url_path; | |
592 | size_t len = strlen(kIBFilePrefix); | |
593 | ||
594 | if (strncmp(kIBFilePrefix, url_path, len) == 0) { | |
595 | /* its a URL - remove the file:// prefix and percent-decode */ | |
596 | url_path += len; | |
597 | ||
598 | len = strlen(url_path); | |
599 | if (len) { | |
600 | /* Make a copy of the path to URL-decode */ | |
601 | path = kalloc(len + 1); | |
602 | if (path == NULL) { | |
603 | panic("imageboot path allocation failed - cannot allocate %d bytes\n", (int)len); | |
604 | } | |
605 | ||
606 | strlcpy(path, url_path, len + 1); | |
607 | url_decode(path); | |
608 | } else { | |
609 | panic("Bogus imageboot path URL - missing path\n"); | |
610 | } | |
611 | ||
612 | DBG_TRACE("%s: root image URL <%s> becomes %s\n", __func__, url_path, path); | |
613 | } | |
614 | ||
615 | return path; | |
616 | } | |
617 | ||
618 | static boolean_t | |
619 | imageboot_setup_new(imageboot_type_t type) | |
620 | { | |
621 | int error; | |
622 | char *root_path = NULL; | |
623 | int height = 0; | |
624 | boolean_t done = FALSE; | |
625 | boolean_t auth_root = TRUE; | |
626 | boolean_t ramdisk_root = FALSE; | |
627 | ||
628 | MALLOC_ZONE(root_path, caddr_t, MAXPATHLEN, M_NAMEI, M_WAITOK); | |
629 | assert(root_path != NULL); | |
630 | ||
631 | #if CONFIG_LOCKERBOOT | |
632 | if (type == IMAGEBOOT_LOCKER) { | |
633 | if (!PE_parse_boot_argn(IMAGEBOOT_LOCKER_ARG, root_path, MAXPATHLEN)) { | |
634 | panic("locker boot with no locker given"); | |
635 | } | |
636 | ||
637 | DBG_TRACE("%s: root fsname: %s\n", __FUNCTION__, rootvnode->v_mount->mnt_vtable->vfc_name); | |
638 | ||
639 | /* | |
640 | * The locker path is a path, not a URL, so just pass it directly to | |
641 | * imageboot_mount_image(). | |
642 | */ | |
643 | error = imageboot_mount_image(root_path, 0, type); | |
644 | if (error) { | |
645 | panic("failed to mount system locker: %d", error); | |
646 | } | |
647 | ||
648 | done = TRUE; | |
649 | goto out; | |
650 | } | |
651 | #endif /* CONFIG_LOCKERBOOT */ | |
652 | ||
653 | unsigned imgboot_arg; | |
654 | if (PE_parse_boot_argn("-rootdmg-ramdisk", &imgboot_arg, sizeof(imgboot_arg))) { | |
655 | ramdisk_root = TRUE; | |
656 | } | |
657 | ||
658 | if (PE_parse_boot_argn(IMAGEBOOT_CONTAINER_ARG, root_path, MAXPATHLEN) == TRUE) { | |
659 | printf("%s: container image url is %s\n", __FUNCTION__, root_path); | |
660 | error = imageboot_mount_image(root_path, height, type); | |
661 | if (error != 0) { | |
662 | panic("Failed to mount container image."); | |
663 | } | |
664 | ||
665 | height++; | |
666 | } | |
667 | ||
668 | if (PE_parse_boot_argn(IMAGEBOOT_AUTHROOT_ARG, root_path, MAXPATHLEN) == FALSE && | |
669 | PE_parse_boot_argn(IMAGEBOOT_ROOT_ARG, root_path, MAXPATHLEN) == FALSE) { | |
670 | if (height > 0) { | |
671 | panic("%s specified without %s or %s?\n", IMAGEBOOT_CONTAINER_ARG, IMAGEBOOT_AUTHROOT_ARG, IMAGEBOOT_ROOT_ARG); | |
672 | } | |
673 | goto out; | |
674 | } | |
675 | ||
676 | printf("%s: root image URL is '%s'\n", __func__, root_path); | |
677 | ||
678 | #if CONFIG_CSR | |
679 | if (auth_root && (csr_check(CSR_ALLOW_ANY_RECOVERY_OS) == 0)) { | |
680 | AUTHPRNT("CSR_ALLOW_ANY_RECOVERY_OS set, skipping root image authentication"); | |
681 | auth_root = FALSE; | |
682 | } | |
683 | #endif | |
684 | ||
685 | /* Make a copy of the path to URL-decode */ | |
686 | char *path = url_to_path(root_path); | |
687 | assert(path); | |
688 | ||
689 | #if CONFIG_IMAGEBOOT_CHUNKLIST | |
690 | if (auth_root) { | |
691 | AUTHDBG("authenticating root image at %s", path); | |
692 | error = authenticate_root_with_chunklist(path); | |
693 | if (error) { | |
694 | panic("root image authentication failed (err = %d)\n", error); | |
695 | } | |
696 | AUTHDBG("successfully authenticated %s", path); | |
697 | } | |
698 | #endif | |
699 | ||
700 | if (ramdisk_root) { | |
701 | error = imageboot_mount_ramdisk(path); | |
702 | } else { | |
703 | error = imageboot_mount_image(root_path, height, type); | |
704 | } | |
705 | ||
706 | if (path != root_path) { | |
707 | kfree_safe(path); | |
708 | } | |
709 | ||
710 | if (error) { | |
711 | panic("Failed to mount root image (err=%d, auth=%d, ramdisk=%d)\n", | |
712 | error, auth_root, ramdisk_root); | |
713 | } | |
714 | ||
715 | #if CONFIG_IMAGEBOOT_CHUNKLIST | |
716 | if (auth_root) { | |
717 | /* check that the image version matches the running kernel */ | |
718 | AUTHDBG("checking root image version"); | |
719 | error = authenticate_root_version_check(); | |
720 | if (error) { | |
721 | panic("root image version check failed"); | |
722 | } else { | |
723 | AUTHDBG("root image version matches kernel"); | |
724 | } | |
725 | } | |
726 | #endif | |
727 | ||
728 | done = TRUE; | |
729 | ||
730 | out: | |
731 | FREE_ZONE(root_path, MAXPATHLEN, M_NAMEI); | |
732 | return done; | |
733 | } | |
734 | ||
735 | __private_extern__ void | |
736 | imageboot_setup(imageboot_type_t type) | |
737 | { | |
738 | int error = 0; | |
739 | char *root_path = NULL; | |
740 | ||
741 | DBG_TRACE("%s: entry\n", __FUNCTION__); | |
742 | ||
743 | if (rootvnode == NULL) { | |
744 | panic("imageboot_setup: rootvnode is NULL."); | |
745 | } | |
746 | ||
747 | /* | |
748 | * New boot-arg scheme: | |
749 | * root-dmg : the dmg that will be the root filesystem, authenticated by default. | |
750 | * auth-root-dmg : same as root-dmg. | |
751 | * container-dmg : an optional dmg that contains the root-dmg. | |
752 | * locker : the locker that will be the root filesystem -- mutually | |
753 | * exclusive with any other boot-arg. | |
754 | */ | |
755 | if (imageboot_setup_new(type)) { | |
756 | return; | |
757 | } | |
758 | ||
759 | MALLOC_ZONE(root_path, caddr_t, MAXPATHLEN, M_NAMEI, M_WAITOK); | |
760 | assert(root_path != NULL); | |
761 | ||
762 | /* | |
763 | * Look for outermost disk image to root from. If we're doing a nested boot, | |
764 | * there's some sense in which the outer image never needs to be the root filesystem, | |
765 | * but it does need very similar treatment: it must not be unmounted, needs a fake | |
766 | * device vnode created for it, and should not show up in getfsstat() until exposed | |
767 | * with MNT_IMGSRC. We just make it the temporary root. | |
768 | */ | |
769 | #if CONFIG_IMAGEBOOT_IMG4 | |
770 | if (PE_parse_boot_argn("arp0", root_path, MAXPATHLEN)) { | |
771 | char *path = url_to_path(root_path); | |
772 | ||
773 | assert(path); | |
774 | ||
775 | if (authenticate_root_with_img4(path)) { | |
776 | panic("Root image %s does not match the manifest\n", root_path); | |
777 | } | |
778 | if (path != root_path) { | |
779 | kfree_safe(path); | |
780 | } | |
781 | } else | |
782 | #endif /* CONFIG_IMAGEBOOT_IMG4 */ | |
783 | if ((PE_parse_boot_argn("rp", root_path, MAXPATHLEN) == FALSE) && | |
784 | (PE_parse_boot_argn("rp0", root_path, MAXPATHLEN) == FALSE)) { | |
785 | panic("%s: no valid path to image.\n", __FUNCTION__); | |
786 | } | |
787 | ||
788 | DBG_TRACE("%s: root image url is %s\n", __FUNCTION__, root_path); | |
789 | ||
790 | error = imageboot_mount_image(root_path, 0, type); | |
791 | if (error) { | |
792 | panic("Failed on first stage of imageboot."); | |
793 | } | |
794 | ||
795 | /* | |
796 | * See if we are rooting from a nested image | |
797 | */ | |
798 | if (PE_parse_boot_argn("rp1", root_path, MAXPATHLEN) == FALSE) { | |
799 | goto done; | |
800 | } | |
801 | ||
802 | printf("%s: second level root image url is %s\n", __FUNCTION__, root_path); | |
803 | ||
804 | /* | |
805 | * If we fail to set up second image, it's not a given that we | |
806 | * can safely root off the first. | |
807 | */ | |
808 | error = imageboot_mount_image(root_path, 1, type); | |
809 | if (error) { | |
810 | panic("Failed on second stage of imageboot."); | |
811 | } | |
812 | ||
813 | done: | |
814 | FREE_ZONE(root_path, MAXPATHLEN, M_NAMEI); | |
815 | ||
816 | DBG_TRACE("%s: exit\n", __FUNCTION__); | |
817 | ||
818 | return; | |
819 | } |