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 | #include <kern/kalloc.h> |
51 | |
52 | #include <pexpert/pexpert.h> |
53 | #include <kern/chunklist.h> |
54 | |
55 | extern struct filedesc filedesc0; |
56 | |
57 | extern int (*mountroot)(void); |
58 | extern char rootdevice[DEVMAXNAMESIZE]; |
59 | |
60 | #define DEBUG_IMAGEBOOT 0 |
61 | |
62 | #if DEBUG_IMAGEBOOT |
63 | #define DBG_TRACE(...) printf(__VA_ARGS__) |
64 | #else |
65 | #define DBG_TRACE(...) do {} while(0) |
66 | #endif |
67 | |
68 | extern int di_root_image(const char *path, char *devname, size_t devsz, dev_t *dev_p); |
69 | extern int di_root_ramfile_buf(void *buf, size_t bufsz, char *devname, size_t devsz, dev_t *dev_p); |
70 | |
71 | static boolean_t imageboot_setup_new(void); |
72 | |
73 | #define kIBFilePrefix "file://" |
74 | |
75 | __private_extern__ int |
76 | imageboot_format_is_valid(const char *root_path) |
77 | { |
78 | return (strncmp(root_path, kIBFilePrefix, |
79 | strlen(kIBFilePrefix)) == 0); |
80 | } |
81 | |
82 | static void |
83 | vnode_get_and_drop_always(vnode_t vp) |
84 | { |
85 | vnode_getalways(vp); |
86 | vnode_rele(vp); |
87 | vnode_put(vp); |
88 | } |
89 | |
90 | __private_extern__ int |
91 | imageboot_needed(void) |
92 | { |
93 | int result = 0; |
94 | char *root_path = NULL; |
95 | |
96 | DBG_TRACE("%s: checking for presence of root path\n" , __FUNCTION__); |
97 | |
98 | MALLOC_ZONE(root_path, caddr_t, MAXPATHLEN, M_NAMEI, M_WAITOK); |
99 | if (root_path == NULL) |
100 | panic("%s: M_NAMEI zone exhausted" , __FUNCTION__); |
101 | |
102 | /* Check for first layer */ |
103 | if (!(PE_parse_boot_argn("rp0" , root_path, MAXPATHLEN) || |
104 | PE_parse_boot_argn("rp" , root_path, MAXPATHLEN) || |
105 | PE_parse_boot_argn(IMAGEBOOT_ROOT_ARG, root_path, MAXPATHLEN) || |
106 | PE_parse_boot_argn(IMAGEBOOT_AUTHROOT_ARG, root_path, MAXPATHLEN))) { |
107 | goto out; |
108 | } |
109 | |
110 | /* Sanity-check first layer */ |
111 | if (imageboot_format_is_valid(root_path)) { |
112 | DBG_TRACE("%s: Found %s\n" , __FUNCTION__, root_path); |
113 | } else { |
114 | goto out; |
115 | } |
116 | |
117 | result = 1; |
118 | |
119 | /* Check for second layer */ |
120 | if (!(PE_parse_boot_argn("rp1" , root_path, MAXPATHLEN) || |
121 | PE_parse_boot_argn(IMAGEBOOT_CONTAINER_ARG, root_path, MAXPATHLEN))) { |
122 | goto out; |
123 | } |
124 | |
125 | /* Sanity-check second layer */ |
126 | if (imageboot_format_is_valid(root_path)) { |
127 | DBG_TRACE("%s: Found %s\n" , __FUNCTION__, root_path); |
128 | } else { |
129 | panic("%s: Invalid URL scheme for %s\n" , |
130 | __FUNCTION__, root_path); |
131 | } |
132 | |
133 | out: |
134 | FREE_ZONE(root_path, MAXPATHLEN, M_NAMEI); |
135 | |
136 | return (result); |
137 | } |
138 | |
139 | |
140 | /* |
141 | * Swaps in new root filesystem based on image path. |
142 | * Current root filesystem is removed from mount list and |
143 | * tagged MNTK_BACKS_ROOT, MNT_ROOTFS is cleared on it, and |
144 | * "rootvnode" is reset. Root vnode of currentroot filesystem |
145 | * is returned with usecount (no iocount). |
146 | */ |
147 | __private_extern__ int |
148 | imageboot_mount_image(const char *root_path, int height) |
149 | { |
150 | dev_t dev; |
151 | int error; |
152 | vnode_t old_rootvnode = NULL; |
153 | vnode_t newdp; |
154 | mount_t new_rootfs; |
155 | |
156 | error = di_root_image(root_path, rootdevice, DEVMAXNAMESIZE, &dev); |
157 | if (error) { |
158 | panic("%s: di_root_image failed: %d\n" , __FUNCTION__, error); |
159 | } |
160 | |
161 | rootdev = dev; |
162 | mountroot = NULL; |
163 | printf("%s: root device 0x%x\n" , __FUNCTION__, rootdev); |
164 | error = vfs_mountroot(); |
165 | if (error != 0) { |
166 | panic("vfs_mountroot() failed.\n" ); |
167 | } |
168 | |
169 | /* |
170 | * Get the vnode for '/'. |
171 | * Set fdp->fd_fd.fd_cdir to reference it. |
172 | */ |
173 | if (VFS_ROOT(TAILQ_LAST(&mountlist,mntlist), &newdp, vfs_context_kernel())) |
174 | panic("%s: cannot find root vnode" , __FUNCTION__); |
175 | |
176 | if (rootvnode != NULL) { |
177 | /* remember the old rootvnode, but remove it from mountlist */ |
178 | mount_t old_rootfs; |
179 | |
180 | old_rootvnode = rootvnode; |
181 | old_rootfs = rootvnode->v_mount; |
182 | |
183 | mount_list_remove(old_rootfs); |
184 | |
185 | mount_lock(old_rootfs); |
186 | #ifdef CONFIG_IMGSRC_ACCESS |
187 | old_rootfs->mnt_kern_flag |= MNTK_BACKS_ROOT; |
188 | #endif /* CONFIG_IMGSRC_ACCESS */ |
189 | old_rootfs->mnt_flag &= ~MNT_ROOTFS; |
190 | mount_unlock(old_rootfs); |
191 | } |
192 | |
193 | /* switch to the new rootvnode */ |
194 | rootvnode = newdp; |
195 | |
196 | new_rootfs = rootvnode->v_mount; |
197 | mount_lock(new_rootfs); |
198 | new_rootfs->mnt_flag |= MNT_ROOTFS; |
199 | mount_unlock(new_rootfs); |
200 | |
201 | vnode_ref(newdp); |
202 | vnode_put(newdp); |
203 | filedesc0.fd_cdir = newdp; |
204 | DBG_TRACE("%s: root switched\n" , __FUNCTION__); |
205 | |
206 | if (old_rootvnode != NULL) { |
207 | #ifdef CONFIG_IMGSRC_ACCESS |
208 | if (height >= 0 && PE_imgsrc_mount_supported()) { |
209 | imgsrc_rootvnodes[height] = old_rootvnode; |
210 | } else { |
211 | vnode_get_and_drop_always(old_rootvnode); |
212 | } |
213 | #else |
214 | height = 0; /* keep the compiler from complaining */ |
215 | vnode_get_and_drop_always(old_rootvnode); |
216 | #endif /* CONFIG_IMGSRC_ACCESS */ |
217 | } |
218 | return 0; |
219 | } |
220 | |
221 | |
222 | /* |
223 | * Authenticated root-dmg support |
224 | */ |
225 | |
226 | #define AUTHDBG(fmt, args...) do { printf("%s: " fmt "\n", __func__, ##args); } while (0) |
227 | #define AUTHPRNT(fmt, args...) do { printf("%s: " fmt "\n", __func__, ##args); } while (0) |
228 | |
229 | #define kfree_safe(x) do { if ((x)) { kfree_addr((x)); (x) = NULL; } } while (0) |
230 | |
231 | enum { |
232 | MISSING_SIG = -1, |
233 | INVALID_SIG = -2 |
234 | }; |
235 | |
236 | static void |
237 | key_byteswap(void *_dst, const void *_src, size_t len) |
238 | { |
239 | uint32_t *dst __attribute__((align_value(1))) = _dst; |
240 | const uint32_t *src __attribute__((align_value(1))) = _src; |
241 | |
242 | assert(len % sizeof(uint32_t) == 0); |
243 | |
244 | len = len / sizeof(uint32_t); |
245 | for (size_t i = 0; i < len; i++) { |
246 | dst[len-i-1] = OSSwapInt32(src[i]); |
247 | } |
248 | } |
249 | |
250 | static int |
251 | read_file(const char *path, void **bufp, size_t *bufszp) |
252 | { |
253 | int err = 0; |
254 | struct nameidata ndp = {}; |
255 | struct vnode *vp = NULL; |
256 | off_t fsize = 0; |
257 | int resid = 0; |
258 | char *buf = NULL; |
259 | bool doclose = false; |
260 | |
261 | vfs_context_t ctx = vfs_context_kernel(); |
262 | proc_t p = vfs_context_proc(ctx); |
263 | kauth_cred_t kerncred = vfs_context_ucred(ctx); |
264 | |
265 | NDINIT(&ndp, LOOKUP, OP_OPEN, LOCKLEAF, UIO_SYSSPACE, CAST_USER_ADDR_T(path), ctx); |
266 | if ((err = namei(&ndp)) != 0) { |
267 | AUTHPRNT("namei failed (%s)" , path); |
268 | goto out; |
269 | } |
270 | nameidone(&ndp); |
271 | vp = ndp.ni_vp; |
272 | |
273 | if ((err = vnode_size(vp, &fsize, ctx)) != 0) { |
274 | AUTHPRNT("failed to get vnode size" ); |
275 | goto out; |
276 | } |
277 | if (fsize < 0) { |
278 | panic("negative file size" ); |
279 | } |
280 | |
281 | if ((err = VNOP_OPEN(vp, FREAD, ctx)) != 0) { |
282 | AUTHPRNT("failed to open vnode" ); |
283 | goto out; |
284 | } |
285 | doclose = true; |
286 | |
287 | /* if bufsz is non-zero, cap the read at bufsz bytes */ |
288 | if (*bufszp && *bufszp < (size_t)fsize) { |
289 | fsize = *bufszp; |
290 | } |
291 | |
292 | buf = kalloc(fsize); |
293 | if (buf == NULL) { |
294 | err = ENOMEM; |
295 | goto out; |
296 | } |
297 | |
298 | if ((err = vn_rdwr(UIO_READ, vp, (caddr_t)buf, fsize, 0, UIO_SYSSPACE, IO_NODELOCKED, kerncred, &resid, p)) != 0) { |
299 | AUTHPRNT("vn_rdwr() failed" ); |
300 | goto out; |
301 | } |
302 | |
303 | if (resid) { |
304 | /* didnt get everything we wanted */ |
305 | AUTHPRNT("vn_rdwr resid = %d" , resid); |
306 | err = EINVAL; |
307 | goto out; |
308 | } |
309 | |
310 | out: |
311 | if (doclose) { |
312 | VNOP_CLOSE(vp, FREAD, ctx); |
313 | } |
314 | if (vp) { |
315 | vnode_put(vp); |
316 | vp = NULL; |
317 | } |
318 | |
319 | if (err) { |
320 | kfree_safe(buf); |
321 | } else { |
322 | *bufp = buf; |
323 | *bufszp = fsize; |
324 | } |
325 | |
326 | return err; |
327 | } |
328 | |
329 | static int |
330 | validate_signature(const uint8_t *key_msb, size_t keylen, uint8_t *sig_msb, size_t siglen, uint8_t *digest) |
331 | { |
332 | int err = 0; |
333 | bool sig_valid = false; |
334 | uint8_t *sig = NULL; |
335 | |
336 | const uint8_t exponent[] = { 0x01, 0x00, 0x01 }; |
337 | uint8_t *modulus = kalloc(keylen); |
338 | rsa_pub_ctx *rsa_ctx = kalloc(sizeof(rsa_pub_ctx)); |
339 | sig = kalloc(siglen); |
340 | |
341 | if (modulus == NULL || rsa_ctx == NULL || sig == NULL) { |
342 | err = ENOMEM; |
343 | goto out; |
344 | } |
345 | |
346 | bzero(rsa_ctx, sizeof(rsa_pub_ctx)); |
347 | key_byteswap(modulus, key_msb, keylen); |
348 | key_byteswap(sig, sig_msb, siglen); |
349 | |
350 | err = rsa_make_pub(rsa_ctx, |
351 | sizeof(exponent), exponent, |
352 | CHUNKLIST_PUBKEY_LEN, modulus); |
353 | if (err) { |
354 | AUTHPRNT("rsa_make_pub() failed" ); |
355 | goto out; |
356 | } |
357 | |
358 | err = rsa_verify_pkcs1v15(rsa_ctx, CC_DIGEST_OID_SHA256, |
359 | SHA256_DIGEST_LENGTH, digest, |
360 | siglen, sig, |
361 | &sig_valid); |
362 | if (err) { |
363 | sig_valid = false; |
364 | AUTHPRNT("rsa_verify() failed" ); |
365 | err = EINVAL; |
366 | goto out; |
367 | } |
368 | |
369 | out: |
370 | kfree_safe(sig); |
371 | kfree_safe(rsa_ctx); |
372 | kfree_safe(modulus); |
373 | |
374 | if (err) { |
375 | return err; |
376 | } else if (sig_valid == true) { |
377 | return 0; /* success */ |
378 | } else { |
379 | return INVALID_SIG; |
380 | } |
381 | } |
382 | |
383 | static int |
384 | validate_chunklist(void *buf, size_t len) |
385 | { |
386 | int err = 0; |
387 | size_t sigsz = 0; |
388 | size_t sig_end = 0; |
389 | size_t chunks_end = 0; |
390 | bool valid_sig = false; |
391 | struct chunklist_hdr *hdr = buf; |
392 | |
393 | if (len < sizeof(struct chunklist_hdr)) { |
394 | AUTHPRNT("no space for header" ); |
395 | return EINVAL; |
396 | } |
397 | |
398 | /* recognized file format? */ |
399 | if (hdr->cl_magic != CHUNKLIST_MAGIC || |
400 | hdr->cl_file_ver != CHUNKLIST_FILE_VERSION_10 || |
401 | hdr->cl_chunk_method != CHUNKLIST_SIGNATURE_METHOD_10 || |
402 | hdr->cl_sig_method != CHUNKLIST_SIGNATURE_METHOD_10) { |
403 | AUTHPRNT("unrecognized chunklist format" ); |
404 | return EINVAL; |
405 | } |
406 | |
407 | /* does the chunk list fall within the bounds of the buffer? */ |
408 | if (os_mul_and_add_overflow(hdr->cl_chunk_count, sizeof(struct chunklist_chunk), hdr->cl_chunk_offset, &chunks_end) || |
409 | hdr->cl_chunk_offset < sizeof(struct chunklist_hdr) || chunks_end > len) { |
410 | AUTHPRNT("invalid chunk_count (%llu) or chunk_offset (%llu)" , |
411 | hdr->cl_chunk_count, hdr->cl_chunk_offset); |
412 | return EINVAL; |
413 | } |
414 | |
415 | /* does the signature fall within the bounds of the buffer? */ |
416 | if (os_add_overflow(hdr->cl_sig_offset, sizeof(struct chunklist_sig), &sig_end) || |
417 | hdr->cl_sig_offset < sizeof(struct chunklist_hdr) || |
418 | hdr->cl_sig_offset < chunks_end || |
419 | hdr->cl_sig_offset > len) { |
420 | AUTHPRNT("invalid signature offset (%llu)" , hdr->cl_sig_offset); |
421 | return EINVAL; |
422 | } |
423 | |
424 | if (sig_end > len || os_sub_overflow(len, hdr->cl_sig_offset, &sigsz) || sigsz != CHUNKLIST_SIG_LEN) { |
425 | /* missing or incorrect signature size */ |
426 | return MISSING_SIG; |
427 | } |
428 | |
429 | AUTHDBG("hashing chunklist" ); |
430 | |
431 | /* hash the chunklist (excluding the signature) */ |
432 | uint8_t sha_digest[SHA256_DIGEST_LENGTH]; |
433 | SHA256_CTX sha_ctx; |
434 | SHA256_Init(&sha_ctx); |
435 | SHA256_Update(&sha_ctx, buf, hdr->cl_sig_offset); |
436 | SHA256_Final(sha_digest, &sha_ctx); |
437 | |
438 | AUTHDBG("validating chunklist signature against pub keys" ); |
439 | for (size_t i = 0; i < CHUNKLIST_NPUBKEYS; i++) { |
440 | const struct chunklist_pubkey *key = &chunklist_pubkeys[i]; |
441 | err = validate_signature(key->key, CHUNKLIST_PUBKEY_LEN, |
442 | buf + hdr->cl_sig_offset, sigsz, sha_digest); |
443 | if (err == 0) { |
444 | AUTHDBG("validated chunklist signature with key %lu (prod=%d)" , i, key->isprod); |
445 | valid_sig = key->isprod; |
446 | #if IMAGEBOOT_ALLOW_DEVKEYS |
447 | if (!key->isprod) { |
448 | /* allow dev keys in dev builds only */ |
449 | AUTHDBG("*** allowing DEV key: this will fail in customer builds ***" ); |
450 | valid_sig = true; |
451 | } |
452 | #endif |
453 | goto out; |
454 | } else if (err == INVALID_SIG) { |
455 | /* try the next key */ |
456 | } else { |
457 | goto out; /* something bad happened */ |
458 | } |
459 | } |
460 | |
461 | /* At this point we tried all the keys: nothing went wrong but none of them |
462 | * signed our chunklist. */ |
463 | AUTHPRNT("signature did not verify against any known public key" ); |
464 | |
465 | out: |
466 | if (err) { |
467 | return err; |
468 | } else if (valid_sig == true) { |
469 | return 0; /* signed, and everything checked out */ |
470 | } else { |
471 | return EINVAL; |
472 | } |
473 | } |
474 | |
475 | static int |
476 | validate_root_image(const char *root_path, void *chunklist) |
477 | { |
478 | int err = 0; |
479 | struct chunklist_hdr *hdr = chunklist; |
480 | struct chunklist_chunk *chk = NULL; |
481 | size_t ch = 0; |
482 | struct nameidata ndp = {}; |
483 | struct vnode *vp = NULL; |
484 | off_t fsize = 0; |
485 | off_t offset = 0; |
486 | bool doclose = false; |
487 | size_t bufsz = 0; |
488 | void *buf = NULL; |
489 | |
490 | vfs_context_t ctx = vfs_context_kernel(); |
491 | kauth_cred_t kerncred = vfs_context_ucred(ctx); |
492 | proc_t p = vfs_context_proc(ctx); |
493 | |
494 | AUTHDBG("validating root dmg %s" , root_path); |
495 | |
496 | /* |
497 | * Open the DMG |
498 | */ |
499 | NDINIT(&ndp, LOOKUP, OP_OPEN, LOCKLEAF, UIO_SYSSPACE, CAST_USER_ADDR_T(root_path), ctx); |
500 | if ((err = namei(&ndp)) != 0) { |
501 | AUTHPRNT("namei failed (%s)" , root_path); |
502 | goto out; |
503 | } |
504 | nameidone(&ndp); |
505 | vp = ndp.ni_vp; |
506 | |
507 | if (vp->v_type != VREG) { |
508 | err = EINVAL; |
509 | goto out; |
510 | } |
511 | |
512 | if ((err = vnode_size(vp, &fsize, ctx)) != 0) { |
513 | AUTHPRNT("failed to get vnode size" ); |
514 | goto out; |
515 | } |
516 | |
517 | if ((err = VNOP_OPEN(vp, FREAD, ctx)) != 0) { |
518 | AUTHPRNT("failed to open vnode" ); |
519 | goto out; |
520 | } |
521 | doclose = true; |
522 | |
523 | /* |
524 | * Iterate the chunk list and check each chunk |
525 | */ |
526 | chk = chunklist + hdr->cl_chunk_offset; |
527 | for (ch = 0; ch < hdr->cl_chunk_count; ch++) { |
528 | int resid = 0; |
529 | |
530 | if (!buf) { |
531 | /* allocate buffer based on first chunk size */ |
532 | buf = kalloc(chk->chunk_size); |
533 | if (buf == NULL) { |
534 | err = ENOMEM; |
535 | goto out; |
536 | } |
537 | bufsz = chk->chunk_size; |
538 | } |
539 | |
540 | if (chk->chunk_size > bufsz) { |
541 | AUTHPRNT("chunk size too big" ); |
542 | err = EINVAL; |
543 | goto out; |
544 | } |
545 | |
546 | err = vn_rdwr(UIO_READ, vp, (caddr_t)buf, chk->chunk_size, offset, UIO_SYSSPACE, IO_NODELOCKED, kerncred, &resid, p); |
547 | if (err) { |
548 | AUTHPRNT("vn_rdrw fail (err = %d, resid = %d)" , err, resid); |
549 | goto out; |
550 | } |
551 | if (resid) { |
552 | err = EINVAL; |
553 | AUTHPRNT("chunk covered non-existant part of image" ); |
554 | goto out; |
555 | } |
556 | |
557 | /* calculate the SHA256 of this chunk */ |
558 | uint8_t sha_digest[SHA256_DIGEST_LENGTH]; |
559 | SHA256_CTX sha_ctx; |
560 | SHA256_Init(&sha_ctx); |
561 | SHA256_Update(&sha_ctx, buf, chk->chunk_size); |
562 | SHA256_Final(sha_digest, &sha_ctx); |
563 | |
564 | /* Check the calculated SHA matches the chunk list */ |
565 | if (bcmp(sha_digest, chk->chunk_sha256, SHA256_DIGEST_LENGTH) != 0) { |
566 | AUTHPRNT("SHA mismatch on chunk %lu (offset %lld, size %u)" , ch, offset, chk->chunk_size); |
567 | err = EINVAL; |
568 | goto out; |
569 | } |
570 | |
571 | if (os_add_overflow(offset, chk->chunk_size, &offset)) { |
572 | err = EINVAL; |
573 | goto out; |
574 | } |
575 | chk++; |
576 | } |
577 | |
578 | if (offset != fsize) { |
579 | AUTHPRNT("chunklist did not cover entire file (offset = %lld, fsize = %lld)" , offset, fsize); |
580 | err = EINVAL; |
581 | goto out; |
582 | } |
583 | |
584 | out: |
585 | kfree_safe(buf); |
586 | if (doclose) { |
587 | VNOP_CLOSE(vp, FREAD, ctx); |
588 | } |
589 | if (vp) { |
590 | vnode_put(vp); |
591 | vp = NULL; |
592 | } |
593 | |
594 | return err; |
595 | } |
596 | |
597 | static int |
598 | construct_chunklist_path(const char *root_path, char **bufp) |
599 | { |
600 | int err = 0; |
601 | char *path = NULL; |
602 | size_t len = 0; |
603 | |
604 | path = kalloc(MAXPATHLEN); |
605 | if (path == NULL) { |
606 | AUTHPRNT("failed to allocate space for chunklist path" ); |
607 | err = ENOMEM; |
608 | goto out; |
609 | } |
610 | |
611 | len = strnlen(root_path, MAXPATHLEN); |
612 | if (len < MAXPATHLEN && len > strlen(".dmg" )) { |
613 | /* correctly terminated string with space for extension */ |
614 | } else { |
615 | AUTHPRNT("malformed root path" ); |
616 | err = EINVAL; |
617 | goto out; |
618 | } |
619 | |
620 | len = strlcpy(path, root_path, MAXPATHLEN); |
621 | if (len >= MAXPATHLEN) { |
622 | AUTHPRNT("root path is too long" ); |
623 | err = EINVAL; |
624 | goto out; |
625 | } |
626 | |
627 | path[len - strlen(".dmg" )] = '\0'; |
628 | len = strlcat(path, ".chunklist" , MAXPATHLEN); |
629 | if (len >= MAXPATHLEN) { |
630 | AUTHPRNT("chunklist path is too long" ); |
631 | err = EINVAL; |
632 | goto out; |
633 | } |
634 | |
635 | out: |
636 | if (err) { |
637 | kfree_safe(path); |
638 | } else { |
639 | *bufp = path; |
640 | } |
641 | return err; |
642 | } |
643 | |
644 | static int |
645 | authenticate_root(const char *root_path) |
646 | { |
647 | char *chunklist_path = NULL; |
648 | void *chunklist_buf = NULL; |
649 | size_t chunklist_len = 32*1024*1024UL; |
650 | int err = 0; |
651 | |
652 | err = construct_chunklist_path(root_path, &chunklist_path); |
653 | if (err) { |
654 | AUTHPRNT("failed creating chunklist path" ); |
655 | goto out; |
656 | } |
657 | |
658 | AUTHDBG("validating root against chunklist %s" , chunklist_path); |
659 | |
660 | /* |
661 | * Read and authenticate the chunklist, then validate the root image against |
662 | * the chunklist. |
663 | */ |
664 | |
665 | AUTHDBG("reading chunklist" ); |
666 | err = read_file(chunklist_path, &chunklist_buf, &chunklist_len); |
667 | if (err) { |
668 | AUTHPRNT("failed to read chunklist" ); |
669 | goto out; |
670 | } |
671 | |
672 | AUTHDBG("validating chunklist" ); |
673 | err = validate_chunklist(chunklist_buf, chunklist_len); |
674 | if (err < 0) { |
675 | AUTHDBG("missing or incorrect signature on chunklist" ); |
676 | goto out; |
677 | } else if (err) { |
678 | AUTHPRNT("failed to validate chunklist" ); |
679 | goto out; |
680 | } else { |
681 | AUTHDBG("successfully validated chunklist" ); |
682 | } |
683 | |
684 | AUTHDBG("validating root image against chunklist" ); |
685 | err = validate_root_image(root_path, chunklist_buf); |
686 | if (err) { |
687 | AUTHPRNT("failed to validate root image against chunklist (%d)" , err); |
688 | goto out; |
689 | } |
690 | |
691 | /* everything checked out - go ahead and mount this */ |
692 | AUTHDBG("root image authenticated" ); |
693 | |
694 | out: |
695 | kfree_safe(chunklist_buf); |
696 | kfree_safe(chunklist_path); |
697 | return err; |
698 | } |
699 | |
700 | static const uuid_t * |
701 | (const void *buf, size_t bufsz, size_t *uuidsz) |
702 | { |
703 | const struct uuid_command *cmd = NULL; |
704 | const kernel_mach_header_t *mh = buf; |
705 | |
706 | /* space for the header and at least one load command? */ |
707 | if (bufsz < sizeof(kernel_mach_header_t) + sizeof(struct uuid_command)) { |
708 | AUTHPRNT("libkern image too small" ); |
709 | return NULL; |
710 | } |
711 | |
712 | /* validate the mach header */ |
713 | if (mh->magic != MH_MAGIC_64 || (mh->sizeofcmds > bufsz - sizeof(kernel_mach_header_t))) { |
714 | AUTHPRNT("invalid MachO header" ); |
715 | return NULL; |
716 | } |
717 | |
718 | /* iterate the load commands */ |
719 | size_t offset = sizeof(kernel_mach_header_t); |
720 | for (size_t i = 0; i < mh->ncmds; i++) { |
721 | cmd = buf + offset; |
722 | |
723 | if (cmd->cmd == LC_UUID) { |
724 | *uuidsz = sizeof(cmd->uuid); |
725 | return &cmd->uuid; |
726 | } |
727 | |
728 | if (os_add_overflow(cmd->cmdsize, offset, &offset) || |
729 | offset > bufsz - sizeof(struct uuid_command)) { |
730 | return NULL; |
731 | } |
732 | } |
733 | |
734 | return NULL; |
735 | } |
736 | |
737 | static const char *libkern_path = "/System/Library/Extensions/System.kext/PlugIns/Libkern.kext/Libkern" ; |
738 | static const char *libkern_bundle = "com.apple.kpi.libkern" ; |
739 | |
740 | /* |
741 | * Check that the UUID of the libkern currently loaded matches the one on disk. |
742 | */ |
743 | static int |
744 | auth_version_check(void) |
745 | { |
746 | int err = 0; |
747 | void *buf = NULL; |
748 | size_t bufsz = 4*1024*1024UL; |
749 | |
750 | /* get the UUID of the libkern in /S/L/E */ |
751 | |
752 | err = read_file(libkern_path, &buf, &bufsz); |
753 | if (err) { |
754 | goto out; |
755 | } |
756 | |
757 | unsigned long uuidsz = 0; |
758 | const uuid_t *img_uuid = getuuidfromheader_safe(buf, bufsz, &uuidsz); |
759 | if (img_uuid == NULL || uuidsz != sizeof(uuid_t)) { |
760 | AUTHPRNT("invalid UUID (sz = %lu)" , uuidsz); |
761 | err = EINVAL; |
762 | goto out; |
763 | } |
764 | |
765 | /* Get the UUID of the loaded libkern */ |
766 | uuid_t live_uuid; |
767 | err = OSKextGetUUIDForName(libkern_bundle, live_uuid); |
768 | if (err) { |
769 | AUTHPRNT("could not find loaded libkern" ); |
770 | goto out; |
771 | } |
772 | |
773 | /* ... and compare them */ |
774 | if (bcmp(live_uuid, img_uuid, uuidsz) != 0) { |
775 | AUTHPRNT("UUID of running libkern does not match %s" , libkern_path); |
776 | |
777 | uuid_string_t img_uuid_str, live_uuid_str; |
778 | uuid_unparse(*img_uuid, img_uuid_str); |
779 | uuid_unparse(live_uuid, live_uuid_str); |
780 | AUTHPRNT("loaded libkern UUID = %s" , live_uuid_str); |
781 | AUTHPRNT("on-disk libkern UUID = %s" , img_uuid_str); |
782 | |
783 | err = EINVAL; |
784 | goto out; |
785 | } |
786 | |
787 | /* UUID matches! */ |
788 | |
789 | out: |
790 | kfree_safe(buf); |
791 | return err; |
792 | } |
793 | |
794 | #if 0 |
795 | int |
796 | auth_imgboot_test(proc_t __unused ap, struct auth_imgboot_test_args *uap, int32_t *retval) |
797 | { |
798 | int ret = 0; |
799 | int err; |
800 | char path[MAXPATHLEN]; |
801 | vm_size_t len; |
802 | *retval = 0; |
803 | |
804 | err = copyinstr(uap->path, path, MAXPATHLEN, &len); |
805 | if (err) { |
806 | return err; |
807 | } |
808 | if (len >= MAXPATHLEN) { |
809 | return ENAMETOOLONG; |
810 | } |
811 | |
812 | AUTHDBG("authenticating root image at %s" , path); |
813 | err = authenticate_root(path); |
814 | if (err) { |
815 | AUTHPRNT("root authentication FAIL (%d)" , err); |
816 | ret = err; |
817 | } else { |
818 | AUTHDBG("successfully authenticated %s" , path); |
819 | } |
820 | |
821 | AUTHDBG("checking root image version" ); |
822 | err = auth_version_check(); |
823 | if (err) { |
824 | AUTHPRNT("root image version check FAIL (%d)" , err); |
825 | err = err ?: ret; |
826 | } else { |
827 | AUTHPRNT("root version check success (%d)" , err); |
828 | } |
829 | |
830 | if (ret < 0) { |
831 | return EINVAL; /* negative return values have special meaning */ |
832 | } |
833 | return ret; |
834 | } |
835 | #endif |
836 | |
837 | /* |
838 | * Attach the image at 'path' as a ramdisk and mount it as our new rootfs. |
839 | * All existing mounts are first umounted. |
840 | */ |
841 | static int |
842 | imageboot_mount_ramdisk(const char *path) |
843 | { |
844 | int err = 0; |
845 | size_t bufsz = 0; |
846 | void *buf = NULL; |
847 | dev_t dev; |
848 | vnode_t newdp; |
849 | mount_t new_rootfs; |
850 | |
851 | /* Read our target image from disk */ |
852 | err = read_file(path, &buf, &bufsz); |
853 | if (err) { |
854 | printf("%s: failed: read_file() = %d\n" , __func__, err); |
855 | goto out; |
856 | } |
857 | DBG_TRACE("%s: read '%s' sz = %lu\n" , __func__, path, bufsz); |
858 | |
859 | #if CONFIG_IMGSRC_ACCESS |
860 | /* Re-add all root mounts to the mount list in the correct order... */ |
861 | mount_list_remove(rootvnode->v_mount); |
862 | for (int i = 0; i < MAX_IMAGEBOOT_NESTING; i++) { |
863 | struct vnode *vn = imgsrc_rootvnodes[i]; |
864 | if (vn) { |
865 | vnode_getalways(vn); |
866 | imgsrc_rootvnodes[i] = NULLVP; |
867 | |
868 | mount_t mnt = vn->v_mount; |
869 | mount_lock(mnt); |
870 | mnt->mnt_flag |= MNT_ROOTFS; |
871 | mount_list_add(mnt); |
872 | mount_unlock(mnt); |
873 | |
874 | vnode_rele(vn); |
875 | vnode_put(vn); |
876 | } |
877 | } |
878 | mount_list_add(rootvnode->v_mount); |
879 | #endif |
880 | |
881 | /* ... and unmount everything */ |
882 | vnode_get_and_drop_always(rootvnode); |
883 | filedesc0.fd_cdir = NULL; |
884 | rootvnode = NULL; |
885 | vfs_unmountall(); |
886 | |
887 | /* Attach the ramfs image ... */ |
888 | err = di_root_ramfile_buf(buf, bufsz, rootdevice, DEVMAXNAMESIZE, &dev); |
889 | if (err) { |
890 | printf("%s: failed: di_root_ramfile_buf() = %d\n" , __func__, err); |
891 | goto out; |
892 | } |
893 | |
894 | /* ... and mount it */ |
895 | rootdev = dev; |
896 | mountroot = NULL; |
897 | err = vfs_mountroot(); |
898 | if (err) { |
899 | printf("%s: failed: vfs_mountroot() = %d\n" , __func__, err); |
900 | goto out; |
901 | } |
902 | |
903 | /* Switch to new root vnode */ |
904 | if (VFS_ROOT(TAILQ_LAST(&mountlist,mntlist), &newdp, vfs_context_kernel())) { |
905 | panic("%s: cannot find root vnode" , __func__); |
906 | } |
907 | rootvnode = newdp; |
908 | rootvnode->v_flag |= VROOT; |
909 | new_rootfs = rootvnode->v_mount; |
910 | mount_lock(new_rootfs); |
911 | new_rootfs->mnt_flag |= MNT_ROOTFS; |
912 | mount_unlock(new_rootfs); |
913 | |
914 | vnode_ref(newdp); |
915 | vnode_put(newdp); |
916 | filedesc0.fd_cdir = newdp; |
917 | |
918 | DBG_TRACE("%s: root switched\n" , __func__); |
919 | |
920 | out: |
921 | if (err) { |
922 | kfree_safe(buf); |
923 | } |
924 | return err; |
925 | } |
926 | |
927 | static boolean_t |
928 | imageboot_setup_new() |
929 | { |
930 | int error; |
931 | char *root_path = NULL; |
932 | int height = 0; |
933 | boolean_t done = FALSE; |
934 | boolean_t auth_root = FALSE; |
935 | boolean_t ramdisk_root = FALSE; |
936 | |
937 | MALLOC_ZONE(root_path, caddr_t, MAXPATHLEN, M_NAMEI, M_WAITOK); |
938 | assert(root_path != NULL); |
939 | |
940 | unsigned imgboot_arg; |
941 | if (PE_parse_boot_argn("-rootdmg-ramdisk" , &imgboot_arg, sizeof(imgboot_arg))) { |
942 | ramdisk_root = TRUE; |
943 | } |
944 | |
945 | if (PE_parse_boot_argn(IMAGEBOOT_CONTAINER_ARG, root_path, MAXPATHLEN) == TRUE) { |
946 | printf("%s: container image url is %s\n" , __FUNCTION__, root_path); |
947 | error = imageboot_mount_image(root_path, height); |
948 | if (error != 0) { |
949 | panic("Failed to mount container image." ); |
950 | } |
951 | |
952 | height++; |
953 | } |
954 | |
955 | if (PE_parse_boot_argn(IMAGEBOOT_AUTHROOT_ARG, root_path, MAXPATHLEN) == TRUE) { |
956 | auth_root = TRUE; |
957 | } else if (PE_parse_boot_argn(IMAGEBOOT_ROOT_ARG, root_path, MAXPATHLEN) == FALSE) { |
958 | if (height > 0) { |
959 | panic("%s specified without %s?\n" , IMAGEBOOT_CONTAINER_ARG, IMAGEBOOT_ROOT_ARG); |
960 | } |
961 | goto out; |
962 | } |
963 | |
964 | printf("%s: root image url is %s\n" , __func__, root_path); |
965 | |
966 | #if CONFIG_CSR |
967 | if (auth_root && (csr_check(CSR_ALLOW_ANY_RECOVERY_OS) == 0)) { |
968 | AUTHPRNT("CSR_ALLOW_ANY_RECOVERY_OS set, skipping root image authentication" ); |
969 | auth_root = false; |
970 | } |
971 | #endif |
972 | |
973 | /* Make a copy of the path to URL-decode */ |
974 | char *path_alloc = kalloc(MAXPATHLEN); |
975 | if (path_alloc == NULL) { |
976 | panic("imageboot path allocation failed\n" ); |
977 | } |
978 | char *path = path_alloc; |
979 | |
980 | size_t len = strlen(kIBFilePrefix); |
981 | strlcpy(path, root_path, MAXPATHLEN); |
982 | if (strncmp(kIBFilePrefix, path, len) == 0) { |
983 | /* its a URL - remove the file:// prefix and percent-decode */ |
984 | path += len; |
985 | url_decode(path); |
986 | } |
987 | |
988 | if (auth_root) { |
989 | AUTHDBG("authenticating root image at %s" , path); |
990 | error = authenticate_root(path); |
991 | if (error) { |
992 | panic("root image authentication failed (err = %d)\n" , error); |
993 | } |
994 | AUTHDBG("successfully authenticated %s" , path); |
995 | } |
996 | |
997 | if (ramdisk_root) { |
998 | error = imageboot_mount_ramdisk(path); |
999 | } else { |
1000 | error = imageboot_mount_image(root_path, height); |
1001 | } |
1002 | |
1003 | kfree_safe(path_alloc); |
1004 | |
1005 | if (error) { |
1006 | panic("Failed to mount root image (err=%d, auth=%d, ramdisk=%d)\n" , |
1007 | error, auth_root, ramdisk_root); |
1008 | } |
1009 | |
1010 | if (auth_root) { |
1011 | /* check that the image version matches the running kernel */ |
1012 | AUTHDBG("checking root image version" ); |
1013 | error = auth_version_check(); |
1014 | if (error) { |
1015 | panic("root image version check failed" ); |
1016 | } else { |
1017 | AUTHDBG("root image version matches kernel" ); |
1018 | } |
1019 | } |
1020 | |
1021 | done = TRUE; |
1022 | |
1023 | out: |
1024 | FREE_ZONE(root_path, MAXPATHLEN, M_NAMEI); |
1025 | return done; |
1026 | } |
1027 | |
1028 | __private_extern__ void |
1029 | imageboot_setup() |
1030 | { |
1031 | int error = 0; |
1032 | char *root_path = NULL; |
1033 | |
1034 | DBG_TRACE("%s: entry\n" , __FUNCTION__); |
1035 | |
1036 | if (rootvnode == NULL) { |
1037 | panic("imageboot_setup: rootvnode is NULL." ); |
1038 | } |
1039 | |
1040 | /* |
1041 | * New boot-arg scheme: |
1042 | * root-dmg : the dmg that will be the root filesystem. |
1043 | * auth-root-dmg : same as root-dmg but with image authentication. |
1044 | * container-dmg : an optional dmg that contains the root-dmg. |
1045 | */ |
1046 | if (imageboot_setup_new()) { |
1047 | return; |
1048 | } |
1049 | |
1050 | MALLOC_ZONE(root_path, caddr_t, MAXPATHLEN, M_NAMEI, M_WAITOK); |
1051 | assert(root_path != NULL); |
1052 | |
1053 | /* |
1054 | * Look for outermost disk image to root from. If we're doing a nested boot, |
1055 | * there's some sense in which the outer image never needs to be the root filesystem, |
1056 | * but it does need very similar treatment: it must not be unmounted, needs a fake |
1057 | * device vnode created for it, and should not show up in getfsstat() until exposed |
1058 | * with MNT_IMGSRC. We just make it the temporary root. |
1059 | */ |
1060 | if((PE_parse_boot_argn("rp" , root_path, MAXPATHLEN) == FALSE) && |
1061 | (PE_parse_boot_argn("rp0" , root_path, MAXPATHLEN) == FALSE)) { |
1062 | panic("%s: no valid path to image.\n" , __FUNCTION__); |
1063 | } |
1064 | |
1065 | printf("%s: root image url is %s\n" , __FUNCTION__, root_path); |
1066 | |
1067 | error = imageboot_mount_image(root_path, 0); |
1068 | if (error) { |
1069 | panic("Failed on first stage of imageboot." ); |
1070 | } |
1071 | |
1072 | /* |
1073 | * See if we are rooting from a nested image |
1074 | */ |
1075 | if(PE_parse_boot_argn("rp1" , root_path, MAXPATHLEN) == FALSE) { |
1076 | goto done; |
1077 | } |
1078 | |
1079 | printf("%s: second level root image url is %s\n" , __FUNCTION__, root_path); |
1080 | |
1081 | /* |
1082 | * If we fail to set up second image, it's not a given that we |
1083 | * can safely root off the first. |
1084 | */ |
1085 | error = imageboot_mount_image(root_path, 1); |
1086 | if (error) { |
1087 | panic("Failed on second stage of imageboot." ); |
1088 | } |
1089 | |
1090 | done: |
1091 | FREE_ZONE(root_path, MAXPATHLEN, M_NAMEI); |
1092 | |
1093 | DBG_TRACE("%s: exit\n" , __FUNCTION__); |
1094 | |
1095 | return; |
1096 | } |
1097 | |