1#!/bin/sh 2# 3# Copyright (c) 2012 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# This script can change key (usually developer keys) and kernel config 8# of kernels on an disk image (usually for SSD but also works for USB). 9 10SCRIPT_BASE="$(dirname "$0")" 11. "$SCRIPT_BASE/common_minimal.sh" 12load_shflags || exit 1 13 14# Constants used by DEFINE_* 15VBOOT_BASE='/usr/share/vboot' 16DEFAULT_KEYS_FOLDER="$VBOOT_BASE/devkeys" 17DEFAULT_BACKUP_FOLDER='/mnt/stateful_partition/backups' 18DEFAULT_PARTITIONS='2 4' 19 20# TODO(hungte) The default image selection is no longer a SSD, so the script 21# works more like "make_dev_image". We may change the file name in future. 22ROOTDEV="$(rootdev -s 2>/dev/null)" 23ROOTDEV_PARTITION="$(echo $ROOTDEV | sed -n 's/.*\([0-9][0-9]*\)$/\1/p')" 24ROOTDEV_DISK="$(rootdev -s -d 2>/dev/null)" 25ROOTDEV_KERNEL="$((ROOTDEV_PARTITION - 1))" 26 27# DEFINE_string name default_value description flag 28DEFINE_string image "$ROOTDEV_DISK" "Path to device or image file" "i" 29DEFINE_string keys "$DEFAULT_KEYS_FOLDER" "Path to folder of dev keys" "k" 30DEFINE_boolean remove_rootfs_verification \ 31 $FLAGS_FALSE "Modify kernel boot config to disable rootfs verification" "" 32DEFINE_string backup_dir \ 33 "$DEFAULT_BACKUP_FOLDER" "Path of directory to store kernel backups" "" 34DEFINE_string save_config "" \ 35 "Base filename to store kernel configs to, instead of resigning." "" 36DEFINE_string set_config "" \ 37 "Base filename to load kernel configs from" "" 38DEFINE_string partitions "" \ 39 "List of partitions to examine (default: $DEFAULT_PARTITIONS)" "" 40DEFINE_boolean recovery_key "$FLAGS_FALSE" \ 41 "Use recovery key to sign image (to boot from USB" "" 42DEFINE_boolean force "$FLAGS_FALSE" "Skip sanity checks and make the change" "f" 43 44# Parse command line 45FLAGS "$@" || exit 1 46ORIGINAL_PARAMS="$@" 47eval set -- "$FLAGS_ARGV" 48ORIGINAL_PARTITIONS="$FLAGS_partitions" 49: ${FLAGS_partitions:=$DEFAULT_PARTITIONS} 50 51# Globals 52# ---------------------------------------------------------------------------- 53set -e 54 55# a log file to keep the output results of executed command 56EXEC_LOG="$(make_temp_file)" 57 58# Functions 59# ---------------------------------------------------------------------------- 60 61# Removes rootfs verification from kernel boot parameter 62remove_rootfs_verification() { 63 local new_root="PARTUUID=%U/PARTNROFF=1" 64 echo "$*" | sed ' 65 s| root=/dev/dm-[0-9] | root='"$new_root"' | 66 s| dm_verity.dev_wait=1 | dm_verity.dev_wait=0 | 67 s| payload=PARTUUID=%U/PARTNROFF=1 | payload=ROOT_DEV | 68 s| hashtree=PARTUUID=%U/PARTNROFF=1 | hashtree=HASH_DEV | 69 s| ro | rw |' 70} 71 72remove_legacy_boot_rootfs_verification() { 73 # See src/scripts/create_legacy_bootloader_templates 74 local image="$1" 75 local mount_point="$(make_temp_dir)" 76 local config_file 77 debug_msg "Removing rootfs verification for legacy boot configuration." 78 mount_image_partition "$image" 12 "$mount_point" || return $FLAGS_FALSE 79 config_file="$mount_point/efi/boot/grub.cfg" 80 [ ! -f "$config_file" ] || 81 sudo sed -i 's/^ *set default=2 *$/set default=0/g' "$config_file" 82 config_file="$mount_point/syslinux/default.cfg" 83 [ ! -f "$config_file" ] || 84 sudo sed -i 's/-vusb/-usb/g; s/-vhd/-hd/g' "$config_file" 85 sudo umount "$mount_point" 86} 87 88# Wrapped version of dd 89mydd() { 90 # oflag=sync is safer, but since we need bs=512, syncing every block would be 91 # very slow. 92 dd "$@" >"$EXEC_LOG" 2>&1 || 93 err_die "Failed in [dd $@], Message: $(cat "$EXEC_LOG")" 94} 95 96# Prints a more friendly name from kernel index number 97cros_kernel_name() { 98 case $1 in 99 2) 100 echo "Kernel A" 101 ;; 102 4) 103 echo "Kernel B" 104 ;; 105 6) 106 echo "Kernel C" 107 ;; 108 *) 109 echo "Partition $1" 110 esac 111} 112 113find_valid_kernel_partitions() { 114 local part_id 115 local valid_partitions="" 116 for part_id in $*; do 117 local name="$(cros_kernel_name $part_id)" 118 local kernel_part="$(make_partition_dev "$FLAGS_image" "$part_id")" 119 if [ -z "$(dump_kernel_config "$kernel_part" 2>"$EXEC_LOG")" ]; then 120 echo "INFO: $name: no kernel boot information, ignored." >&2 121 else 122 [ -z "$valid_partitions" ] && 123 valid_partitions="$part_id" || 124 valid_partitions="$valid_partitions $part_id" 125 continue 126 fi 127 done 128 debug_msg "find_valid_kernel_partitions: [$*] -> [$valid_partitions]" 129 echo "$valid_partitions" 130} 131 132# Resigns a kernel on SSD or image. 133resign_ssd_kernel() { 134 # bs=512 is the fixed block size for dd and cgpt 135 local bs=512 136 local ssd_device="$1" 137 138 # reasonable size for current kernel partition 139 local min_kernel_size=16000 140 local max_kernel_size=65536 141 local resigned_kernels=0 142 143 for kernel_index in $FLAGS_partitions; do 144 local old_blob="$(make_temp_file)" 145 local new_blob="$(make_temp_file)" 146 local name="$(cros_kernel_name $kernel_index)" 147 local rootfs_index="$(($kernel_index + 1))" 148 149 debug_msg "Probing $name information" 150 local offset size 151 offset="$(partoffset "$ssd_device" "$kernel_index")" || 152 err_die "Failed to get partition $kernel_index offset from $ssd_device" 153 size="$(partsize "$ssd_device" "$kernel_index")" || 154 err_die "Failed to get partition $kernel_index size from $ssd_device" 155 if [ ! $size -gt $min_kernel_size ]; then 156 echo "INFO: $name seems too small ($size), ignored." 157 continue 158 fi 159 if [ ! $size -le $max_kernel_size ]; then 160 echo "INFO: $name seems too large ($size), ignored." 161 continue 162 fi 163 164 debug_msg "Reading $name from partition $kernel_index" 165 mydd if="$ssd_device" of="$old_blob" bs=$bs skip=$offset count=$size 166 167 debug_msg "Checking if $name is valid" 168 local kernel_config 169 if ! kernel_config="$(dump_kernel_config "$old_blob" 2>"$EXEC_LOG")"; then 170 debug_msg "dump_kernel_config error message: $(cat "$EXEC_LOG")" 171 echo "INFO: $name: no kernel boot information, ignored." 172 continue 173 fi 174 175 if [ -n "${FLAGS_save_config}" ]; then 176 # Save current kernel config 177 local old_config_file 178 old_config_file="${FLAGS_save_config}.$kernel_index" 179 echo "Saving $name config to $old_config_file" 180 echo "$kernel_config" > "$old_config_file" 181 # Just save; don't resign 182 continue 183 fi 184 185 if [ -n "${FLAGS_set_config}" ]; then 186 # Set new kernel config from file 187 local new_config_file 188 new_config_file="${FLAGS_set_config}.$kernel_index" 189 kernel_config="$(cat "$new_config_file")" || 190 err_die "Failed to read new kernel config from $new_config_file" 191 debug_msg "New kernel config: $kernel_config)" 192 echo "$name: Replaced config from $new_config_file" 193 fi 194 195 if [ ${FLAGS_remove_rootfs_verification} = $FLAGS_FALSE ]; then 196 debug_msg "Bypassing rootfs verification check" 197 else 198 debug_msg "Changing boot parameter to remove rootfs verification" 199 kernel_config="$(remove_rootfs_verification "$kernel_config")" 200 debug_msg "New kernel config: $kernel_config" 201 echo "$name: Disabled rootfs verification." 202 remove_legacy_boot_rootfs_verification "$ssd_device" 203 fi 204 205 local new_kernel_config_file="$(make_temp_file)" 206 echo -n "$kernel_config" >"$new_kernel_config_file" 207 208 debug_msg "Re-signing $name from $old_blob to $new_blob" 209 debug_msg "Using key: $KERNEL_DATAKEY" 210 vbutil_kernel \ 211 --repack "$new_blob" \ 212 --keyblock "$KERNEL_KEYBLOCK" \ 213 --config "$new_kernel_config_file" \ 214 --signprivate "$KERNEL_DATAKEY" \ 215 --oldblob "$old_blob" >"$EXEC_LOG" 2>&1 || 216 err_die "Failed to resign $name. Message: $(cat "$EXEC_LOG")" 217 218 debug_msg "Creating new kernel image (vboot+code+config)" 219 local new_kern="$(make_temp_file)" 220 cp "$old_blob" "$new_kern" 221 mydd if="$new_blob" of="$new_kern" conv=notrunc 222 223 if is_debug_mode; then 224 debug_msg "for debug purposes, check *.dbgbin" 225 cp "$old_blob" old_blob.dbgbin 226 cp "$new_blob" new_blob.dbgbin 227 cp "$new_kern" new_kern.dbgbin 228 fi 229 230 debug_msg "Verifying new kernel and keys" 231 vbutil_kernel \ 232 --verify "$new_kern" \ 233 --signpubkey "$KERNEL_PUBKEY" --verbose >"$EXEC_LOG" 2>&1 || 234 err_die "Failed to verify new $name. Message: $(cat "$EXEC_LOG")" 235 236 debug_msg "Backup old kernel blob" 237 local backup_date_time="$(date +'%Y%m%d_%H%M%S')" 238 local backup_name="$(echo "$name" | sed 's/ /_/g; s/^K/k/')" 239 local backup_file_name="${backup_name}_${backup_date_time}.bin" 240 local backup_file_path="$FLAGS_backup_dir/$backup_file_name" 241 if mkdir -p "$FLAGS_backup_dir" && 242 cp -f "$old_blob" "$backup_file_path"; then 243 echo "Backup of $name is stored in: $backup_file_path" 244 else 245 echo "WARNING: Cannot create file in $FLAGS_backup_dir... Ignore backups." 246 fi 247 248 debug_msg "Writing $name to partition $kernel_index" 249 mydd \ 250 if="$new_kern" \ 251 of="$ssd_device" \ 252 seek=$offset \ 253 bs=$bs \ 254 count=$size \ 255 conv=notrunc 256 resigned_kernels=$(($resigned_kernels + 1)) 257 258 debug_msg "Make the root file system writable if needed." 259 # TODO(hungte) for safety concern, a more robust way would be to: 260 # (1) change kernel config to ro 261 # (2) check if we can enable rw mount 262 # (3) change kernel config to rw 263 if [ ${FLAGS_remove_rootfs_verification} = $FLAGS_TRUE ]; then 264 local root_offset_sector=$(partoffset "$ssd_device" $rootfs_index) 265 local root_offset_bytes=$((root_offset_sector * 512)) 266 if ! is_ext2 "$ssd_device" "$root_offset_bytes"; then 267 debug_msg "Non-ext2 partition: $ssd_device$rootfs_index, skip." 268 elif ! rw_mount_disabled "$ssd_device" "$root_offset_bytes"; then 269 debug_msg "Root file system is writable. No need to modify." 270 else 271 # disable the RO ext2 hack 272 debug_msg "Disabling rootfs ext2 RO bit hack" 273 enable_rw_mount "$ssd_device" "$root_offset_bytes" >"$EXEC_LOG" 2>&1 || 274 err_die "Failed turning off rootfs RO bit. OS may be corrupted. " \ 275 "Message: $(cat "$EXEC_LOG")" 276 fi 277 fi 278 279 # Sometimes doing "dump_kernel_config" or other I/O now (or after return to 280 # shell) will get the data before modification. Not a problem now, but for 281 # safety, let's try to sync more. 282 sync; sync; sync 283 284 echo "$name: Re-signed with developer keys successfully." 285 done 286 287 # If we saved the kernel config, exit now so we don't print an error 288 if [ -n "${FLAGS_save_config}" ]; then 289 echo "(Kernels have not been resigned.)" 290 exit 0 291 fi 292 293 return $resigned_kernels 294} 295 296sanity_check_live_partitions() { 297 debug_msg "Partition sanity check" 298 if [ "$FLAGS_partitions" = "$ROOTDEV_KERNEL" ]; then 299 debug_msg "only for current active partition - safe." 300 return 301 fi 302 if [ "$ORIGINAL_PARTITIONS" != "" ]; then 303 debug_msg "user has assigned partitions - provide more info." 304 echo "INFO: Making change to $FLAGS_partitions on $FLAGS_image." 305 return 306 fi 307 echo " 308 ERROR: YOU ARE TRYING TO MODIFY THE LIVE SYSTEM IMAGE $FLAGS_image. 309 310 The system may become unusable after that change, especially when you have 311 some auto updates in progress. To make it safer, we suggest you to only 312 change the partition you have booted with. To do that, re-execute this command 313 as: 314 315 sudo ./make_dev_ssd.sh $ORIGINAL_PARAMS --partitions $ROOTDEV_KERNEL 316 317 If you are sure to modify other partition, please invoke the command again and 318 explicitly assign only one target partition for each time (--partitions N ) 319 " 320 return $FLAGS_FALSE 321} 322 323sanity_check_live_firmware() { 324 debug_msg "Firmware compatibility sanity check" 325 if [ "$(crossystem mainfw_type)" = "developer" ]; then 326 debug_msg "developer type firmware in active." 327 return 328 fi 329 debug_msg "Loading firmware to check root key..." 330 local bios_image="$(make_temp_file)" 331 local rootkey_file="$(make_temp_file)" 332 echo "INFO: checking system firmware..." 333 sudo flashrom -p host -i GBB -r "$bios_image" >/dev/null 2>&1 334 gbb_utility -g --rootkey="$rootkey_file" "$bios_image" >/dev/null 2>&1 335 if [ ! -s "$rootkey_file" ]; then 336 debug_msg "failed to read root key from system firmware..." 337 else 338 # The magic 130 is counted by "od dev-rootkey" for the lines until the body 339 # of key is reached. Trailing bytes (0x00 or 0xFF - both may appear, and 340 # that's why we need to skip them) are started at line 131. 341 # TODO(hungte) compare with rootkey in $VBOOT_BASE directly. 342 local rootkey_hash="$(od "$rootkey_file" | 343 head -130 | md5sum | 344 sed 's/ .*$//' )" 345 if [ "$rootkey_hash" = "a13642246ef93daaf75bd791446fec9b" ]; then 346 debug_msg "detected DEV root key in firmware." 347 return 348 else 349 debug_msg "non-devkey hash: $rootkey_hash" 350 fi 351 fi 352 353 echo " 354 ERROR: YOU ARE NOT USING DEVELOPER FIRMWARE, AND RUNNING THIS COMMAND MAY 355 THROW YOUR CHROMEOS DEVICE INTO UN-BOOTABLE STATE. 356 357 You need to either install developer firmware, or change system root key. 358 359 - To install developer firmware: type command 360 sudo chromeos-firmwareupdate --mode=todev 361 362 - To change system rootkey: disable firmware write protection (a hardware 363 switch) and then type command: 364 sudo ./make_dev_firmware.sh 365 366 If you are sure that you want to make such image without developer 367 firmware or you've already changed system root keys, please run this 368 command again with --force paramemeter: 369 370 sudo ./make_dev_ssd.sh --force $ORIGINAL_PARAMS 371 " 372 return $FLAGS_FALSE 373} 374 375# Main 376# ---------------------------------------------------------------------------- 377main() { 378 local num_signed=0 379 local num_given=$(echo "$FLAGS_partitions" | wc -w) 380 # Check parameters 381 if [ "$FLAGS_recovery_key" = "$FLAGS_TRUE" ]; then 382 KERNEL_KEYBLOCK="$FLAGS_keys/recovery_kernel.keyblock" 383 KERNEL_DATAKEY="$FLAGS_keys/recovery_kernel_data_key.vbprivk" 384 KERNEL_PUBKEY="$FLAGS_keys/recovery_key.vbpubk" 385 else 386 KERNEL_KEYBLOCK="$FLAGS_keys/kernel.keyblock" 387 KERNEL_DATAKEY="$FLAGS_keys/kernel_data_key.vbprivk" 388 KERNEL_PUBKEY="$FLAGS_keys/kernel_subkey.vbpubk" 389 fi 390 391 debug_msg "Prerequisite check" 392 ensure_files_exist \ 393 "$KERNEL_KEYBLOCK" \ 394 "$KERNEL_DATAKEY" \ 395 "$KERNEL_PUBKEY" \ 396 "$FLAGS_image" || 397 exit 1 398 399 # checks for running on a live system image. 400 if [ "$FLAGS_image" = "$ROOTDEV_DISK" ]; then 401 debug_msg "check valid kernel partitions for live system" 402 local valid_partitions="$(find_valid_kernel_partitions $FLAGS_partitions)" 403 [ -n "$valid_partitions" ] || 404 err_die "No valid kernel partitions on $FLAGS_image ($FLAGS_partitions)." 405 FLAGS_partitions="$valid_partitions" 406 407 # Sanity checks 408 if [ "$FLAGS_force" = "$FLAGS_TRUE" ]; then 409 echo " 410 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 411 ! INFO: ALL SANITY CHECKS WERE BYPASSED. YOU ARE ON YOUR OWN. ! 412 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 413 " >&2 414 local i 415 for i in $(seq 5 -1 1); do 416 echo -n "\rStart in $i second(s) (^C to abort)... " >&2 417 sleep 1 418 done 419 echo "" 420 elif ! sanity_check_live_firmware || 421 ! sanity_check_live_partitions; then 422 err_die "IMAGE $FLAGS_image IS NOT MODIFIED." 423 fi 424 fi 425 426 resign_ssd_kernel "$FLAGS_image" || num_signed=$? 427 428 debug_msg "Complete." 429 if [ $num_signed -gt 0 -a $num_signed -le $num_given ]; then 430 # signed something at least 431 echo "Successfully re-signed $num_signed of $num_given kernel(s)" \ 432 " on device $FLAGS_image". 433 else 434 err_die "Failed re-signing kernels." 435 fi 436} 437 438# People using this to process images may forget to add "-i", 439# so adding parameter check is safer. 440if [ "$#" -gt 0 ]; then 441 flags_help 442 err_die "Unknown parameters: $@" 443fi 444 445main 446