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. |
Nick Sanders | 4fc3f46 | 2011-03-04 21:31:25 -0800 | [diff] [blame^] | 311 | if ! mount -n "${STATE_DEV}" "${STATEFUL_MNT}"; then |
Nick Sanders | ee7e6ae | 2011-03-03 11:21:43 -0800 | [diff] [blame] | 312 | log "Failed to mount ${STATE_DEV}!! Failing." |
| 313 | return 1 |
| 314 | fi |
Nick Sanders | f11aca6 | 2011-01-28 17:39:55 -0800 | [diff] [blame] | 315 | 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 Sanders | ee7e6ae | 2011-03-03 11:21:43 -0800 | [diff] [blame] | 319 | else |
| 320 | log "Failed to find ${STATEFUL_MNT}/${STATEFUL_LSB}!! Failing." |
| 321 | umount "$STATEFUL_MNT" |
| 322 | return 1 |
Nick Sanders | f11aca6 | 2011-01-28 17:39:55 -0800 | [diff] [blame] | 323 | fi |
| 324 | umount "$STATEFUL_MNT" |
| 325 | rmdir "$STATEFUL_MNT" |
Colin Chow | eb584db | 2010-07-08 16:02:56 -0700 | [diff] [blame] | 326 | } |
| 327 | |
| 328 | move_mounts() { |
Nick Sanders | f11aca6 | 2011-01-28 17:39:55 -0800 | [diff] [blame] | 329 | 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 Chow | eb584db | 2010-07-08 16:02:56 -0700 | [diff] [blame] | 333 | done |
Nick Sanders | f11aca6 | 2011-01-28 17:39:55 -0800 | [diff] [blame] | 334 | TTY_PATH="$NEWROOT_MNT/dev/tty" |
| 335 | dlog "Done." |
Colin Chow | eb584db | 2010-07-08 16:02:56 -0700 | [diff] [blame] | 336 | return 0 |
| 337 | } |
| 338 | |
Nick Sanders | f11aca6 | 2011-01-28 17:39:55 -0800 | [diff] [blame] | 339 | unmove_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 Chow | eb584db | 2010-07-08 16:02:56 -0700 | [diff] [blame] | 347 | } |
| 348 | |
Nick Sanders | f11aca6 | 2011-01-28 17:39:55 -0800 | [diff] [blame] | 349 | unmount_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 | |
| 363 | strip_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 |
| 370 | get_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 | |
| 376 | dev_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 | |
| 386 | recovery_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 | |
| 397 | make_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 | |
| 407 | check_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. |
| 433 | attempt_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 | |
| 476 | get_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 | |
| 487 | get_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 | |
| 494 | verify_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 | |
| 510 | verify_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 | |
| 530 | touch_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 | |
| 538 | call_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 | |
| 575 | clear_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 | |
| 587 | save_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 | |
| 599 | stop_log_file() { |
| 600 | # Drop logging |
| 601 | exec &> "$TTY_PATH"3 |
| 602 | [ -n "$TAIL_PID" ] && kill $TAIL_PID |
| 603 | } |
| 604 | |
| 605 | is_unofficial_root() { |
| 606 | [ $UNOFFICIAL_ROOT -eq 1 ] |
| 607 | } |
| 608 | |
| 609 | set_unofficial_root() { |
| 610 | UNOFFICIAL_ROOT=1 |
| 611 | return 0 |
| 612 | } |
| 613 | |
| 614 | recover_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 | |
| 645 | use_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 | |
| 659 | is_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 | |
| 670 | lock_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 |
| 693 | export_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 | |
| 708 | dlog() { |
| 709 | echo "$@" | tee -a "$TTY_PATH"2 "$TTY_PATH"3 |
| 710 | } |
| 711 | |
| 712 | # User visible |
| 713 | log() { |
| 714 | echo "$@" | tee -a "$TTY_PATH"1 "$TTY_PATH"2 |
| 715 | } |
| 716 | |
| 717 | plog() { |
| 718 | # plog doesn't go to /dev/tty3 or log file. |
| 719 | printf "$@" | tee "$TTY_PATH"1 "$TTY_PATH"2 |
| 720 | } |
| 721 | |
| 722 | main() { |
| 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. |
| 817 | if [ "$0" = "/init" ]; then |
| 818 | main "$@" |
| 819 | # Should never reach here. |
| 820 | exit 1 |
| 821 | fi |
| 822 | |