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