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