Home | History | Annotate | Download | only in image_signing
      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 
     10 SCRIPT_BASE="$(dirname "$0")"
     11 . "$SCRIPT_BASE/common_minimal.sh"
     12 load_shflags || exit 1
     13 
     14 # Constants used by DEFINE_*
     15 VBOOT_BASE='/usr/share/vboot'
     16 DEFAULT_KEYS_FOLDER="$VBOOT_BASE/devkeys"
     17 DEFAULT_BACKUP_FOLDER='/mnt/stateful_partition/backups'
     18 DEFAULT_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.
     22 ROOTDEV="$(rootdev -s 2>/dev/null)"
     23 ROOTDEV_PARTITION="$(echo $ROOTDEV | sed -n 's/.*\([0-9][0-9]*\)$/\1/p')"
     24 ROOTDEV_DISK="$(rootdev -s -d 2>/dev/null)"
     25 ROOTDEV_KERNEL="$((ROOTDEV_PARTITION - 1))"
     26 
     27 # DEFINE_string name default_value description flag
     28 DEFINE_string image "$ROOTDEV_DISK" "Path to device or image file" "i"
     29 DEFINE_string keys "$DEFAULT_KEYS_FOLDER" "Path to folder of dev keys" "k"
     30 DEFINE_boolean remove_rootfs_verification \
     31   $FLAGS_FALSE "Modify kernel boot config to disable rootfs verification" ""
     32 DEFINE_string backup_dir \
     33   "$DEFAULT_BACKUP_FOLDER" "Path of directory to store kernel backups" ""
     34 DEFINE_string save_config "" \
     35   "Base filename to store kernel configs to, instead of resigning." ""
     36 DEFINE_string set_config "" \
     37   "Base filename to load kernel configs from" ""
     38 DEFINE_string partitions "" \
     39   "List of partitions to examine (default: $DEFAULT_PARTITIONS)" ""
     40 DEFINE_boolean recovery_key "$FLAGS_FALSE" \
     41  "Use recovery key to sign image (to boot from USB" ""
     42 DEFINE_boolean force "$FLAGS_FALSE" "Skip sanity checks and make the change" "f"
     43 
     44 # Parse command line
     45 FLAGS "$@" || exit 1
     46 ORIGINAL_PARAMS="$@"
     47 eval set -- "$FLAGS_ARGV"
     48 ORIGINAL_PARTITIONS="$FLAGS_partitions"
     49 : ${FLAGS_partitions:=$DEFAULT_PARTITIONS}
     50 
     51 # Globals
     52 # ----------------------------------------------------------------------------
     53 set -e
     54 
     55 # a log file to keep the output results of executed command
     56 EXEC_LOG="$(make_temp_file)"
     57 
     58 # Functions
     59 # ----------------------------------------------------------------------------
     60 
     61 # Removes rootfs verification from kernel boot parameter
     62 remove_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 
     72 remove_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
     89 mydd() {
     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
     97 cros_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 
    113 find_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.
    133 resign_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 
    296 sanity_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 
    323 sanity_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 # ----------------------------------------------------------------------------
    377 main() {
    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.
    440 if [ "$#" -gt 0 ]; then
    441   flags_help
    442   err_die "Unknown parameters: $@"
    443 fi
    444 
    445 main
    446