| Index: chromeos-base/chromeos-initramfs/files/init
|
| diff --git a/chromeos-base/chromeos-initramfs/files/init b/chromeos-base/chromeos-initramfs/files/init
|
| index 3edc0b6840a6a658784e82109a40a50006bddc8d..a1aabd36e905c0023daf66b792876ca88fc0aa3d 100644
|
| --- a/chromeos-base/chromeos-initramfs/files/init
|
| +++ b/chromeos-base/chromeos-initramfs/files/init
|
| @@ -9,12 +9,38 @@
|
|
|
| # USB card partition and mount point.
|
| USB_DEVS="sdb3 sdc3 mmcblk1p3"
|
| +USB_SHIM_DEVS="sdb1 sdc1 mmcblk1p1"
|
| USB_MNT=/usb
|
| REAL_USB_DEV=
|
|
|
| +DST=
|
| +
|
| STATEFUL_MNT=/stateful
|
| STATE_DEV=
|
|
|
| +LOG_DEV=
|
| +LOG_FILE="/log/recovery.log"
|
| +
|
| +TPM_B_LOCKED=
|
| +TPM_PP_LOCKED=
|
| +
|
| +# Developer script to run
|
| +SHIM_SCRIPT="$STATEFUL_MNT/userdir/runme"
|
| +SHIM_VBLOCK="$STATEFUL_MNT/userdir/runme.vblock"
|
| +
|
| +KERN_B_VBLOCK="$STATEFUL_MNT/vmlinuz_hd.vblock"
|
| +REAL_KERN_B_HASH=
|
| +
|
| +MOVE_MOUNTS="/sys /proc /dev"
|
| +
|
| +# To be updated to keep logging after move_mounts.
|
| +TTY_PATH="/dev/tty"
|
| +TAIL_PID=
|
| +
|
| +# Used to ensure the factory check only occurs with
|
| +# a properly matched root and kernel.
|
| +UNOFFICIAL_ROOT=0
|
| +
|
| # Size of the root ramdisk.
|
| TMPFS_SIZE=300M
|
|
|
| @@ -23,6 +49,13 @@ on_error() {
|
| # start a shell) because it would be trivially easy to get here (just unplug
|
| # the USB drive after the kernel starts but before the USB drives are probed
|
| # by the kernel) and starting a shell here would be a BIG security hole.
|
| + log
|
| + log
|
| + log "An unrecoverable error occurred during recovery!"
|
| + log
|
| + log "Please try again or try a newer recovery image."
|
| + save_log_file
|
| + sleep 1d
|
| exit 1
|
| }
|
|
|
| @@ -33,141 +66,248 @@ initial_mounts() {
|
| if ! mount -t devtmpfs -o mode=0755 none /dev; then
|
| mount -t tmpfs -o mode=0755 none /dev
|
| mknod -m 0600 /dev/console c 5 1
|
| - mknod -m 0600 /dev/tty1 c 4 1
|
| + mknod -m 0601 /dev/tty1 c 4 1
|
| + mknod -m 0601 /dev/tty2 c 4 2
|
| + mknod -m 0601 /dev/tty3 c 4 3
|
| + mknod -m 0600 /dev/tpm0 c 10 224
|
| mknod /dev/null c 1 3
|
| fi
|
| - mkdir /dev/pts
|
| + mkdir -p /dev/pts
|
| mount -t devpts -o noexec,nosuid none /dev/pts || true
|
| }
|
|
|
| +# Look for a device with our GPT ID.
|
| +wait_for_gpt_root() {
|
| + [ -z "$KERN_ARG_KERN_GUID" ] && return 1
|
| + dlog -n "Looking for rootfs using kern_guid..."
|
| + for try in $(seq 20); do
|
| + plog " ."
|
| + kern=$(cgpt find -1 -u $KERN_ARG_KERN_GUID)
|
| + # We always try ROOT-A in recovery.
|
| + newroot="${kern%[0-9]*}3"
|
| + if [ -b "$newroot" ]; then
|
| + USB_DEV="$newroot"
|
| + dlog "Found $USB_DEV"
|
| + return 0
|
| + fi
|
| + sleep 1
|
| + done
|
| + dlog "Failed waiting for kern_guid"
|
| + return 1
|
| +}
|
| +
|
| # Look for any USB device.
|
| wait_for_root() {
|
| - echo -n "Waiting for $USB_DEVS to appear"
|
| + dlog -n "Waiting for $USB_DEVS to appear"
|
| for try in $(seq 20); do
|
| - echo -n "."
|
| + plog " ."
|
| for dev in $USB_DEVS; do
|
| if [ -b "/dev/${dev}" ]; then
|
| USB_DEV="/dev/${dev}"
|
| - echo "Found $USB_DEV"
|
| + dlog "Found $USB_DEV"
|
| return 0
|
| fi
|
| done
|
| sleep 1
|
| done
|
| - echo "Failed waiting for root!"
|
| + dlog "Failed waiting for root!"
|
| return 1
|
| }
|
|
|
| # Wait for dm-0 to come up.
|
| wait_for_dm_control() {
|
| MAPPER_CONTROL=/dev/mapper/control
|
| - echo -n "Waiting for $MAPPER_CONTROL to appear"
|
| + dlog -n "Waiting for $MAPPER_CONTROL to appear"
|
| for try in $(seq 20); do
|
| - echo -n "."
|
| + plog " ."
|
| if [ -c "$MAPPER_CONTROL" ]; then
|
| return 0
|
| fi
|
| sleep 1
|
| done
|
| - echo "Failed waiting for $MAPPER_CONTROL!"
|
| + dlog "Failed waiting for $MAPPER_CONTROL!"
|
| + return 1
|
| +}
|
| +
|
| +check_if_dm_root() {
|
| + [ "$KERN_ARG_ROOT" = "/dev/dm-0" ]
|
| +}
|
| +
|
| +# Attempt to find the root defined in the signed recovery
|
| +# kernel we're booted into to. Exports REAL_USB_DEV if there
|
| +# is a root partition that may be used - on succes or failure.
|
| +find_official_root() {
|
| + plog "Checking for an official recovery image . . ."
|
| +
|
| + # Check for a kernel selected root device or one in a well known location.
|
| + wait_for_gpt_root || wait_for_root || return 1
|
| +
|
| + # Now see if it has a Chrome OS rootfs partition.
|
| + cgpt find -t rootfs "$(strip_partition "$USB_DEV")" || return 1
|
| + REAL_USB_DEV="$USB_DEV"
|
| +
|
| + LOG_DEV="$(strip_partition "$USB_DEV")"1 # Default to stateful.
|
| +
|
| + # Now see if the root should be integrity checked.
|
| + if check_if_dm_root; then
|
| + setup_dm_root || return 1
|
| + fi
|
| +
|
| + mount_usb || return 1
|
| + return 0
|
| +}
|
| +
|
| +find_developer_root() {
|
| + is_developer_mode || return 1
|
| + # Lock the TPM prior to using an untrusted root.
|
| + lock_tpm || return 1
|
| + plog "\nSearching for developer root . . ."
|
| + # If an official root could not be mounted, free up the underlying device
|
| + # if it is claimed by verity.
|
| + dmsetup remove "$DM_NAME"
|
| +
|
| + # If we found a valid rootfs earlier, then we're done.
|
| + USB_DEV="$REAL_USB_DEV"
|
| + [ -z "$USB_DEV" ] && return 1
|
| + set_unofficial_root || return 1
|
| + mount_usb || return 1
|
| + return 0
|
| +}
|
| +
|
| +# If this kernel image has been placed on a drive with only a
|
| +# stateful partition, root detection will rightly fail. However,
|
| +# we can still run a developer supplied script so we will pretend
|
| +# stateful is the root (USB_DEV).
|
| +find_shim_root() {
|
| + # Lock the TPM prior to using an untrusted root.
|
| + lock_tpm || return 1
|
| + plog "\nSearching for an alternate recovery image . . ."
|
| + dlog -n "Waiting for $USB_SHIM_DEVS to appear"
|
| + for try in $(seq 20); do
|
| + plog " ."
|
| + for dev in $USB_SHIM_DEVS; do
|
| + if [ -b "/dev/${dev}" ]; then
|
| + USB_DEV="/dev/${dev}"
|
| + REAL_USB_DEV="$USB_DEV"
|
| + dlog "Found $USB_DEV"
|
| + set_unofficial_root || on_error
|
| + mount_usb || return 1
|
| + return 0
|
| + fi
|
| + done
|
| + sleep 1
|
| + done
|
| return 1
|
| }
|
|
|
| +# If we have a verified recovery root, ensure all blocks are valid before
|
| +# handing it off to the installer.
|
| +validate_recovery_root() {
|
| + # Allow test recovery roots that are unverified.
|
| + [ "$USB_DEV" = "/dev/dm-0" ] || return 0
|
| + is_unofficial_root && return 0
|
| +
|
| + plog "Validating official recovery image . . . "
|
| + # Ensure the verified rootfs is fully intact or fail with no USB_DEV.
|
| + # REAL_USB_DEV is left intact.
|
| + # Correctness wins over speed for this.
|
| + if ! dd if="$USB_DEV" of=/dev/null bs=$((16 * 1024 * 1024)); then
|
| + dlog "Included root filesystem could not be verified."
|
| + log " failed!"
|
| + dmsetup remove "$DM_NAME" # Free up the real root for use.
|
| + USB_DEV=
|
| + return 1
|
| + fi
|
| + log " completed."
|
| + return 0
|
| +}
|
| +
|
| setup_dm_root() {
|
| - echo -n "Extracting the device mapper configuration..."
|
| + dlog -n "Extracting the device mapper configuration..."
|
| + # export_args can't handle dm="..." at present.
|
| DMARG=$(cat /proc/cmdline | sed -e 's/.*dm="\([^"\]*\)".*/\1/g')
|
| DM_NAME=$(echo "$DMARG" | cut -f1 -d' ')
|
| # We override the reboot-to-recovery error behavior so that we can fail
|
| # gracefully on invalid rootfs.
|
| DM_TABLE="$(echo "$DMARG" | cut -f2 -d,) eio"
|
| +
|
| + # Don't attempt to call dmsetup if the root device isn't one that was
|
| + # discovered as the creation process will hang.
|
| + # TODO(wad) once we pass the UUID this will be easier to robustly check.
|
| + if [ -n "${USB_DEV}" ]; then
|
| + [ "${DM_TABLE%$USB_DEV*}" = "${DM_TABLE}" ] && return 1
|
| + fi
|
| +
|
| if ! dmsetup create -r "$DM_NAME" --major 254 --minor 0 --table "$DM_TABLE"
|
| then
|
| - echo "Failed to configure device mapper root"
|
| + dlog "Failed to configure device mapper root"
|
| return 1
|
| fi
|
| USB_DEV="/dev/dm-0"
|
| if [ ! -b "$USB_DEV" ]; then
|
| - mknod -m 0600 /dev/dm-0 b 254 0
|
| + mknod -m 0600 "$USB_DEV" b 254 0
|
| fi
|
| - echo "Created device mapper root $DM_NAME."
|
| + dlog "Created device mapper root $DM_NAME."
|
| return 0
|
| }
|
|
|
| -# Look for a device with our GPT ID.
|
| -wait_for_gpt_root() {
|
| - echo -n "Looking for rootfs..."
|
| - for try in $(seq 20); do
|
| - echo -n "."
|
| - newroot=$(/usr/sbin/chromeos-findrootfs)
|
| - if [ -n "$newroot" ]; then
|
| - USB_DEV="$newroot"
|
| - echo "Found $USB_DEV"
|
| - return 0
|
| - fi
|
| - sleep 1
|
| - done
|
| - echo "Failed waiting for kern_guid"
|
| - return 1
|
| -}
|
| -
|
| mount_usb() {
|
| - echo -n "Mounting usb"
|
| + dlog -n "Mounting usb"
|
| for try in $(seq 20); do
|
| - echo -n "."
|
| + plog " ."
|
| if mount -n -o ro "$USB_DEV" "$USB_MNT"; then
|
| - echo "ok"
|
| + dlog "ok"
|
| return 0
|
| fi
|
| sleep 1
|
| done
|
| - echo "Failed to mount usb!"
|
| + dlog "Failed to mount usb!"
|
| return 1
|
| }
|
|
|
| -check_if_dm_root() {
|
| - grep -q "root=/dev/dm-0" /proc/cmdline
|
| -}
|
| -
|
| check_if_factory_install() {
|
| + if is_unofficial_root; then
|
| + dlog "Skipping factory install check."
|
| + return 1
|
| + fi
|
| +
|
| if [ -e "${USB_MNT}/root/.factory_installer" ]; then
|
| - echo "Detected factory install."
|
| + log "Detected factory install."
|
| return 0
|
| fi
|
| return 1
|
| }
|
|
|
| get_stateful_dev() {
|
| - # Determine the STATE_DEV using rootdev on the target
|
| - # to work seamlessly with dm-verity or normal devices.
|
| - REAL_USB_DEV=$(rootdev -s "$USB_MNT")
|
| STATE_DEV=${REAL_USB_DEV%[0-9]*}1
|
| if [ ! -b "$STATE_DEV" ]; then
|
| - echo "Failed to determine stateful device"
|
| + dlog "Failed to determine stateful device"
|
| return 1
|
| fi
|
| return 0
|
| }
|
|
|
| mount_tmpfs() {
|
| - echo "Mounting tmpfs..."
|
| + dlog "Mounting tmpfs..."
|
| mount -n -t tmpfs tmpfs "$NEWROOT_MNT" -o "size=$TMPFS_SIZE"
|
| return $?
|
| }
|
|
|
| copy_contents() {
|
| - echo "Copying usb->tmpfs..."
|
| + log "Copying usb->tmpfs..."
|
| (cd "${USB_MNT}" ; tar cf - . | (cd "${NEWROOT_MNT}" && tar xf -))
|
| RES=$?
|
| - echo "Copy returned with result $RES"
|
| + log "Copy returned with result $RES"
|
| return $RES
|
| }
|
|
|
| copy_lsb() {
|
| STATEFUL_LSB="dev_image/etc/lsb-factory"
|
| mkdir -p "${NEWROOT_MNT}/mnt/stateful_partition/dev_image/etc"
|
| - mount -n -o ro -t ext3 "$STATE_DEV" "$STATEFUL_MNT"
|
| + # Mounting ext3 as ext2 since the journal is unneeded in ro.
|
| + mount -n -o ro -t ext2 "$STATE_DEV" "$STATEFUL_MNT"
|
| if [ -f "${STATEFUL_MNT}/${STATEFUL_LSB}" ]; then
|
| - echo "Found ${STATEFUL_MNT}/${STATEFUL_LSB}"
|
| + log "Found ${STATEFUL_MNT}/${STATEFUL_LSB}"
|
| cp -a "${STATEFUL_MNT}/${STATEFUL_LSB}" \
|
| "${NEWROOT_MNT}/mnt/stateful_partition/${STATEFUL_LSB}"
|
| fi
|
| @@ -176,50 +316,434 @@ copy_lsb() {
|
| }
|
|
|
| move_mounts() {
|
| - echo "Moving sys. proc, dev to $NEWROOT_MNT"
|
| - mount -n -o move /sys "$NEWROOT_MNT/sys"
|
| - mount -n -o move /proc "$NEWROOT_MNT/proc"
|
| - mount -n -o move /dev "$NEWROOT_MNT/dev"
|
| - echo "Done."
|
| + dlog "Moving sys. proc, dev to $NEWROOT_MNT"
|
| + for mnt in $MOVE_MOUNTS; do
|
| + mkdir -p "$NEWROOT_MNT$mnt"
|
| + mount -n -o move "$mnt" "$NEWROOT_MNT$mnt"
|
| + done
|
| + TTY_PATH="$NEWROOT_MNT/dev/tty"
|
| + dlog "Done."
|
| + return 0
|
| +}
|
| +
|
| +unmove_mounts() {
|
| + dlog "Moving sys. proc, dev to $NEWROOT_MNT"
|
| + for mnt in $MOVE_MOUNTS; do
|
| + mount -n -o move "$NEWROOT_MNT$mnt" "$mnt"
|
| + done
|
| + TTY_PATH="/dev/tty"
|
| + dlog "Done."
|
| return 0
|
| }
|
|
|
| unmount_usb() {
|
| - echo "Unmounting $USB_MNT"
|
| + dlog "Unmounting $USB_MNT"
|
| umount "$USB_MNT"
|
| - echo
|
| - echo "$USB_DEV can now be safely removed"
|
| - echo
|
| + dlog
|
| + dlog "$USB_DEV can now be safely removed"
|
| + dlog
|
| + return 0
|
| +}
|
| +
|
| +strip_partition() {
|
| + local dev="${1%[0-9]*}"
|
| + # handle mmcblk0p case as well
|
| + echo "${dev%p*}"
|
| +}
|
| +
|
| +# Accomodate sd* or mmcblk* devices
|
| +get_dst() {
|
| + DST=$(echo ${REAL_USB_DEV%[0-9]*} | \
|
| + tr -s '[0-9]' '0' | \
|
| + sed -e 's/sd[b-z]/sda/g')
|
| +}
|
| +
|
| +dev_wait_or_error() {
|
| + is_developer_mode || on_error # terminal if we get here in regular mode.
|
| + log ""
|
| + log "A developer key change is required to proceed."
|
| + plog "Please wait 300 seconds . . ."
|
| + # TODO(wad) Divvy the total up into a few different prompts.
|
| + make_user_wait 300
|
| + log ""
|
| +}
|
| +
|
| +recovery_wait() {
|
| + log ""
|
| + log "Preparing to recover your system image."
|
| + plog "If you do not wish to proceed, please reboot in the next 10 seconds ."
|
| + make_user_wait 10
|
| + log ""
|
| + log "System recovery is beginning."
|
| + log "Please do not disconnect from a power source or power down."
|
| + log ""
|
| +}
|
| +
|
| +make_user_wait() {
|
| + local delay_in_sec="${1-300}"
|
| + while [ $delay_in_sec -gt 0 ]; do
|
| + plog " ."
|
| + sleep 1
|
| + delay_in_sec=$((delay_in_sec - 1))
|
| + done
|
| + log ""
|
| +}
|
| +
|
| +check_key_or_wait() {
|
| + plog "Searching the system disk for a matching kernel key . . ."
|
| + if ! cgpt find -t kernel -M "$1" "$DST"; then
|
| + log " failed."
|
| + dev_wait_or_error
|
| + return 0
|
| + fi
|
| + log " found."
|
| +
|
| + plog "Validating matching signature(s) . . ."
|
| + # If we found a keyblock, at the right offset, make sure it actually signed
|
| + # the subsequent payload.
|
| + local kdev=
|
| + for kdev in $(cgpt find -t kernel -M "$1" "$DST"); do
|
| + plog " ."
|
| + verify_kernel_signature "$kdev" "/tmp/kern.keyblock" || continue
|
| + log " done."
|
| + return 0
|
| + done
|
| +
|
| + log " failed."
|
| + dev_wait_or_error
|
| return 0
|
| }
|
|
|
| +# Never returns on success.
|
| +attempt_shim_script() {
|
| + # TODO(wad) Add static root of trust validation then remove the next line.
|
| + # http://crosbug/8390
|
| + is_developer_mode || return 1
|
| +
|
| + # Now we will either install a colocated Chromium OS image by
|
| + # checking the keys on KERN-B against any on disk (KERN-[ABC])
|
| + # or by checking a signed script on stateful.
|
| + dlog "Checking for a shim script . . ."
|
| + [ -x "$SHIM_SCRIPT" ] || return 1
|
| + [ -f "$SHIM_VBLOCK" ] || return 1
|
| + log "Shim script and signing file found!"
|
| +
|
| + plog "Verifying the signature on the script . . ."
|
| + # Extract pubkey and check signature
|
| + if ! dev_sign_file --verify "$SHIM_SCRIPT" \
|
| + --vblock "$SHIM_VBLOCK" \
|
| + --keyblock /tmp/shim.keyblock; then
|
| + log " failed."
|
| + fi
|
| + log " done."
|
| +
|
| + # If we're not in developer mode, this will be terminal on failure.
|
| + check_key_or_wait /tmp/shim.keyblock
|
| +
|
| + # Run the user supplied script. It is done in the current environment
|
| + # to avoid needing anything other than the script/program on the partition.
|
| + log "Executing shim script . . ."
|
| +
|
| + dlog "calling $SHIM_SCRIPT with exec"
|
| + # Fix up the input/output
|
| + stop_log_file
|
| + set +x
|
| + exec &> "$TTY_PATH"1
|
| + exec < "$TTY_PATH"1
|
| + # Call the script!
|
| + exec "$SHIM_SCRIPT"
|
| +
|
| + # Never reached.
|
| + save_log_file
|
| + return 0
|
| +}
|
| +
|
| +get_kern_b_device() {
|
| + # TODO(wad) By changing boot priority, could we end up
|
| + # checking the recovery image or the recovery image could not
|
| + # be in slot A. In that case, it should fail in normal mode.
|
| + KERN_B_DEV=${REAL_USB_DEV%[0-9]*}4
|
| + if [ ! -b "${KERN_B_DEV}" ]; then
|
| + return 1
|
| + fi
|
| + return 0
|
| +}
|
| +
|
| +get_real_kern_b_hash() {
|
| + REAL_KERN_B_HASH=$(dd if="${KERN_B_DEV}" | \
|
| + sha1sum | \
|
| + cut -f1 -d' ')
|
| + [ -n "$REAL_KERN_B_HASH" ]
|
| +}
|
| +
|
| +verify_kernel_signature() {
|
| + local kern_dev="$1"
|
| + local keyblock="$2"
|
| +
|
| + if ! dd if="$kern_dev" of="/tmp/kern.bin"; then
|
| + return 1
|
| + fi
|
| +
|
| + # Validates the signature and outputs a keyblock.
|
| + if ! vbutil_kernel --verify "/tmp/kern.bin" \
|
| + --keyblock "$keyblock"; then
|
| + return 1
|
| + fi
|
| + return 0
|
| +}
|
| +
|
| +verify_install_kernel() {
|
| + get_kern_b_device || return 1
|
| + get_real_kern_b_hash || return 1
|
| +
|
| + # TODO(wad) check signatures from stateful on kern b using the
|
| + # root of trust instead of using a baked in cmdline.
|
| + if [ "$REAL_KERN_B_HASH" != "$KERN_ARG_KERN_B_HASH" ]; then
|
| + if ! is_developer_mode; then
|
| + log "The recovery image cannot be verified."
|
| + return 1
|
| + fi
|
| +
|
| + # Extract the kernel so that vbutil_kernel will happily consume it.
|
| + log "Checking the install kernel for a valid developer signature . . ."
|
| + verify_kernel_signature "$KERN_B_DEV" "/tmp/kern_b.keyblock" || return 1
|
| + check_key_or_wait /tmp/kern_b.keyblock
|
| + fi
|
| + return 0
|
| +}
|
| +
|
| +touch_developer_mode_file() {
|
| + is_developer_mode || return 1
|
| + mount -n -o rw -t ext3 "$DST"1 "$STATEFUL_MNT" || return 1
|
| + touch "$STATEFUL_MNT/.developer_mode" || return 1
|
| + umount "$STATEFUL_MNT" || return 1
|
| + return 0
|
| +}
|
| +
|
| +call_image_recovery_script() {
|
| + mount -t tmpfs -o mode=1777 none "$USB_MNT/tmp" || return 1
|
| + move_mounts || return 1
|
| +
|
| + # Start the copy.
|
| + log ""
|
| + log "Beginning system image copy. This will take some time . . ."
|
| + log ""
|
| + # Until images are built with the installer keyblock in KERN-B by
|
| + # default, we keep copying over the installer vblock from the
|
| + # stateful partition.
|
| + # TODO(wad) http://crosbug/8378
|
| + export IS_RECOVERY_INSTALL=1
|
| + chroot "$USB_MNT" \
|
| + /usr/sbin/chromeos-install --run_as_root --yes \
|
| + --payload_image="$1" \
|
| + --use_payload_kern_b
|
| + if [ $? -eq 0 ]; then
|
| + log "System copy completed."
|
| + else
|
| + log "Error performing system recovery!"
|
| + fi
|
| +
|
| + # Clean up doesn't need to be successful.
|
| + umount "$USB_MNT/tmp"
|
| + unmove_mounts
|
| +
|
| + # If we're in developer mode, touch the .developer_mode file on stateful
|
| + # to avoid a bonus wait period on reboot.
|
| + # Failure here is non-terminal and it may not succeed just because the
|
| + # partition table of the destination has not been synchronized.
|
| + dlog "Prepping destination stateful to avoid a secondary delay."
|
| + touch_developer_mode_file && log "Prepped image for developer use."
|
| +
|
| + return 0
|
| +}
|
| +
|
| +clear_tpm() {
|
| + plog "Resetting security device . . ."
|
| + # TODO(wad) should we fail on this?
|
| + tpmc ppon || dlog "tpmc ppon error: $?"
|
| + tpmc clear || dlog "tpmc clear error: $?"
|
| + tpmc enable || dlog "tpmc enable error: $?"
|
| + tpmc activate || dlog "tpmc activate error: $?"
|
| + tpmc pplock || dlog "tpmc pplock error: $?"
|
| + log " done."
|
| + return 0
|
| +}
|
| +
|
| +save_log_file() {
|
| + local log_dev="${1:-$LOG_DEV}"
|
| + [ -z "$log_dev" ] && return 0
|
| + # The recovery stateful is usually too small for ext3.
|
| + # TODO(wad) We could also just write the data raw if needed.
|
| + # Should this also try to save
|
| + dlog "Attempting to save log file: $LOG_FILE -> $log_dev"
|
| + mount -n -o sync,rw -t ext2 "$log_dev" /tmp
|
| + cp "$LOG_FILE" /tmp
|
| + umount -n /tmp
|
| +}
|
| +
|
| +stop_log_file() {
|
| + # Drop logging
|
| + exec &> "$TTY_PATH"3
|
| + [ -n "$TAIL_PID" ] && kill $TAIL_PID
|
| +}
|
| +
|
| +is_unofficial_root() {
|
| + [ $UNOFFICIAL_ROOT -eq 1 ]
|
| +}
|
| +
|
| +set_unofficial_root() {
|
| + UNOFFICIAL_ROOT=1
|
| + return 0
|
| +}
|
| +
|
| +recover_system() {
|
| + local source=$(strip_partition "$REAL_USB_DEV")
|
| + dlog "Beginning system recovery from $source"
|
| +
|
| + recovery_wait
|
| +
|
| + if is_unofficial_root; then
|
| + dlog "Attempting to use shim . . ."
|
| + # Mounting read only so a journal is not needed.
|
| + # If it fails, we can still proceed on a normal recovery path.
|
| + mount -n -o ro -t ext2 "$STATE_DEV" "$STATEFUL_MNT"
|
| + attempt_shim_script # never returns on success.
|
| + umount "$STATEFUL_MNT"
|
| + fi
|
| +
|
| + # If we're not running a developer script then we're either
|
| + # installing a developer image or an official one. If we're
|
| + # in normal recovery mode, then we require that the KERN-B
|
| + # on the recovery image matches the hash on the command line.
|
| + # In developer mode, we will just check the keys.
|
| + verify_install_kernel || return 1
|
| +
|
| + # Only clear on full installs. Shim scripts can call tpmc if they
|
| + # like. Only bGlobalLock will be in place in advance.
|
| + clear_tpm || return 1
|
| +
|
| + call_image_recovery_script "$source" || return 1
|
| +
|
| + return 0
|
| +}
|
| +
|
| +use_new_root() {
|
| + move_mounts || on_error
|
| +
|
| + # Chroot into newroot, erase the contents of the old /, and exec real init.
|
| + log "About to switch root"
|
| + stop_log_file
|
| + exec switch_root -c /dev/console "$NEWROOT_MNT" /sbin/init
|
| +
|
| + # This should not really happen.
|
| + log "Failed to switch root."
|
| + save_log_file
|
| + return 1
|
| +}
|
| +
|
| +is_developer_mode() {
|
| + # See Firmware High-Level Spec for details on CHSW values
|
| + CHSW=$(cat /sys/devices/platform/chromeos_acpi/CHSW)
|
| + # If the switch is unsupported, treat as developer mode.
|
| + [ -z "$CHSW" ] && return 0
|
| + if [ $CHSW -gt 0 -a $((CHSW & 32)) -eq 32 ]; then
|
| + return 0
|
| + fi
|
| + return 1
|
| +}
|
| +
|
| +lock_tpm() {
|
| + if [ -z "$TPM_B_LOCKED" ]; then
|
| + # Depending on the system, the tpm may need to be started.
|
| + # Don't fail if it doesn't work though.
|
| + tpmc startup
|
| + tpmc ctest
|
| + if ! tpmc block; then
|
| + log "An unrecoverable error occurred with your security device"
|
| + log "Please power down and try again."
|
| + dlog "Failed to lock bGlobalLock."
|
| + on_error
|
| + return 1 # Never reached.
|
| + fi
|
| + TPM_B_LOCKED=y
|
| + fi
|
| + if [ -z "$TPM_PP_LOCKED" ]; then
|
| + # TODO: tpmc pplock if appropriate
|
| + TPM_PP_LOCKED=y
|
| + fi
|
| + return 0
|
| +}
|
| +
|
| +# Extract and export kernel arguments
|
| +export_args() {
|
| + # We trust out kernel command line explicitly.
|
| + local arg=
|
| + local key=
|
| + local val=
|
| + local acceptable_set='[A-Za-z0-9]_'
|
| + for arg in "$@"; do
|
| + key=$(echo "${arg%=*}" | tr 'a-z' 'A-Z' | \
|
| + tr -dc "$acceptable_set" '_')
|
| + val="${arg#*=}"
|
| + export "KERN_ARG_$key"="$val"
|
| + dlog "Exporting kernel argument $key as KERN_ARG_$key"
|
| + done
|
| +}
|
| +
|
| +dlog() {
|
| + echo "$@" | tee -a "$TTY_PATH"2 "$TTY_PATH"3
|
| +}
|
| +
|
| +# User visible
|
| +log() {
|
| + echo "$@" | tee -a "$TTY_PATH"1 "$TTY_PATH"2
|
| +}
|
| +
|
| +plog() {
|
| + # plog doesn't go to /dev/tty3 or log file.
|
| + printf "$@" | tee "$TTY_PATH"1 "$TTY_PATH"2
|
| +}
|
| +
|
| main() {
|
| + exec &> "$LOG_FILE"
|
| +
|
| # Set up basic mounts, console.
|
| initial_mounts
|
|
|
| - # Send any output to where we can see it.
|
| - exec &> /dev/tty1
|
| - echo "Starting initramfs"
|
| + # Send all verbose output to tty3
|
| + (tail -f "$LOG_FILE" > "$TTY_PATH"3) &
|
| + TAIL_PID=$!
|
|
|
| - # If we were booted with a dm-verity rootfs, then
|
| - # we can just wait for it to come up.
|
| - if check_if_dm_root; then
|
| - wait_for_dm_control || on_error
|
| - setup_dm_root || on_error
|
| - # Has the BIOS told us the partition ID?
|
| - elif check_for_gptid; then
|
| - wait_for_gpt_root || on_error
|
| + log "Recovery image booting . . ."
|
| + log ""
|
| + log "Press Ctrl + Alt + F1 - F3 for more detailed information."
|
| + log ""
|
| +
|
| + # Export the kernel command line as a parsed blob prepending KERN_ARG_ to each
|
| + # argument.
|
| + export_args $(cat /proc/cmdline | sed -e 's/"[^"]*"/DROPPED/g')
|
| +
|
| + if is_developer_mode; then
|
| + log "! Your computer's developer mode switch is in the ENABLED position."
|
| + log "!"
|
| + log "! If this is unintentional, you should power off and toggle it back "
|
| + log "! after recovery is completed."
|
| + log ""
|
| + fi
|
| +
|
| + if find_official_root || find_developer_root || find_shim_root; then
|
| + log " found."
|
| else
|
| - # Just wait for any rootfs.
|
| - # TODO(nsanders): add kern_guid into legacy cmdline?
|
| - wait_for_root || on_error
|
| + log " not found."
|
| + on_error
|
| fi
|
|
|
| - mount_usb || on_error
|
| + # Extract the real boot source which may be masked by dm-verity.
|
| get_stateful_dev || on_error
|
|
|
| # Check if we want to run from RAM, in the factory.
|
| if check_if_factory_install; then
|
| + is_developer_mode || on_error # factory install requires it.
|
| # Copy rootfs contents to tmpfs, then unmount USB device.
|
| NEWROOT_MNT=/newroot
|
| mount_tmpfs || on_error
|
| @@ -227,24 +751,56 @@ main() {
|
| copy_lsb || on_error
|
| # USB device is unmounted, we can remove it now.
|
| unmount_usb || on_error
|
| - else
|
| - NEWROOT_MNT="$USB_MNT"
|
| + # Switch to the new root
|
| + use_new_root || on_error
|
| + on_error # !! Never reached. !!
|
| fi
|
|
|
| - move_mounts || on_error
|
| - # Chroot into newroot, erase the contents of the old /, and exec real init.
|
| - echo "About to switch root"
|
| - exec switch_root -c /dev/console "$NEWROOT_MNT" /sbin/init
|
| + # If not, we must be a recovery kernel.
|
| + NEWROOT_MNT="$USB_MNT"
|
|
|
| - # This should not really happen.
|
| - echo "Failed to switch root"
|
| + # Always lock the TPM. If a NVRAM reset is ever needed, we can change it.
|
| + lock_tpm || on_error
|
|
|
| - # Fail here.
|
| - on_error
|
| -}
|
| + # Perform a full device mapper root validation to avoid any unexpected
|
| + # failures during postinst. It also allows us to detect if the root is
|
| + # intentionally mismatched - such as during Chromium OS recovery with a
|
| + # Chrome OS recovery kernel.
|
| + if ! validate_recovery_root; then
|
| + is_developer_mode || on_error
|
| + find_developer_root || find_shim_root || on_error
|
| + log " found."
|
| + # This logic is duplicated to avoid double validating factory media. It
|
| + # will only be hit if a verified root can be mounted but is actually not
|
| + # intact.
|
| + get_stateful_dev || on_error
|
| + fi
|
| +
|
| + get_dst || on_error
|
|
|
| + recover_system || on_error
|
|
|
| -main
|
| + log "System recovery is complete!"
|
| + log "Please remove the recovery device and reboot."
|
| +
|
| + stop_log_file
|
| + # Save the recovery log to the target on success and the USB.
|
| + save_log_file "$DST"1
|
| + save_log_file
|
| +
|
| + unmount_usb
|
| +
|
| + log ""
|
| + log ""
|
| + plog "The system will automatically reboot in 2 minutes"
|
| + make_user_wait 120
|
| + exit 0
|
| +}
|
| +
|
| +# Make this source-able for testing.
|
| +if [ "$0" = "/init" ]; then
|
| + main "$@"
|
| + # Should never reach here.
|
| + exit 1
|
| +fi
|
|
|
| -# Should never reach here.
|
| -exit 1
|
|
|