blob: b812fc15557037a30a4cdd2a2c2223082b35918c [file] [log] [blame]
Nick Sandersf11aca62011-01-28 17:39:55 -08001#!/bin/sh -x
Colin Choweb584db2010-07-08 16:02:56 -07002
3# Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6#
7# /init script for use in factory install shim. Requires busybox in
8# /bin/busybox, and a symlink from /bin/sh -> busybox.
9
Nick Sandersf11aca62011-01-28 17:39:55 -080010# USB card partition and mount point.
11USB_DEVS="sdb3 sdc3 mmcblk1p3"
12USB_SHIM_DEVS="sdb1 sdc1 mmcblk1p1"
13USB_MNT=/usb
14REAL_USB_DEV=
15DM_NAME=
16
17DST=
18
19STATEFUL_MNT=/stateful
20STATE_DEV=
21
22LOG_DEV=
23LOG_FILE="/log/recovery.log"
24
25TPM_B_LOCKED=
26TPM_PP_LOCKED=
27
28# Developer script to run
29SHIM_SCRIPT="$STATEFUL_MNT/userdir/runme"
30SHIM_VBLOCK="$STATEFUL_MNT/userdir/runme.vblock"
31
32KERN_B_VBLOCK="$STATEFUL_MNT/vmlinuz_hd.vblock"
33REAL_KERN_B_HASH=
34
35MOVE_MOUNTS="/sys /proc /dev"
36
37# To be updated to keep logging after move_mounts.
38TTY_PATH="/dev/tty"
39TAIL_PID=
40
41# Used to ensure the factory check only occurs with
42# a properly matched root and kernel.
43UNOFFICIAL_ROOT=0
44
Colin Choweb584db2010-07-08 16:02:56 -070045# Size of the root ramdisk.
46TMPFS_SIZE=300M
47
Nick Sandersf11aca62011-01-28 17:39:55 -080048on_error() {
49 # Exit, thus causing a kernel panic. We don't want to do anything else (like
50 # start a shell) because it would be trivially easy to get here (just unplug
51 # the USB drive after the kernel starts but before the USB drives are probed
52 # by the kernel) and starting a shell here would be a BIG security hole.
53 log
54 log
55 log "An unrecoverable error occurred during recovery!"
56 log
57 log "Please try again or try a newer recovery image."
58 save_log_file
59 sleep 1d
60 exit 1
61}
62
Colin Choweb584db2010-07-08 16:02:56 -070063initial_mounts() {
Nick Sandersf11aca62011-01-28 17:39:55 -080064 mkdir -p /var/lock
65 mount -t proc -o nodev,noexec,nosuid none /proc
66 mount -t sysfs -o nodev,noexec,nosuid none /sys
67 if ! mount -t devtmpfs -o mode=0755 none /dev; then
68 mount -t tmpfs -o mode=0755 none /dev
69 mknod -m 0600 /dev/console c 5 1
70 mknod -m 0601 /dev/tty1 c 4 1
71 mknod -m 0601 /dev/tty2 c 4 2
72 mknod -m 0601 /dev/tty3 c 4 3
73 mknod -m 0600 /dev/tpm0 c 10 224
74 mknod /dev/null c 1 3
75 fi
76 mkdir -p /dev/pts
77 mount -t devpts -o noexec,nosuid none /dev/pts || true
Colin Choweb584db2010-07-08 16:02:56 -070078}
79
Nick Sandersf11aca62011-01-28 17:39:55 -080080# Look for a device with our GPT ID.
81wait_for_gpt_root() {
82 [ -z "$KERN_ARG_KERN_GUID" ] && return 1
83 dlog -n "Looking for rootfs using kern_guid..."
84 for try in $(seq 20); do
85 plog " ."
86 kern=$(cgpt find -1 -u $KERN_ARG_KERN_GUID)
87 # We always try ROOT-A in recovery.
88 newroot="${kern%[0-9]*}3"
89 if [ -b "$newroot" ]; then
90 USB_DEV="$newroot"
91 dlog "Found $USB_DEV"
92 return 0
93 fi
94 sleep 1
95 done
96 dlog "Failed waiting for kern_guid"
97 return 1
Colin Choweb584db2010-07-08 16:02:56 -070098}
99
Nick Sandersf11aca62011-01-28 17:39:55 -0800100# Look for any USB device.
Colin Choweb584db2010-07-08 16:02:56 -0700101wait_for_root() {
Nick Sandersf11aca62011-01-28 17:39:55 -0800102 dlog -n "Waiting for $USB_DEVS to appear"
Colin Choweb584db2010-07-08 16:02:56 -0700103 for try in $(seq 20); do
Nick Sandersf11aca62011-01-28 17:39:55 -0800104 plog " ."
105 for dev in $USB_DEVS; do
106 if [ -b "/dev/${dev}" ]; then
107 USB_DEV="/dev/${dev}"
108 dlog "Found $USB_DEV"
109 return 0
110 fi
111 done
Colin Choweb584db2010-07-08 16:02:56 -0700112 sleep 1
113 done
Nick Sandersf11aca62011-01-28 17:39:55 -0800114 dlog "Failed waiting for root!"
Colin Choweb584db2010-07-08 16:02:56 -0700115 return 1
116}
117
Nick Sandersf11aca62011-01-28 17:39:55 -0800118# Wait for dm-0 to come up.
119wait_for_dm_control() {
120 MAPPER_CONTROL=/dev/mapper/control
121 dlog -n "Waiting for $MAPPER_CONTROL to appear"
Colin Choweb584db2010-07-08 16:02:56 -0700122 for try in $(seq 20); do
Nick Sandersf11aca62011-01-28 17:39:55 -0800123 plog " ."
124 if [ -c "$MAPPER_CONTROL" ]; then
Colin Choweb584db2010-07-08 16:02:56 -0700125 return 0
126 fi
127 sleep 1
128 done
Nick Sandersf11aca62011-01-28 17:39:55 -0800129 dlog "Failed waiting for $MAPPER_CONTROL!"
Colin Choweb584db2010-07-08 16:02:56 -0700130 return 1
131}
132
Nick Sandersf11aca62011-01-28 17:39:55 -0800133check_if_dm_root() {
134 [ "$KERN_ARG_ROOT" = "/dev/dm-0" ]
135}
136
137# Attempt to find the root defined in the signed recovery
138# kernel we're booted into to. Exports REAL_USB_DEV if there
139# is a root partition that may be used - on succes or failure.
140find_official_root() {
141 plog "Checking for an official recovery image . . ."
142
143 # Check for a kernel selected root device or one in a well known location.
144 wait_for_gpt_root || wait_for_root || return 1
145
146 # Now see if it has a Chrome OS rootfs partition.
147 cgpt find -t rootfs "$(strip_partition "$USB_DEV")" || return 1
148 REAL_USB_DEV="$USB_DEV"
149
150 LOG_DEV="$(strip_partition "$USB_DEV")"1 # Default to stateful.
151
152 # Now see if the root should be integrity checked.
153 if check_if_dm_root; then
154 setup_dm_root || return 1
155 fi
156
157 mount_usb || return 1
158 return 0
159}
160
161find_developer_root() {
162 is_developer_mode || return 1
163 # Lock the TPM prior to using an untrusted root.
164 lock_tpm || return 1
165 plog "\nSearching for developer root . . ."
166 # If an official root could not be mounted, free up the underlying device
167 # if it is claimed by verity.
168 dmsetup remove "$DM_NAME"
169
170 # If we found a valid rootfs earlier, then we're done.
171 USB_DEV="$REAL_USB_DEV"
172 [ -z "$USB_DEV" ] && return 1
173 set_unofficial_root || return 1
174 mount_usb || return 1
175 return 0
176}
177
178# If this kernel image has been placed on a drive with only a
179# stateful partition, root detection will rightly fail. However,
180# we can still run a developer supplied script so we will pretend
181# stateful is the root (USB_DEV).
182find_shim_root() {
183 # Lock the TPM prior to using an untrusted root.
184 lock_tpm || return 1
185 plog "\nSearching for an alternate recovery image . . ."
186 dlog -n "Waiting for $USB_SHIM_DEVS to appear"
187 for try in $(seq 20); do
188 plog " ."
189 for dev in $USB_SHIM_DEVS; do
190 if [ -b "/dev/${dev}" ]; then
191 USB_DEV="/dev/${dev}"
192 REAL_USB_DEV="$USB_DEV"
193 dlog "Found $USB_DEV"
194 set_unofficial_root || on_error
195 mount_usb || return 1
196 return 0
197 fi
198 done
199 sleep 1
200 done
201 return 1
202}
203
204# If we have a verified recovery root, ensure all blocks are valid before
205# handing it off to the installer.
206validate_recovery_root() {
207 # Allow test recovery roots that are unverified.
208 [ "$USB_DEV" = "/dev/dm-0" ] || return 0
209 is_unofficial_root && return 0
210
211 plog "Validating official recovery image . . . "
212 # Ensure the verified rootfs is fully intact or fail with no USB_DEV.
213 # REAL_USB_DEV is left intact.
214 # Correctness wins over speed for this.
215 if ! dd if="$USB_DEV" of=/dev/null bs=$((16 * 1024 * 1024)); then
216 dlog "Included root filesystem could not be verified."
217 log " failed!"
218 dmsetup remove "$DM_NAME" # Free up the real root for use.
219 USB_DEV=
220 return 1
221 fi
222 log " completed."
223 return 0
224}
225
226setup_dm_root() {
227 dlog -n "Extracting the device mapper configuration..."
228 # export_args can't handle dm="..." at present.
229 DMARG=$(cat /proc/cmdline | sed -e 's/.*dm="\([^"\]*\)".*/\1/g')
230 DM_NAME=$(echo "$DMARG" | cut -f1 -d' ')
231 # We override the reboot-to-recovery error behavior so that we can fail
232 # gracefully on invalid rootfs.
233 DM_TABLE="$(echo "$DMARG" | cut -f2 -d,) eio"
234
235 # Don't attempt to call dmsetup if the root device isn't one that was
236 # discovered as the creation process will hang.
Will Drewrye7bc1dc2011-02-28 16:28:34 -0600237 # TODO(wad) extract the UUID and use it with cgpt find instead.
238 if [ -n "$KERN_ARG_KERN_GUID" ]; then
239 [ "${DM_TABLE%$KERN_ARG_KERN_GUID*}" = "${DM_TABLE}" ] && return 1
240 elif [ -n "${USB_DEV}" ]; then
Nick Sandersf11aca62011-01-28 17:39:55 -0800241 [ "${DM_TABLE%$USB_DEV*}" = "${DM_TABLE}" ] && return 1
242 fi
243
244 if ! dmsetup create -r "$DM_NAME" --major 254 --minor 0 --table "$DM_TABLE"
245 then
246 dlog "Failed to configure device mapper root"
247 return 1
248 fi
249 USB_DEV="/dev/dm-0"
250 if [ ! -b "$USB_DEV" ]; then
251 mknod -m 0600 "$USB_DEV" b 254 0
252 fi
253 dlog "Created device mapper root $DM_NAME."
254 return 0
255}
256
257mount_usb() {
258 dlog -n "Mounting usb"
259 for try in $(seq 20); do
260 plog " ."
261 if mount -n -o ro "$USB_DEV" "$USB_MNT"; then
262 dlog "ok"
263 return 0
264 fi
265 sleep 1
266 done
267 dlog "Failed to mount usb!"
268 return 1
269}
270
271check_if_factory_install() {
272 if is_unofficial_root; then
273 dlog "Skipping factory install check."
274 return 1
275 fi
276
277 if [ -e "${USB_MNT}/root/.factory_installer" ]; then
278 log "Detected factory install."
279 return 0
280 fi
281 return 1
282}
283
284get_stateful_dev() {
285 STATE_DEV=${REAL_USB_DEV%[0-9]*}1
286 if [ ! -b "$STATE_DEV" ]; then
287 dlog "Failed to determine stateful device"
288 return 1
289 fi
290 return 0
291}
292
Colin Choweb584db2010-07-08 16:02:56 -0700293mount_tmpfs() {
Nick Sandersf11aca62011-01-28 17:39:55 -0800294 dlog "Mounting tmpfs..."
295 mount -n -t tmpfs tmpfs "$NEWROOT_MNT" -o "size=$TMPFS_SIZE"
Colin Choweb584db2010-07-08 16:02:56 -0700296 return $?
297}
298
299copy_contents() {
Nick Sandersf11aca62011-01-28 17:39:55 -0800300 log "Copying usb->tmpfs..."
301 (cd "${USB_MNT}" ; tar cf - . | (cd "${NEWROOT_MNT}" && tar xf -))
302 RES=$?
303 log "Copy returned with result $RES"
304 return $RES
305}
306
307copy_lsb() {
308 STATEFUL_LSB="dev_image/etc/lsb-factory"
309 mkdir -p "${NEWROOT_MNT}/mnt/stateful_partition/dev_image/etc"
310 # Mounting ext3 as ext2 since the journal is unneeded in ro.
Nick Sanders4fc3f462011-03-04 21:31:25 -0800311 if ! mount -n "${STATE_DEV}" "${STATEFUL_MNT}"; then
Nick Sandersee7e6ae2011-03-03 11:21:43 -0800312 log "Failed to mount ${STATE_DEV}!! Failing."
313 return 1
314 fi
Nick Sandersf11aca62011-01-28 17:39:55 -0800315 if [ -f "${STATEFUL_MNT}/${STATEFUL_LSB}" ]; then
316 log "Found ${STATEFUL_MNT}/${STATEFUL_LSB}"
317 cp -a "${STATEFUL_MNT}/${STATEFUL_LSB}" \
318 "${NEWROOT_MNT}/mnt/stateful_partition/${STATEFUL_LSB}"
Nick Sandersee7e6ae2011-03-03 11:21:43 -0800319 else
320 log "Failed to find ${STATEFUL_MNT}/${STATEFUL_LSB}!! Failing."
321 umount "$STATEFUL_MNT"
322 return 1
Nick Sandersf11aca62011-01-28 17:39:55 -0800323 fi
324 umount "$STATEFUL_MNT"
325 rmdir "$STATEFUL_MNT"
Colin Choweb584db2010-07-08 16:02:56 -0700326}
327
328move_mounts() {
Nick Sandersf11aca62011-01-28 17:39:55 -0800329 dlog "Moving sys. proc, dev to $NEWROOT_MNT"
330 for mnt in $MOVE_MOUNTS; do
331 mkdir -p "$NEWROOT_MNT$mnt"
332 mount -n -o move "$mnt" "$NEWROOT_MNT$mnt"
Colin Choweb584db2010-07-08 16:02:56 -0700333 done
Nick Sandersf11aca62011-01-28 17:39:55 -0800334 TTY_PATH="$NEWROOT_MNT/dev/tty"
335 dlog "Done."
Colin Choweb584db2010-07-08 16:02:56 -0700336 return 0
337}
338
Nick Sandersf11aca62011-01-28 17:39:55 -0800339unmove_mounts() {
340 dlog "Moving sys. proc, dev to $NEWROOT_MNT"
341 for mnt in $MOVE_MOUNTS; do
342 mount -n -o move "$NEWROOT_MNT$mnt" "$mnt"
343 done
344 TTY_PATH="/dev/tty"
345 dlog "Done."
346 return 0
Colin Choweb584db2010-07-08 16:02:56 -0700347}
348
Nick Sandersf11aca62011-01-28 17:39:55 -0800349unmount_usb() {
350 dlog "Unmounting $USB_MNT"
351 umount "$USB_MNT"
352 # Make sure we clean up a device-mapper root.
353 if [ "$USB_DEV" = "/dev/dm-0" ]; then
354 dlog "Removing dm-verity target"
355 dmsetup remove "$DM_NAME"
356 fi
357 dlog
358 dlog "$REAL_USB_DEV can now be safely removed"
359 dlog
360 return 0
361}
362
363strip_partition() {
364 local dev="${1%[0-9]*}"
365 # handle mmcblk0p case as well
366 echo "${dev%p*}"
367}
368
369# Accomodate sd* or mmcblk* devices
370get_dst() {
371 DST=$(echo ${REAL_USB_DEV%[0-9]*} | \
372 tr -s '[0-9]' '0' | \
373 sed -e 's/sd[b-z]/sda/g')
374}
375
376dev_wait_or_error() {
377 is_developer_mode || on_error # terminal if we get here in regular mode.
378 log ""
379 log "A developer key change is required to proceed."
380 plog "Please wait 300 seconds . . ."
381 # TODO(wad) Divvy the total up into a few different prompts.
382 make_user_wait 300
383 log ""
384}
385
386recovery_wait() {
387 log ""
388 log "Preparing to recover your system image."
389 plog "If you do not wish to proceed, please reboot in the next 10 seconds ."
390 make_user_wait 10
391 log ""
392 log "System recovery is beginning."
393 log "Please do not disconnect from a power source or power down."
394 log ""
395}
396
397make_user_wait() {
398 local delay_in_sec="${1-300}"
399 while [ $delay_in_sec -gt 0 ]; do
400 plog " ."
401 sleep 1
402 delay_in_sec=$((delay_in_sec - 1))
403 done
404 log ""
405}
406
407check_key_or_wait() {
408 plog "Searching the system disk for a matching kernel key . . ."
409 if ! cgpt find -t kernel -M "$1" "$DST"; then
410 log " failed."
411 dev_wait_or_error
412 return 0
413 fi
414 log " found."
415
416 plog "Validating matching signature(s) . . ."
417 # If we found a keyblock, at the right offset, make sure it actually signed
418 # the subsequent payload.
419 local kdev=
420 for kdev in $(cgpt find -t kernel -M "$1" "$DST"); do
421 plog " ."
422 verify_kernel_signature "$kdev" "/tmp/kern.keyblock" || continue
423 log " done."
424 return 0
425 done
426
427 log " failed."
428 dev_wait_or_error
429 return 0
430}
431
432# Never returns on success.
433attempt_shim_script() {
434 # TODO(wad) Add static root of trust validation then remove the next line.
435 # http://crosbug/8390
436 is_developer_mode || return 1
437
438 # Now we will either install a colocated Chromium OS image by
439 # checking the keys on KERN-B against any on disk (KERN-[ABC])
440 # or by checking a signed script on stateful.
441 dlog "Checking for a shim script . . ."
442 [ -x "$SHIM_SCRIPT" ] || return 1
443 [ -f "$SHIM_VBLOCK" ] || return 1
444 log "Shim script and signing file found!"
445
446 plog "Verifying the signature on the script . . ."
447 # Extract pubkey and check signature
448 if ! dev_sign_file --verify "$SHIM_SCRIPT" \
449 --vblock "$SHIM_VBLOCK" \
450 --keyblock /tmp/shim.keyblock; then
451 log " failed."
452 fi
453 log " done."
454
455 # If we're not in developer mode, this will be terminal on failure.
456 check_key_or_wait /tmp/shim.keyblock
457
458 # Run the user supplied script. It is done in the current environment
459 # to avoid needing anything other than the script/program on the partition.
460 log "Executing shim script . . ."
461
462 dlog "calling $SHIM_SCRIPT with exec"
463 # Fix up the input/output
464 stop_log_file
465 set +x
466 exec &> "$TTY_PATH"1
467 exec < "$TTY_PATH"1
468 # Call the script!
469 exec "$SHIM_SCRIPT"
470
471 # Never reached.
472 save_log_file
473 return 0
474}
475
476get_kern_b_device() {
477 # TODO(wad) By changing boot priority, could we end up
478 # checking the recovery image or the recovery image could not
479 # be in slot A. In that case, it should fail in normal mode.
480 KERN_B_DEV=${REAL_USB_DEV%[0-9]*}4
481 if [ ! -b "${KERN_B_DEV}" ]; then
482 return 1
483 fi
484 return 0
485}
486
487get_real_kern_b_hash() {
488 REAL_KERN_B_HASH=$(dd if="${KERN_B_DEV}" | \
489 sha1sum | \
490 cut -f1 -d' ')
491 [ -n "$REAL_KERN_B_HASH" ]
492}
493
494verify_kernel_signature() {
495 local kern_dev="$1"
496 local keyblock="$2"
497
498 if ! dd if="$kern_dev" of="/tmp/kern.bin"; then
499 return 1
500 fi
501
502 # Validates the signature and outputs a keyblock.
503 if ! vbutil_kernel --verify "/tmp/kern.bin" \
504 --keyblock "$keyblock"; then
505 return 1
506 fi
507 return 0
508}
509
510verify_install_kernel() {
511 get_kern_b_device || return 1
512 get_real_kern_b_hash || return 1
513
514 # TODO(wad) check signatures from stateful on kern b using the
515 # root of trust instead of using a baked in cmdline.
516 if [ "$REAL_KERN_B_HASH" != "$KERN_ARG_KERN_B_HASH" ]; then
517 if ! is_developer_mode; then
518 log "The recovery image cannot be verified."
519 return 1
520 fi
521
522 # Extract the kernel so that vbutil_kernel will happily consume it.
523 log "Checking the install kernel for a valid developer signature . . ."
524 verify_kernel_signature "$KERN_B_DEV" "/tmp/kern_b.keyblock" || return 1
525 check_key_or_wait /tmp/kern_b.keyblock
526 fi
527 return 0
528}
529
530touch_developer_mode_file() {
531 is_developer_mode || return 1
532 mount -n -o rw -t ext3 "$DST"1 "$STATEFUL_MNT" || return 1
533 touch "$STATEFUL_MNT/.developer_mode" || return 1
534 umount "$STATEFUL_MNT" || return 1
535 return 0
536}
537
538call_image_recovery_script() {
539 mount -t tmpfs -o mode=1777 none "$USB_MNT/tmp" || return 1
540 move_mounts || return 1
541
542 # Start the copy.
543 log ""
544 log "Beginning system image copy. This will take some time . . ."
545 log ""
546 # Until images are built with the installer keyblock in KERN-B by
547 # default, we keep copying over the installer vblock from the
548 # stateful partition.
549 # TODO(wad) http://crosbug/8378
550 export IS_RECOVERY_INSTALL=1
551 chroot "$USB_MNT" \
552 /usr/sbin/chromeos-install --run_as_root --yes \
553 --payload_image="$1" \
554 --use_payload_kern_b
555 if [ $? -eq 0 ]; then
556 log "System copy completed."
557 else
558 log "Error performing system recovery!"
559 fi
560
561 # Clean up doesn't need to be successful.
562 umount "$USB_MNT/tmp"
563 unmove_mounts
564
565 # If we're in developer mode, touch the .developer_mode file on stateful
566 # to avoid a bonus wait period on reboot.
567 # Failure here is non-terminal and it may not succeed just because the
568 # partition table of the destination has not been synchronized.
569 dlog "Prepping destination stateful to avoid a secondary delay."
570 touch_developer_mode_file && log "Prepped image for developer use."
571
572 return 0
573}
574
575clear_tpm() {
576 plog "Resetting security device . . ."
577 # TODO(wad) should we fail on this?
578 tpmc ppon || dlog "tpmc ppon error: $?"
579 tpmc clear || dlog "tpmc clear error: $?"
580 tpmc enable || dlog "tpmc enable error: $?"
581 tpmc activate || dlog "tpmc activate error: $?"
582 tpmc pplock || dlog "tpmc pplock error: $?"
583 log " done."
584 return 0
585}
586
587save_log_file() {
588 local log_dev="${1:-$LOG_DEV}"
589 [ -z "$log_dev" ] && return 0
590 # The recovery stateful is usually too small for ext3.
591 # TODO(wad) We could also just write the data raw if needed.
592 # Should this also try to save
593 dlog "Attempting to save log file: $LOG_FILE -> $log_dev"
594 mount -n -o sync,rw -t ext2 "$log_dev" /tmp
595 cp "$LOG_FILE" /tmp
596 umount -n /tmp
597}
598
599stop_log_file() {
600 # Drop logging
601 exec &> "$TTY_PATH"3
602 [ -n "$TAIL_PID" ] && kill $TAIL_PID
603}
604
605is_unofficial_root() {
606 [ $UNOFFICIAL_ROOT -eq 1 ]
607}
608
609set_unofficial_root() {
610 UNOFFICIAL_ROOT=1
611 return 0
612}
613
614recover_system() {
615 local source=$(strip_partition "$REAL_USB_DEV")
616 dlog "Beginning system recovery from $source"
617
618 recovery_wait
619
620 if is_unofficial_root; then
621 dlog "Attempting to use shim . . ."
622 # Mounting read only so a journal is not needed.
623 # If it fails, we can still proceed on a normal recovery path.
624 mount -n -o ro -t ext2 "$STATE_DEV" "$STATEFUL_MNT"
625 attempt_shim_script # never returns on success.
626 umount "$STATEFUL_MNT"
627 fi
628
629 # If we're not running a developer script then we're either
630 # installing a developer image or an official one. If we're
631 # in normal recovery mode, then we require that the KERN-B
632 # on the recovery image matches the hash on the command line.
633 # In developer mode, we will just check the keys.
634 verify_install_kernel || return 1
635
636 # Only clear on full installs. Shim scripts can call tpmc if they
637 # like. Only bGlobalLock will be in place in advance.
638 clear_tpm || return 1
639
640 call_image_recovery_script "$source" || return 1
641
642 return 0
643}
644
645use_new_root() {
646 move_mounts || on_error
647
648 # Chroot into newroot, erase the contents of the old /, and exec real init.
649 log "About to switch root"
650 stop_log_file
651 exec switch_root -c /dev/console "$NEWROOT_MNT" /sbin/init
652
653 # This should not really happen.
654 log "Failed to switch root."
655 save_log_file
656 return 1
657}
658
659is_developer_mode() {
660 # See Firmware High-Level Spec for details on CHSW values
661 CHSW=$(cat /sys/devices/platform/chromeos_acpi/CHSW)
662 # If the switch is unsupported, treat as developer mode.
663 [ -z "$CHSW" ] && return 0
664 if [ $CHSW -gt 0 -a $((CHSW & 32)) -eq 32 ]; then
665 return 0
666 fi
667 return 1
668}
669
670lock_tpm() {
671 if [ -z "$TPM_B_LOCKED" ]; then
672 # Depending on the system, the tpm may need to be started.
673 # Don't fail if it doesn't work though.
674 tpmc startup
675 tpmc ctest
676 if ! tpmc block; then
677 log "An unrecoverable error occurred with your security device"
678 log "Please power down and try again."
679 dlog "Failed to lock bGlobalLock."
680 on_error
681 return 1 # Never reached.
682 fi
683 TPM_B_LOCKED=y
684 fi
685 if [ -z "$TPM_PP_LOCKED" ]; then
686 # TODO: tpmc pplock if appropriate
687 TPM_PP_LOCKED=y
688 fi
689 return 0
690}
691
692# Extract and export kernel arguments
693export_args() {
694 # We trust out kernel command line explicitly.
695 local arg=
696 local key=
697 local val=
698 local acceptable_set='[A-Za-z0-9]_'
699 for arg in "$@"; do
700 key=$(echo "${arg%=*}" | tr 'a-z' 'A-Z' | \
701 tr -dc "$acceptable_set" '_')
702 val="${arg#*=}"
703 export "KERN_ARG_$key"="$val"
704 dlog "Exporting kernel argument $key as KERN_ARG_$key"
705 done
706}
707
708dlog() {
709 echo "$@" | tee -a "$TTY_PATH"2 "$TTY_PATH"3
710}
711
712# User visible
713log() {
714 echo "$@" | tee -a "$TTY_PATH"1 "$TTY_PATH"2
715}
716
717plog() {
718 # plog doesn't go to /dev/tty3 or log file.
719 printf "$@" | tee "$TTY_PATH"1 "$TTY_PATH"2
720}
721
722main() {
723 exec &> "$LOG_FILE"
724
725 # Set up basic mounts, console.
726 initial_mounts
727
728 # Send all verbose output to tty3
729 (tail -f "$LOG_FILE" > "$TTY_PATH"3) &
730 TAIL_PID=$!
731
732 log "Recovery image booting . . ."
733 log ""
734 log "Press Ctrl + Alt + F1 - F3 for more detailed information."
735 log ""
736
737 # Export the kernel command line as a parsed blob prepending KERN_ARG_ to each
738 # argument.
739 export_args $(cat /proc/cmdline | sed -e 's/"[^"]*"/DROPPED/g')
740
741 if is_developer_mode; then
742 log "! Your computer's developer mode switch is in the ENABLED position."
743 log "!"
744 log "! If this is unintentional, you should power off and toggle it back "
745 log "! after recovery is completed."
746 log ""
747 fi
748
749 if find_official_root || find_developer_root || find_shim_root; then
750 log " found."
751 else
752 log " not found."
753 on_error
754 fi
755
756 # Extract the real boot source which may be masked by dm-verity.
757 get_stateful_dev || on_error
758
759 # Check if we want to run from RAM, in the factory.
760 if check_if_factory_install; then
761 is_developer_mode || on_error # factory install requires it.
762 # Copy rootfs contents to tmpfs, then unmount USB device.
763 NEWROOT_MNT=/newroot
764 mount_tmpfs || on_error
765 copy_contents || on_error
766 copy_lsb || on_error
767 # USB device is unmounted, we can remove it now.
768 unmount_usb || on_error
769 # Switch to the new root
770 use_new_root || on_error
771 on_error # !! Never reached. !!
772 fi
773
774 # If not, we must be a recovery kernel.
775 NEWROOT_MNT="$USB_MNT"
776
777 # Always lock the TPM. If a NVRAM reset is ever needed, we can change it.
778 lock_tpm || on_error
779
780 # Perform a full device mapper root validation to avoid any unexpected
781 # failures during postinst. It also allows us to detect if the root is
782 # intentionally mismatched - such as during Chromium OS recovery with a
783 # Chrome OS recovery kernel.
784 if ! validate_recovery_root; then
785 is_developer_mode || on_error
786 find_developer_root || find_shim_root || on_error
787 log " found."
788 # This logic is duplicated to avoid double validating factory media. It
789 # will only be hit if a verified root can be mounted but is actually not
790 # intact.
791 get_stateful_dev || on_error
792 fi
793
794 get_dst || on_error
795
796 recover_system || on_error
797
798 log "System recovery is complete!"
799 log "Please remove the recovery device and reboot."
800
801 stop_log_file
802 # Save the recovery log to the target on success and the USB.
803 save_log_file "$DST"1
804 save_log_file
805
806 unmount_usb
807
808 log ""
809 log ""
810 plog "The system will automatically reboot in 2 minutes"
811 make_user_wait 120
812 reboot -f
813 exit 0
814}
815
816# Make this source-able for testing.
817if [ "$0" = "/init" ]; then
818 main "$@"
819 # Should never reach here.
820 exit 1
821fi
822