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" |
| 12 | USB_SHIM_DEVS="sdb1 sdc1 mmcblk1p1" |
| 13 | USB_MNT=/usb |
| 14 | REAL_USB_DEV= |
| 15 | DM_NAME= |
| 16 | |
| 17 | DST= |
| 18 | |
| 19 | STATEFUL_MNT=/stateful |
| 20 | STATE_DEV= |
| 21 | |
| 22 | LOG_DEV= |
| 23 | LOG_FILE="/log/recovery.log" |
| 24 | |
| 25 | TPM_B_LOCKED= |
| 26 | TPM_PP_LOCKED= |
| 27 | |
| 28 | # Developer script to run |
| 29 | SHIM_SCRIPT="$STATEFUL_MNT/userdir/runme" |
| 30 | SHIM_VBLOCK="$STATEFUL_MNT/userdir/runme.vblock" |
| 31 | |
| 32 | KERN_B_VBLOCK="$STATEFUL_MNT/vmlinuz_hd.vblock" |
| 33 | REAL_KERN_B_HASH= |
| 34 | |
| 35 | MOVE_MOUNTS="/sys /proc /dev" |
| 36 | |
| 37 | # To be updated to keep logging after move_mounts. |
| 38 | TTY_PATH="/dev/tty" |
| 39 | TAIL_PID= |
| 40 | |
| 41 | # Used to ensure the factory check only occurs with |
| 42 | # a properly matched root and kernel. |
| 43 | UNOFFICIAL_ROOT=0 |
| 44 | |
Colin Chow | eb584db | 2010-07-08 16:02:56 -0700 | [diff] [blame] | 45 | # Size of the root ramdisk. |
| 46 | TMPFS_SIZE=300M |
| 47 | |
Nick Sanders | f11aca6 | 2011-01-28 17:39:55 -0800 | [diff] [blame] | 48 | on_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 Chow | eb584db | 2010-07-08 16:02:56 -0700 | [diff] [blame] | 63 | initial_mounts() { |
Nick Sanders | f11aca6 | 2011-01-28 17:39:55 -0800 | [diff] [blame] | 64 | 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 Chow | eb584db | 2010-07-08 16:02:56 -0700 | [diff] [blame] | 78 | } |
| 79 | |
Nick Sanders | f11aca6 | 2011-01-28 17:39:55 -0800 | [diff] [blame] | 80 | # Look for a device with our GPT ID. |
| 81 | wait_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 Chow | eb584db | 2010-07-08 16:02:56 -0700 | [diff] [blame] | 98 | } |
| 99 | |
Nick Sanders | f11aca6 | 2011-01-28 17:39:55 -0800 | [diff] [blame] | 100 | # Look for any USB device. |
Colin Chow | eb584db | 2010-07-08 16:02:56 -0700 | [diff] [blame] | 101 | wait_for_root() { |
Nick Sanders | f11aca6 | 2011-01-28 17:39:55 -0800 | [diff] [blame] | 102 | dlog -n "Waiting for $USB_DEVS to appear" |
Colin Chow | eb584db | 2010-07-08 16:02:56 -0700 | [diff] [blame] | 103 | for try in $(seq 20); do |
Nick Sanders | f11aca6 | 2011-01-28 17:39:55 -0800 | [diff] [blame] | 104 | 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 Chow | eb584db | 2010-07-08 16:02:56 -0700 | [diff] [blame] | 112 | sleep 1 |
| 113 | done |
Nick Sanders | f11aca6 | 2011-01-28 17:39:55 -0800 | [diff] [blame] | 114 | dlog "Failed waiting for root!" |
Colin Chow | eb584db | 2010-07-08 16:02:56 -0700 | [diff] [blame] | 115 | return 1 |
| 116 | } |
| 117 | |
Nick Sanders | f11aca6 | 2011-01-28 17:39:55 -0800 | [diff] [blame] | 118 | # Wait for dm-0 to come up. |
| 119 | wait_for_dm_control() { |
| 120 | MAPPER_CONTROL=/dev/mapper/control |
| 121 | dlog -n "Waiting for $MAPPER_CONTROL to appear" |
Colin Chow | eb584db | 2010-07-08 16:02:56 -0700 | [diff] [blame] | 122 | for try in $(seq 20); do |
Nick Sanders | f11aca6 | 2011-01-28 17:39:55 -0800 | [diff] [blame] | 123 | plog " ." |
| 124 | if [ -c "$MAPPER_CONTROL" ]; then |
Colin Chow | eb584db | 2010-07-08 16:02:56 -0700 | [diff] [blame] | 125 | return 0 |
| 126 | fi |
| 127 | sleep 1 |
| 128 | done |
Nick Sanders | f11aca6 | 2011-01-28 17:39:55 -0800 | [diff] [blame] | 129 | dlog "Failed waiting for $MAPPER_CONTROL!" |
Colin Chow | eb584db | 2010-07-08 16:02:56 -0700 | [diff] [blame] | 130 | return 1 |
| 131 | } |
| 132 | |
Nick Sanders | f11aca6 | 2011-01-28 17:39:55 -0800 | [diff] [blame] | 133 | check_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. |
| 140 | find_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 | |
| 161 | find_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). |
| 182 | find_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. |
| 206 | validate_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 | |
| 226 | setup_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 Drewry | e7bc1dc | 2011-02-28 16:28:34 -0600 | [diff] [blame^] | 237 | # 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 Sanders | f11aca6 | 2011-01-28 17:39:55 -0800 | [diff] [blame] | 241 | [ "${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 | |
| 257 | mount_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 | |
| 271 | check_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 | |
| 284 | get_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 Chow | eb584db | 2010-07-08 16:02:56 -0700 | [diff] [blame] | 293 | mount_tmpfs() { |
Nick Sanders | f11aca6 | 2011-01-28 17:39:55 -0800 | [diff] [blame] | 294 | dlog "Mounting tmpfs..." |
| 295 | mount -n -t tmpfs tmpfs "$NEWROOT_MNT" -o "size=$TMPFS_SIZE" |
Colin Chow | eb584db | 2010-07-08 16:02:56 -0700 | [diff] [blame] | 296 | return $? |
| 297 | } |
| 298 | |
| 299 | copy_contents() { |
Nick Sanders | f11aca6 | 2011-01-28 17:39:55 -0800 | [diff] [blame] | 300 | 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 | |
| 307 | copy_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. |
| 311 | mount -n -o ro -t ext2 "$STATE_DEV" "$STATEFUL_MNT" |
| 312 | if [ -f "${STATEFUL_MNT}/${STATEFUL_LSB}" ]; then |
| 313 | log "Found ${STATEFUL_MNT}/${STATEFUL_LSB}" |
| 314 | cp -a "${STATEFUL_MNT}/${STATEFUL_LSB}" \ |
| 315 | "${NEWROOT_MNT}/mnt/stateful_partition/${STATEFUL_LSB}" |
| 316 | fi |
| 317 | umount "$STATEFUL_MNT" |
| 318 | rmdir "$STATEFUL_MNT" |
Colin Chow | eb584db | 2010-07-08 16:02:56 -0700 | [diff] [blame] | 319 | } |
| 320 | |
| 321 | move_mounts() { |
Nick Sanders | f11aca6 | 2011-01-28 17:39:55 -0800 | [diff] [blame] | 322 | dlog "Moving sys. proc, dev to $NEWROOT_MNT" |
| 323 | for mnt in $MOVE_MOUNTS; do |
| 324 | mkdir -p "$NEWROOT_MNT$mnt" |
| 325 | mount -n -o move "$mnt" "$NEWROOT_MNT$mnt" |
Colin Chow | eb584db | 2010-07-08 16:02:56 -0700 | [diff] [blame] | 326 | done |
Nick Sanders | f11aca6 | 2011-01-28 17:39:55 -0800 | [diff] [blame] | 327 | TTY_PATH="$NEWROOT_MNT/dev/tty" |
| 328 | dlog "Done." |
Colin Chow | eb584db | 2010-07-08 16:02:56 -0700 | [diff] [blame] | 329 | return 0 |
| 330 | } |
| 331 | |
Nick Sanders | f11aca6 | 2011-01-28 17:39:55 -0800 | [diff] [blame] | 332 | unmove_mounts() { |
| 333 | dlog "Moving sys. proc, dev to $NEWROOT_MNT" |
| 334 | for mnt in $MOVE_MOUNTS; do |
| 335 | mount -n -o move "$NEWROOT_MNT$mnt" "$mnt" |
| 336 | done |
| 337 | TTY_PATH="/dev/tty" |
| 338 | dlog "Done." |
| 339 | return 0 |
Colin Chow | eb584db | 2010-07-08 16:02:56 -0700 | [diff] [blame] | 340 | } |
| 341 | |
Nick Sanders | f11aca6 | 2011-01-28 17:39:55 -0800 | [diff] [blame] | 342 | unmount_usb() { |
| 343 | dlog "Unmounting $USB_MNT" |
| 344 | umount "$USB_MNT" |
| 345 | # Make sure we clean up a device-mapper root. |
| 346 | if [ "$USB_DEV" = "/dev/dm-0" ]; then |
| 347 | dlog "Removing dm-verity target" |
| 348 | dmsetup remove "$DM_NAME" |
| 349 | fi |
| 350 | dlog |
| 351 | dlog "$REAL_USB_DEV can now be safely removed" |
| 352 | dlog |
| 353 | return 0 |
| 354 | } |
| 355 | |
| 356 | strip_partition() { |
| 357 | local dev="${1%[0-9]*}" |
| 358 | # handle mmcblk0p case as well |
| 359 | echo "${dev%p*}" |
| 360 | } |
| 361 | |
| 362 | # Accomodate sd* or mmcblk* devices |
| 363 | get_dst() { |
| 364 | DST=$(echo ${REAL_USB_DEV%[0-9]*} | \ |
| 365 | tr -s '[0-9]' '0' | \ |
| 366 | sed -e 's/sd[b-z]/sda/g') |
| 367 | } |
| 368 | |
| 369 | dev_wait_or_error() { |
| 370 | is_developer_mode || on_error # terminal if we get here in regular mode. |
| 371 | log "" |
| 372 | log "A developer key change is required to proceed." |
| 373 | plog "Please wait 300 seconds . . ." |
| 374 | # TODO(wad) Divvy the total up into a few different prompts. |
| 375 | make_user_wait 300 |
| 376 | log "" |
| 377 | } |
| 378 | |
| 379 | recovery_wait() { |
| 380 | log "" |
| 381 | log "Preparing to recover your system image." |
| 382 | plog "If you do not wish to proceed, please reboot in the next 10 seconds ." |
| 383 | make_user_wait 10 |
| 384 | log "" |
| 385 | log "System recovery is beginning." |
| 386 | log "Please do not disconnect from a power source or power down." |
| 387 | log "" |
| 388 | } |
| 389 | |
| 390 | make_user_wait() { |
| 391 | local delay_in_sec="${1-300}" |
| 392 | while [ $delay_in_sec -gt 0 ]; do |
| 393 | plog " ." |
| 394 | sleep 1 |
| 395 | delay_in_sec=$((delay_in_sec - 1)) |
| 396 | done |
| 397 | log "" |
| 398 | } |
| 399 | |
| 400 | check_key_or_wait() { |
| 401 | plog "Searching the system disk for a matching kernel key . . ." |
| 402 | if ! cgpt find -t kernel -M "$1" "$DST"; then |
| 403 | log " failed." |
| 404 | dev_wait_or_error |
| 405 | return 0 |
| 406 | fi |
| 407 | log " found." |
| 408 | |
| 409 | plog "Validating matching signature(s) . . ." |
| 410 | # If we found a keyblock, at the right offset, make sure it actually signed |
| 411 | # the subsequent payload. |
| 412 | local kdev= |
| 413 | for kdev in $(cgpt find -t kernel -M "$1" "$DST"); do |
| 414 | plog " ." |
| 415 | verify_kernel_signature "$kdev" "/tmp/kern.keyblock" || continue |
| 416 | log " done." |
| 417 | return 0 |
| 418 | done |
| 419 | |
| 420 | log " failed." |
| 421 | dev_wait_or_error |
| 422 | return 0 |
| 423 | } |
| 424 | |
| 425 | # Never returns on success. |
| 426 | attempt_shim_script() { |
| 427 | # TODO(wad) Add static root of trust validation then remove the next line. |
| 428 | # http://crosbug/8390 |
| 429 | is_developer_mode || return 1 |
| 430 | |
| 431 | # Now we will either install a colocated Chromium OS image by |
| 432 | # checking the keys on KERN-B against any on disk (KERN-[ABC]) |
| 433 | # or by checking a signed script on stateful. |
| 434 | dlog "Checking for a shim script . . ." |
| 435 | [ -x "$SHIM_SCRIPT" ] || return 1 |
| 436 | [ -f "$SHIM_VBLOCK" ] || return 1 |
| 437 | log "Shim script and signing file found!" |
| 438 | |
| 439 | plog "Verifying the signature on the script . . ." |
| 440 | # Extract pubkey and check signature |
| 441 | if ! dev_sign_file --verify "$SHIM_SCRIPT" \ |
| 442 | --vblock "$SHIM_VBLOCK" \ |
| 443 | --keyblock /tmp/shim.keyblock; then |
| 444 | log " failed." |
| 445 | fi |
| 446 | log " done." |
| 447 | |
| 448 | # If we're not in developer mode, this will be terminal on failure. |
| 449 | check_key_or_wait /tmp/shim.keyblock |
| 450 | |
| 451 | # Run the user supplied script. It is done in the current environment |
| 452 | # to avoid needing anything other than the script/program on the partition. |
| 453 | log "Executing shim script . . ." |
| 454 | |
| 455 | dlog "calling $SHIM_SCRIPT with exec" |
| 456 | # Fix up the input/output |
| 457 | stop_log_file |
| 458 | set +x |
| 459 | exec &> "$TTY_PATH"1 |
| 460 | exec < "$TTY_PATH"1 |
| 461 | # Call the script! |
| 462 | exec "$SHIM_SCRIPT" |
| 463 | |
| 464 | # Never reached. |
| 465 | save_log_file |
| 466 | return 0 |
| 467 | } |
| 468 | |
| 469 | get_kern_b_device() { |
| 470 | # TODO(wad) By changing boot priority, could we end up |
| 471 | # checking the recovery image or the recovery image could not |
| 472 | # be in slot A. In that case, it should fail in normal mode. |
| 473 | KERN_B_DEV=${REAL_USB_DEV%[0-9]*}4 |
| 474 | if [ ! -b "${KERN_B_DEV}" ]; then |
| 475 | return 1 |
| 476 | fi |
| 477 | return 0 |
| 478 | } |
| 479 | |
| 480 | get_real_kern_b_hash() { |
| 481 | REAL_KERN_B_HASH=$(dd if="${KERN_B_DEV}" | \ |
| 482 | sha1sum | \ |
| 483 | cut -f1 -d' ') |
| 484 | [ -n "$REAL_KERN_B_HASH" ] |
| 485 | } |
| 486 | |
| 487 | verify_kernel_signature() { |
| 488 | local kern_dev="$1" |
| 489 | local keyblock="$2" |
| 490 | |
| 491 | if ! dd if="$kern_dev" of="/tmp/kern.bin"; then |
| 492 | return 1 |
| 493 | fi |
| 494 | |
| 495 | # Validates the signature and outputs a keyblock. |
| 496 | if ! vbutil_kernel --verify "/tmp/kern.bin" \ |
| 497 | --keyblock "$keyblock"; then |
| 498 | return 1 |
| 499 | fi |
| 500 | return 0 |
| 501 | } |
| 502 | |
| 503 | verify_install_kernel() { |
| 504 | get_kern_b_device || return 1 |
| 505 | get_real_kern_b_hash || return 1 |
| 506 | |
| 507 | # TODO(wad) check signatures from stateful on kern b using the |
| 508 | # root of trust instead of using a baked in cmdline. |
| 509 | if [ "$REAL_KERN_B_HASH" != "$KERN_ARG_KERN_B_HASH" ]; then |
| 510 | if ! is_developer_mode; then |
| 511 | log "The recovery image cannot be verified." |
| 512 | return 1 |
| 513 | fi |
| 514 | |
| 515 | # Extract the kernel so that vbutil_kernel will happily consume it. |
| 516 | log "Checking the install kernel for a valid developer signature . . ." |
| 517 | verify_kernel_signature "$KERN_B_DEV" "/tmp/kern_b.keyblock" || return 1 |
| 518 | check_key_or_wait /tmp/kern_b.keyblock |
| 519 | fi |
| 520 | return 0 |
| 521 | } |
| 522 | |
| 523 | touch_developer_mode_file() { |
| 524 | is_developer_mode || return 1 |
| 525 | mount -n -o rw -t ext3 "$DST"1 "$STATEFUL_MNT" || return 1 |
| 526 | touch "$STATEFUL_MNT/.developer_mode" || return 1 |
| 527 | umount "$STATEFUL_MNT" || return 1 |
| 528 | return 0 |
| 529 | } |
| 530 | |
| 531 | call_image_recovery_script() { |
| 532 | mount -t tmpfs -o mode=1777 none "$USB_MNT/tmp" || return 1 |
| 533 | move_mounts || return 1 |
| 534 | |
| 535 | # Start the copy. |
| 536 | log "" |
| 537 | log "Beginning system image copy. This will take some time . . ." |
| 538 | log "" |
| 539 | # Until images are built with the installer keyblock in KERN-B by |
| 540 | # default, we keep copying over the installer vblock from the |
| 541 | # stateful partition. |
| 542 | # TODO(wad) http://crosbug/8378 |
| 543 | export IS_RECOVERY_INSTALL=1 |
| 544 | chroot "$USB_MNT" \ |
| 545 | /usr/sbin/chromeos-install --run_as_root --yes \ |
| 546 | --payload_image="$1" \ |
| 547 | --use_payload_kern_b |
| 548 | if [ $? -eq 0 ]; then |
| 549 | log "System copy completed." |
| 550 | else |
| 551 | log "Error performing system recovery!" |
| 552 | fi |
| 553 | |
| 554 | # Clean up doesn't need to be successful. |
| 555 | umount "$USB_MNT/tmp" |
| 556 | unmove_mounts |
| 557 | |
| 558 | # If we're in developer mode, touch the .developer_mode file on stateful |
| 559 | # to avoid a bonus wait period on reboot. |
| 560 | # Failure here is non-terminal and it may not succeed just because the |
| 561 | # partition table of the destination has not been synchronized. |
| 562 | dlog "Prepping destination stateful to avoid a secondary delay." |
| 563 | touch_developer_mode_file && log "Prepped image for developer use." |
| 564 | |
| 565 | return 0 |
| 566 | } |
| 567 | |
| 568 | clear_tpm() { |
| 569 | plog "Resetting security device . . ." |
| 570 | # TODO(wad) should we fail on this? |
| 571 | tpmc ppon || dlog "tpmc ppon error: $?" |
| 572 | tpmc clear || dlog "tpmc clear error: $?" |
| 573 | tpmc enable || dlog "tpmc enable error: $?" |
| 574 | tpmc activate || dlog "tpmc activate error: $?" |
| 575 | tpmc pplock || dlog "tpmc pplock error: $?" |
| 576 | log " done." |
| 577 | return 0 |
| 578 | } |
| 579 | |
| 580 | save_log_file() { |
| 581 | local log_dev="${1:-$LOG_DEV}" |
| 582 | [ -z "$log_dev" ] && return 0 |
| 583 | # The recovery stateful is usually too small for ext3. |
| 584 | # TODO(wad) We could also just write the data raw if needed. |
| 585 | # Should this also try to save |
| 586 | dlog "Attempting to save log file: $LOG_FILE -> $log_dev" |
| 587 | mount -n -o sync,rw -t ext2 "$log_dev" /tmp |
| 588 | cp "$LOG_FILE" /tmp |
| 589 | umount -n /tmp |
| 590 | } |
| 591 | |
| 592 | stop_log_file() { |
| 593 | # Drop logging |
| 594 | exec &> "$TTY_PATH"3 |
| 595 | [ -n "$TAIL_PID" ] && kill $TAIL_PID |
| 596 | } |
| 597 | |
| 598 | is_unofficial_root() { |
| 599 | [ $UNOFFICIAL_ROOT -eq 1 ] |
| 600 | } |
| 601 | |
| 602 | set_unofficial_root() { |
| 603 | UNOFFICIAL_ROOT=1 |
| 604 | return 0 |
| 605 | } |
| 606 | |
| 607 | recover_system() { |
| 608 | local source=$(strip_partition "$REAL_USB_DEV") |
| 609 | dlog "Beginning system recovery from $source" |
| 610 | |
| 611 | recovery_wait |
| 612 | |
| 613 | if is_unofficial_root; then |
| 614 | dlog "Attempting to use shim . . ." |
| 615 | # Mounting read only so a journal is not needed. |
| 616 | # If it fails, we can still proceed on a normal recovery path. |
| 617 | mount -n -o ro -t ext2 "$STATE_DEV" "$STATEFUL_MNT" |
| 618 | attempt_shim_script # never returns on success. |
| 619 | umount "$STATEFUL_MNT" |
| 620 | fi |
| 621 | |
| 622 | # If we're not running a developer script then we're either |
| 623 | # installing a developer image or an official one. If we're |
| 624 | # in normal recovery mode, then we require that the KERN-B |
| 625 | # on the recovery image matches the hash on the command line. |
| 626 | # In developer mode, we will just check the keys. |
| 627 | verify_install_kernel || return 1 |
| 628 | |
| 629 | # Only clear on full installs. Shim scripts can call tpmc if they |
| 630 | # like. Only bGlobalLock will be in place in advance. |
| 631 | clear_tpm || return 1 |
| 632 | |
| 633 | call_image_recovery_script "$source" || return 1 |
| 634 | |
| 635 | return 0 |
| 636 | } |
| 637 | |
| 638 | use_new_root() { |
| 639 | move_mounts || on_error |
| 640 | |
| 641 | # Chroot into newroot, erase the contents of the old /, and exec real init. |
| 642 | log "About to switch root" |
| 643 | stop_log_file |
| 644 | exec switch_root -c /dev/console "$NEWROOT_MNT" /sbin/init |
| 645 | |
| 646 | # This should not really happen. |
| 647 | log "Failed to switch root." |
| 648 | save_log_file |
| 649 | return 1 |
| 650 | } |
| 651 | |
| 652 | is_developer_mode() { |
| 653 | # See Firmware High-Level Spec for details on CHSW values |
| 654 | CHSW=$(cat /sys/devices/platform/chromeos_acpi/CHSW) |
| 655 | # If the switch is unsupported, treat as developer mode. |
| 656 | [ -z "$CHSW" ] && return 0 |
| 657 | if [ $CHSW -gt 0 -a $((CHSW & 32)) -eq 32 ]; then |
| 658 | return 0 |
| 659 | fi |
| 660 | return 1 |
| 661 | } |
| 662 | |
| 663 | lock_tpm() { |
| 664 | if [ -z "$TPM_B_LOCKED" ]; then |
| 665 | # Depending on the system, the tpm may need to be started. |
| 666 | # Don't fail if it doesn't work though. |
| 667 | tpmc startup |
| 668 | tpmc ctest |
| 669 | if ! tpmc block; then |
| 670 | log "An unrecoverable error occurred with your security device" |
| 671 | log "Please power down and try again." |
| 672 | dlog "Failed to lock bGlobalLock." |
| 673 | on_error |
| 674 | return 1 # Never reached. |
| 675 | fi |
| 676 | TPM_B_LOCKED=y |
| 677 | fi |
| 678 | if [ -z "$TPM_PP_LOCKED" ]; then |
| 679 | # TODO: tpmc pplock if appropriate |
| 680 | TPM_PP_LOCKED=y |
| 681 | fi |
| 682 | return 0 |
| 683 | } |
| 684 | |
| 685 | # Extract and export kernel arguments |
| 686 | export_args() { |
| 687 | # We trust out kernel command line explicitly. |
| 688 | local arg= |
| 689 | local key= |
| 690 | local val= |
| 691 | local acceptable_set='[A-Za-z0-9]_' |
| 692 | for arg in "$@"; do |
| 693 | key=$(echo "${arg%=*}" | tr 'a-z' 'A-Z' | \ |
| 694 | tr -dc "$acceptable_set" '_') |
| 695 | val="${arg#*=}" |
| 696 | export "KERN_ARG_$key"="$val" |
| 697 | dlog "Exporting kernel argument $key as KERN_ARG_$key" |
| 698 | done |
| 699 | } |
| 700 | |
| 701 | dlog() { |
| 702 | echo "$@" | tee -a "$TTY_PATH"2 "$TTY_PATH"3 |
| 703 | } |
| 704 | |
| 705 | # User visible |
| 706 | log() { |
| 707 | echo "$@" | tee -a "$TTY_PATH"1 "$TTY_PATH"2 |
| 708 | } |
| 709 | |
| 710 | plog() { |
| 711 | # plog doesn't go to /dev/tty3 or log file. |
| 712 | printf "$@" | tee "$TTY_PATH"1 "$TTY_PATH"2 |
| 713 | } |
| 714 | |
| 715 | main() { |
| 716 | exec &> "$LOG_FILE" |
| 717 | |
| 718 | # Set up basic mounts, console. |
| 719 | initial_mounts |
| 720 | |
| 721 | # Send all verbose output to tty3 |
| 722 | (tail -f "$LOG_FILE" > "$TTY_PATH"3) & |
| 723 | TAIL_PID=$! |
| 724 | |
| 725 | log "Recovery image booting . . ." |
| 726 | log "" |
| 727 | log "Press Ctrl + Alt + F1 - F3 for more detailed information." |
| 728 | log "" |
| 729 | |
| 730 | # Export the kernel command line as a parsed blob prepending KERN_ARG_ to each |
| 731 | # argument. |
| 732 | export_args $(cat /proc/cmdline | sed -e 's/"[^"]*"/DROPPED/g') |
| 733 | |
| 734 | if is_developer_mode; then |
| 735 | log "! Your computer's developer mode switch is in the ENABLED position." |
| 736 | log "!" |
| 737 | log "! If this is unintentional, you should power off and toggle it back " |
| 738 | log "! after recovery is completed." |
| 739 | log "" |
| 740 | fi |
| 741 | |
| 742 | if find_official_root || find_developer_root || find_shim_root; then |
| 743 | log " found." |
| 744 | else |
| 745 | log " not found." |
| 746 | on_error |
| 747 | fi |
| 748 | |
| 749 | # Extract the real boot source which may be masked by dm-verity. |
| 750 | get_stateful_dev || on_error |
| 751 | |
| 752 | # Check if we want to run from RAM, in the factory. |
| 753 | if check_if_factory_install; then |
| 754 | is_developer_mode || on_error # factory install requires it. |
| 755 | # Copy rootfs contents to tmpfs, then unmount USB device. |
| 756 | NEWROOT_MNT=/newroot |
| 757 | mount_tmpfs || on_error |
| 758 | copy_contents || on_error |
| 759 | copy_lsb || on_error |
| 760 | # USB device is unmounted, we can remove it now. |
| 761 | unmount_usb || on_error |
| 762 | # Switch to the new root |
| 763 | use_new_root || on_error |
| 764 | on_error # !! Never reached. !! |
| 765 | fi |
| 766 | |
| 767 | # If not, we must be a recovery kernel. |
| 768 | NEWROOT_MNT="$USB_MNT" |
| 769 | |
| 770 | # Always lock the TPM. If a NVRAM reset is ever needed, we can change it. |
| 771 | lock_tpm || on_error |
| 772 | |
| 773 | # Perform a full device mapper root validation to avoid any unexpected |
| 774 | # failures during postinst. It also allows us to detect if the root is |
| 775 | # intentionally mismatched - such as during Chromium OS recovery with a |
| 776 | # Chrome OS recovery kernel. |
| 777 | if ! validate_recovery_root; then |
| 778 | is_developer_mode || on_error |
| 779 | find_developer_root || find_shim_root || on_error |
| 780 | log " found." |
| 781 | # This logic is duplicated to avoid double validating factory media. It |
| 782 | # will only be hit if a verified root can be mounted but is actually not |
| 783 | # intact. |
| 784 | get_stateful_dev || on_error |
| 785 | fi |
| 786 | |
| 787 | get_dst || on_error |
| 788 | |
| 789 | recover_system || on_error |
| 790 | |
| 791 | log "System recovery is complete!" |
| 792 | log "Please remove the recovery device and reboot." |
| 793 | |
| 794 | stop_log_file |
| 795 | # Save the recovery log to the target on success and the USB. |
| 796 | save_log_file "$DST"1 |
| 797 | save_log_file |
| 798 | |
| 799 | unmount_usb |
| 800 | |
| 801 | log "" |
| 802 | log "" |
| 803 | plog "The system will automatically reboot in 2 minutes" |
| 804 | make_user_wait 120 |
| 805 | reboot -f |
| 806 | exit 0 |
| 807 | } |
| 808 | |
| 809 | # Make this source-able for testing. |
| 810 | if [ "$0" = "/init" ]; then |
| 811 | main "$@" |
| 812 | # Should never reach here. |
| 813 | exit 1 |
| 814 | fi |
| 815 | |