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