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