| #!/bin/sh -u |
| # Copyright (c) 2010 The Chromium OS Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| # |
| # A script to display or change the preferred image |
| |
| # Load functions and constants for chromeos-install. |
| . "$(dirname "$0")/chromeos-common.sh" || exit 1 |
| if [ -e /usr/lib/shflags ]; then |
| . /usr/lib/shflags || exit 1 |
| else |
| # In case we're being called from a mounted image. |
| if [ -e "$(dirname "$0")"/../../usr/lib/shflags ]; then |
| . "$(dirname "$0")"/../../usr/lib/shflags || exit 1 |
| else |
| # Try to find it relative to src/scripts |
| . ../third_party/shflags/files/src/shflags || exit 1 |
| fi |
| fi |
| |
| DEFINE_string dst "" "Boot device to update (Default: current root device)" |
| # Any time the verified boot flags change, this is needed |
| # as well as a new grub.cfg. |
| DEFINE_string update_syslinux_cfg "" \ |
| "If specified, the path to a syslinux root.[A|B].cfg." |
| DEFINE_string update_grub_cfg "" \ |
| "If specified, the path to a new grub.cfg." |
| DEFINE_string update_vmlinuz "" \ |
| "If specified, the path to a vmlinuz to use as syslinux's vmlinuz.[A|B]" |
| DEFINE_boolean run_as_root ${FLAGS_FALSE} \ |
| "Allow root to run this script (Careful, it won't prompt for a password!)" |
| DEFINE_boolean enable_rootfs_verification ${FLAGS_TRUE} \ |
| "With override_kernel_options, will toggle verified rootfs for legacy \ |
| bootloaders" |
| DEFINE_boolean override_kernel_options ${FLAGS_FALSE} \ |
| "If given, setimage will ignore the configuration in the new kernel for vboot" |
| DEFINE_string esp_mounted_at "" \ |
| "Specify a location if the esp has already been mounted" |
| DEFINE_string kernel_image "" \ |
| "Specifies a kernel image/partition to extract the configuration from. \ |
| By default, it uses the kernel partition for A or B as requested." |
| DEFINE_string rootfs_image "" \ |
| "Specifies a rootfs image/partition to hash and update for vboot. |
| By default, it uses the current root device." |
| DEFINE_string install_root "/" \ |
| "Specifies the base location to find utilities in the new root" |
| DEFINE_boolean ignore_bad_verification ${FLAGS_FALSE} \ |
| "With override_kernel_options, will ignore rootfs integrity errors" |
| DEFINE_boolean debug ${FLAGS_FALSE} "Show debug output. Default: false" |
| |
| # Parse command line |
| FLAGS "$@" || exit 1 |
| eval set -- "${FLAGS_ARGV}" |
| |
| set -e |
| if [ "$FLAGS_debug" -eq "${FLAGS_TRUE}" ]; then |
| set -x |
| fi |
| |
| EXIT_CODE=0 # To be made non-zero in case of a non-terminal error case |
| INSTALL_ROOT="${FLAGS_install_root}" |
| # This export is so that locate_gpt will look within this install |
| # partition to find that copy of the cgpt program. |
| # TODO(wad,adlr) This duplicates the hack from postinst and should be fixed. |
| export DEFAULT_CHROOT_DIR="${INSTALL_ROOT}" |
| locate_gpt # sets $GPT to cgpt executable |
| |
| if [ "$PATH" != "/" ]; then |
| ir="${FLAGS_install_root}" |
| PATH="$ir/bin:$ir/sbin:$ir/usr/bin:$ir/usr/sbin:${PATH}" |
| unset ir |
| fi |
| |
| # Hack to run the code to setup the default dst only if not provided since if |
| # this is run from a mounted image on a different machine, rootdev will fail. |
| if [ -z "${FLAGS_dst}" ]; then |
| # The default device is the one we booted from. |
| ROOTDEV=$(rootdev -s) |
| # From root partition to root block device. |
| DEFAULT_DST=$(get_block_dev_from_partition_dev ${ROOTDEV}) |
| |
| FLAGS_dst="${DEFAULT_DST}" |
| fi |
| |
| # Validate args. |
| if [ -n "${@:-}" ]; then |
| if [ "$1" = "0" ] || [ "$1" = "A" ] || [ "$1" = "a" ]; then |
| newimg=0 |
| newimg_letter=A |
| elif [ "$1" = "1" ] || [ "$1" = "B" ] || [ "$1" = "b" ]; then |
| newimg=1 |
| newimg_letter=B |
| else |
| echo "Usage: $0 [A|B]" 1>&2 |
| exit 1 |
| fi |
| fi |
| |
| # Don't run this as root |
| dont_run_as_root |
| |
| # Check out the dst device. |
| if [ ! -b "$FLAGS_dst" ] |
| then |
| echo "Error: Unable to find block device: $FLAGS_dst" |
| exit 1 |
| fi |
| |
| # Setup loop devices so that we don't get burned by the kernel not knowing |
| # about updated partition changes. |
| ESP_LOOP= |
| ROOT_LOOP= |
| KERN_LOOP= |
| |
| # Cleans up a device and unmount if required. |
| cleanup_loop() { |
| local dev="$1" |
| local need_umount="$2" |
| if [ -z "${dev}" ]; then |
| return 0 |
| fi |
| if [ ${need_umount} -eq ${FLAGS_TRUE} ]; then |
| sudo umount -d "${dev}" || true |
| elif [ ${need_umount} -eq ${FLAGS_FALSE} ]; then |
| sudo losetup -d "${dev}" || true |
| else |
| echo "INTERNAL ERROR: unknown parameter for cleanup_loop: ${need_umount}" >&2 |
| fi |
| } |
| |
| cleanup() { |
| # Currently only ESP may be mounted. KERN and ROOT are never mounted. |
| cleanup_loop "${ESP_LOOP}" "${FLAGS_TRUE}" |
| ESP_LOOP= |
| cleanup_loop "${KERN_LOOP}" "${FLAGS_FALSE}" |
| KERN_LOOP= |
| cleanup_loop "${ROOT_LOOP}" "${FLAGS_FALSE}" |
| ROOT_LOOP= |
| |
| # Failing to clean this up isn't the worst. |
| if [ -n "${ESP_TMP_DIR}" ]; then |
| rmdir "${ESP_TMP_DIR}" || true |
| fi |
| |
| rm -f ${tempfile} || true |
| } |
| |
| setup_dst_loop() { |
| local part="$1" |
| local dev=$(sudo losetup -f) |
| if [ -z "$dev" ]; then |
| echo "No free loop devices." 1>&2 |
| return 1 |
| fi |
| local offset=$(partoffset ${FLAGS_dst} $part) |
| local size=$(partsize ${FLAGS_dst} $part) |
| sudo losetup -o $((512 * offset)) \ |
| --sizelimit $((512 * size)) \ |
| ${dev} \ |
| ${FLAGS_dst} |
| echo $dev |
| } |
| |
| # For details, see crosutils.git/common.sh |
| enable_rw_mount() { |
| local rootfs="$1" |
| local offset="${2-0}" |
| local ro_compat_offset=$((0x464 + 3)) # Set 'highest' byte |
| # Dash can't do echo -ne, but it can do printf "\NNN" |
| # We could use /dev/zero here, but this matches what would be |
| # needed for disable_rw_mount (printf '\377'). |
| printf '\000' | |
| sudo dd of="$rootfs" seek=$((offset + ro_compat_offset)) \ |
| conv=notrunc count=1 bs=1 |
| } |
| |
| ESP_TMP_DIR= |
| tempfile=$(mktemp /tmp/grubcfg_XXXXXXXXX) |
| trap cleanup EXIT |
| |
| # Mount the EFI System Partition |
| mountpoint="${FLAGS_esp_mounted_at}" |
| if [ -z "${mountpoint}" ]; then |
| mountpoint=$(mktemp -d /tmp/mountesp_XXXXXXXXX) |
| ESP_TMP_DIR=${mountpoint} |
| ESP_LOOP=$(setup_dst_loop 12) |
| sudo mount ${ESP_LOOP} ${mountpoint} |
| fi |
| |
| if [ -n "$FLAGS_update_syslinux_cfg" -a ! -r "$FLAGS_update_syslinux_cfg" ]; |
| then |
| echo "Warning: Specified syslinux root cfg file not found. Skipping" |
| fi |
| |
| # Re-hash the root filesystem and use the table for dm-verity. |
| # We extract the parameters for verification from the kernel |
| # partition, but we regenerate and reappend the hash tree to |
| # keep the updater from needing to manage them explicitly. |
| # Instead, rootfs integrity will be validated on next boot through |
| # the verified kernel configuration. |
| selected_kernel=$(( (newimg + 1) * 2)) # part 2 or 4 |
| kernel_image="${FLAGS_kernel_image}" |
| if [ -z "${FLAGS_kernel_image}" ]; then |
| KERN_LOOP=$(setup_dst_loop ${selected_kernel}) |
| kernel_image="${KERN_LOOP}" |
| fi |
| |
| # Grab the contents of dm="" and trim off the preamble. |
| # TODO(kwaters): dump_kernel_config segfaults on ARM. We use this to know that |
| # we need to scrape the u-boot header. This needs to be cleaned up once |
| # verified root works on the ARM kernel. |
| DUMP_KERNEL_CONFIG="${INSTALL_ROOT}"/usr/bin/dump_kernel_config |
| table= |
| if ! KERNEL_CONFIG=$(sudo "${DUMP_KERNEL_CONFIG}" "${kernel_image}"); then |
| # FIXME(kwaters): Terrible hack to extract the kernel config from the uboot |
| # header. There is a 64 byte u-boot header, followed by the size of the |
| # script as a uint32_t, followed by (uint32_t)0. So, the script itself |
| # starts 72 bytes into the image. |
| dd if="${kernel_image}" of="${tempfile}" bs=1 count=512 skip=72 |
| KERNEL_CONFIG=$(sed -n '/setenv bootargs/{s/setenv bootargs *//;p}' \ |
| "${tempfile}") |
| fi |
| |
| # Use the root to determine if verification should be happening. |
| kernel_root="$(echo "${KERNEL_CONFIG}" | sed -e 's/.*root=\([^ ]*\).*/\1/g')" |
| # Unless specified, the kernel root should dictate whether we are verified |
| # or not. |
| if [ ${FLAGS_override_kernel_options} -eq ${FLAGS_FALSE} ]; then |
| if [ "${kernel_root}" != "/dev/dm-0" ]; then |
| FLAGS_enable_rootfs_verification=${FLAGS_FALSE} |
| FLAGS_ignore_bad_verification=${FLAGS_TRUE} |
| else |
| FLAGS_enable_rootfs_verification=${FLAGS_TRUE} |
| FLAGS_ignore_bad_verification=${FLAGS_FALSE} |
| fi |
| fi |
| |
| # Extract the dm-verity target options. |
| kernel_cfg="$(echo "${KERNEL_CONFIG}" | sed -e 's/.*dm="\([^"]*\)".*/\1/g' | |
| cut -f2- -d,)" |
| verity_preamble="$(echo "${KERNEL_CONFIG}" | |
| sed -e 's/.*dm="\([^"]*\)".*/\1/g' | |
| cut -f1 -d,)" |
| rootfs_sectors=$(echo ${kernel_cfg} | cut -f2 -d' ') |
| verity_depth=$(echo ${kernel_cfg} | cut -f7 -d' ') |
| verity_algorithm=$(echo ${kernel_cfg} | cut -f8 -d' ') |
| |
| rootfs_image="${FLAGS_rootfs_image}" |
| selected_root=$((selected_kernel + 1)) |
| if [ -z "${rootfs_image}" ]; then |
| ROOT_LOOP=$(setup_dst_loop ${selected_root}) |
| rootfs_image="${ROOT_LOOP}" |
| fi |
| |
| # Update the root filesystem to ensure it can be mounted if rw access is |
| # expected. We never disable it going back to verification here since |
| # we don't want to change a pre-created image at all if it is verified. |
| if [ ${FLAGS_enable_rootfs_verification} -eq ${FLAGS_FALSE} ]; then |
| enable_rw_mount "${rootfs_image}" |
| fi |
| |
| # Compute the rootfs hash tree and dump it in the tempfile. |
| # TODO(wad) take preamble from dump_kernel_config. |
| VERITY="${INSTALL_ROOT}"/bin/verity |
| table="${verity_preamble},"$(sudo "${VERITY}" create \ |
| ${verity_depth} \ |
| "${verity_algorithm}" \ |
| "${rootfs_image}" \ |
| $((rootfs_sectors / 8)) \ |
| ${tempfile}) |
| |
| expected_hash=$(echo ${kernel_cfg} | cut -f9 -d' ') |
| generated_hash=$(echo ${table} | cut -f2- -d, | cut -f9 -d' ') |
| |
| if [ ${FLAGS_ignore_bad_verification} -eq ${FLAGS_FALSE} ]; then |
| if [ "${expected_hash}" != "${generated_hash}" ]; then |
| echo "Root filesystem has been modified unexpectedly!" 1>&2 |
| echo "Expected ${expected_hash} != ${generated_hash}" 1>&2 |
| # It doesn't make sense to exit here since the root filesystem |
| # is already in place. Might as well get everything in sync. |
| EXIT_CODE=1 |
| fi |
| fi |
| |
| if [ -n "${newimg:-}" ]; then |
| # This will usually be ${rootfs_image} except for cases where |
| # FLAGS_dst and FLAGS_rootfs_image diverge. |
| dst_part=$(make_partition_dev ${FLAGS_dst} ${selected_root}) |
| # Replace the placeholders from the verity tool. |
| table="$(echo "$table" | |
| sed -s "s|ROOT_DEV|${dst_part}|g;s|HASH_DEV|${dst_part}|g")" |
| fi |
| # Overwrite the appended hashes with the new ones. |
| sudo dd if=${tempfile} of="${rootfs_image}" \ |
| bs=512 seek=${rootfs_sectors} conv=notrunc |
| |
| # Make the change |
| if [ -z "$FLAGS_update_grub_cfg" ]; then |
| FLAGS_update_grub_cfg=${mountpoint}/efi/boot/grub.cfg |
| fi |
| if [ -n "${newimg:-}" -a -s "$FLAGS_update_grub_cfg" ]; then |
| grubid=${newimg} |
| test ${FLAGS_enable_rootfs_verification} -eq ${FLAGS_TRUE} && \ |
| grubid=$((newimg+2)) |
| echo "Updating the verified boot config and grub boot default: $grubid" |
| sed -e "s/^set default=.*/set default=${grubid}/" \ |
| -e "s|DMTABLE${newimg_letter}|${table}|g" \ |
| $FLAGS_update_grub_cfg > ${tempfile} |
| # TODO(wad) Break grub.cfg into multiple files if possible. |
| # Because grub.cfg is monolithic when we update from a templated |
| # copy, the inactive image will be unpopulated since we do this |
| # once per call to setimage. |
| |
| sudo mkdir -p "${mountpoint}/efi/boot" |
| sudo cp ${tempfile} ${mountpoint}/efi/boot/grub.cfg |
| fi |
| |
| # Rinse and repeat for the Legacy BIOS bootloader (syslinux) |
| syslinux_root_cfg=${mountpoint}/syslinux/root.${newimg_letter}.cfg |
| if [ -z "$FLAGS_update_syslinux_cfg" ]; then |
| # Update the root.X.cfg in place if none given. |
| # Note that the replacement variables may not be intact. |
| FLAGS_update_syslinux_cfg=$syslinux_root_cfg |
| fi |
| if [ -n "${newimg:-}" -a -s "$FLAGS_update_syslinux_cfg" ]; then |
| echo "Updating default syslinux target to $newimg" |
| target_hd=chromeos-hd |
| test ${FLAGS_enable_rootfs_verification} -eq ${FLAGS_TRUE} && \ |
| target_hd=chromeos-vhd |
| |
| echo "DEFAULT ${target_hd}.${newimg_letter}" > ${tempfile} |
| sudo cp ${tempfile} ${mountpoint}/syslinux/default.cfg |
| |
| vmlinuz_tgt=${mountpoint}/syslinux/vmlinuz.${newimg_letter} |
| |
| # 3 and 5 are the current rootfs a and b partitions |
| DST_PART_A=$(make_partition_dev ${FLAGS_dst} 3) |
| DST_PART_B=$(make_partition_dev ${FLAGS_dst} 5) |
| sed -e "s|HDROOTA|${DST_PART_A}|g;s|HDROOTB|${DST_PART_B}|g" \ |
| -e "s|DMTABLEA|${table}|g;s|DMTABLEB|${table}|g" \ |
| $FLAGS_update_syslinux_cfg > ${tempfile} |
| # Even though we replace both A and B, we're only updating the file |
| # that is relevant to the setimage request so we won't put an invalid |
| # value for the old root nor will the old root.cfg be broken. |
| sudo cp ${tempfile} $syslinux_root_cfg |
| |
| # We only need to do this for syslinux since grub can read the rootfs. |
| if [ -n "$FLAGS_update_vmlinuz" ]; then |
| echo "Updating vmlinuz..." |
| sudo cp -fL "$FLAGS_update_vmlinuz" "$vmlinuz_tgt" |
| fi |
| fi |
| |
| # Lather, rinse and repeat for Das U-Boot |
| if [ -s "${INSTALL_ROOT}/boot/boot-A.scr.uimg" ]; then |
| cp "${INSTALL_ROOT}/boot/boot-${newimg_letter}.scr.uimg" \ |
| "${mountpoint}/u-boot/boot.scr.uimg" |
| fi |
| |
| set +e # just grep oneliners and cleanup from here on out. |
| |
| echo "Current EFI boot default is:" |
| # Print the [new] default choice |
| grep -qs '^set default=0' ${mountpoint}/efi/boot/grub.cfg && echo "A" |
| grep -qs '^set default=1' ${mountpoint}/efi/boot/grub.cfg && echo "B" |
| grep -qs '^set default=2' ${mountpoint}/efi/boot/grub.cfg && echo "A (verified)" |
| grep -qs '^set default=3' ${mountpoint}/efi/boot/grub.cfg && echo "B (verified)" |
| |
| echo "Current legacy boot default is:" |
| # Print the [new] default choice |
| grep -qs 's-hd\.A$' ${mountpoint}/syslinux/default.cfg && echo "A" |
| grep -qs 's-hd\.B$' ${mountpoint}/syslinux/default.cfg && echo "B" |
| grep -qs 'vhd\.A$' ${mountpoint}/syslinux/default.cfg && echo "A (verified)" |
| grep -qs 'vhd\.B$' ${mountpoint}/syslinux/default.cfg && echo "B (verified)" |
| |
| exit $EXIT_CODE |