#!/bin/bash # # 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. # # Script which ensures that a given image has an up-to-date # kernel partition, rootfs integrity hashes, and legacy bootloader configs. # --- BEGIN COMMON.SH BOILERPLATE --- # Load common CrOS utilities. Inside the chroot this file is installed in # /usr/lib/crosutils. Outside the chroot we find it relative to the script's # location. find_common_sh() { local common_paths=("$(dirname "$(readlink -f "$0")")/.." /usr/lib/crosutils) local path SCRIPT_ROOT="${common_paths[0]}" for path in "${common_paths[@]}"; do if [ -r "${path}/common.sh" ]; then SCRIPT_ROOT="${path}" break fi done } find_common_sh . "${SCRIPT_ROOT}/common.sh" || exit 1 # --- END COMMON.SH BOILERPLATE --- # Need to be inside the chroot to load chromeos-common.sh assert_inside_chroot # Load functions and constants for chromeos-install . /usr/lib/installer/chromeos-common.sh || exit 1 . "${SCRIPTS_DIR}/build_library/build_image_util.sh" || exit 1 switch_to_strict_mode if [ $# -lt 2 ]; then echo "Usage: ${0} /PATH/TO/IMAGE IMAGE.BIN [shflags overrides]" exit 1 fi IMAGE_DIR="$(readlink -f "${1}")" BOOT_DESC_FILE="${IMAGE_DIR}/boot.desc" IMAGE="${IMAGE_DIR}/${2}" shift shift FLAG_OVERRIDES="${@}" if [ ! -r "${BOOT_DESC_FILE}" ]; then warn "${BOOT_DESC_FILE} cannot be read!" warn "Falling back to command line parsing" BOOT_DESC="${@}" else BOOT_DESC="$(cat ${BOOT_DESC_FILE} | tr -s '\n' ' ')" info "Boot-time configuration for $(dirname "${IMAGE}"): " cat ${BOOT_DESC_FILE} | while read line; do info " ${line}" done fi if [ ! -r "${IMAGE}" ]; then die "${IMAGE} cannot be read!" fi locate_gpt set +e # Now parse the build settings from ${OUTPUT_DIR}/boot.desc DEFINE_string output_dir "/tmp" \ "Directory to place output in." DEFINE_string image "chromiumos_base.img" \ "Full path to the chromiumos image to make bootable." DEFINE_string arch "x86" \ "Architecture to make bootable for: arm, x86, or amd64" DEFINE_string usb_disk "/dev/sdb3" \ "Path syslinux should use to do a usb boot." DEFINE_boolean cleanup_dirs ${FLAGS_TRUE} \ "Whether the mount dirs should be removed on completion." DEFINE_string boot_args "noinitrd" \ "Additional boot arguments to pass to the commandline" DEFINE_integer rootfs_size 720 \ "rootfs filesystem size in MBs." # ceil(0.1 * rootfs_size) is a good minimum. DEFINE_integer rootfs_hash_pad 8 \ "MBs reserved at the end of the rootfs image." DEFINE_string rootfs_hash "/tmp/rootfs.hash" \ "Path where the rootfs hash should be stored." DEFINE_boolean enable_rootfs_verification ${FLAGS_FALSE} \ "Default all bootloaders to use kernel-based root fs integrity checking." DEFINE_integer verity_error_behavior 2 \ "Kernel verified boot error behavior (0: I/O errors, 1: reboot, 2: nothing)" DEFINE_integer verity_max_ios 1024 \ "Number of outstanding I/O operations dm-verity caps at." DEFINE_string verity_algorithm "sha1" \ "Cryptographic hash algorithm used for kernel vboot." DEFINE_string verity_salt "" \ "Salt for rootfs hash tree." DEFINE_string keys_dir "/usr/share/vboot/devkeys" \ "Directory containing the signing keys." DEFINE_string rootfs_mountpoint "/tmp/rootfs" \ "Path where the rootfs can be safely mounted" DEFINE_string statefulfs_mountpoint "/tmp/statefulfs" \ "Path where the statefulfs can be safely mounted" DEFINE_string espfs_mountpoint "/tmp/espfs" \ "Path where the espfs can be safely mounted" DEFINE_boolean use_dev_keys ${FLAGS_FALSE} \ "Use developer keys for signing. (Default: false)" DEFINE_boolean fsck_rootfs ${FLAGS_FALSE} \ "Check integrity of the rootfs on the modified image." # TODO(pkumar): Remove once known that no images are using this flag DEFINE_boolean crosbug12352_arm_kernel_signing ${FLAGS_FALSE} \ "This flag is deprecated but the bots still need parse old images." # TODO(sosa): Remove once known images no longer use this in their config. DEFINE_string arm_extra_bootargs "" "DEPRECATED FLAG. Do not use." DEFINE_boolean force_developer_mode ${FLAGS_FALSE} \ "Add cros_debug to boot args." DEFINE_boolean enable_squashfs ${FLAGS_FALSE} \ "Make the rootfs of the image squashfs." DEFINE_string squash_sort_file "" \ "Specify the priority of files when squashing the rootfs." # Parse the boot.desc and any overrides eval set -- "${BOOT_DESC} ${FLAG_OVERRIDES}" FLAGS "${@}" || exit 1 [ -z "${FLAGS_verity_salt}" ] && FLAGS_verity_salt=$(make_salt) # Only now can we die on error. shflags functions leak non-zero error codes, # so will die prematurely if 'switch_to_strict_mode' is specified before now. switch_to_strict_mode -u # $1 - Directory where developer rootfs is mounted. # $2 - Directory where developer stateful_partition is mounted. # $3 - Directory where the ESP partition is mounted. mount_gpt_cleanup() { local rootfs="${1-$FLAGS_rootfs_mountpoint}" local statefs="${2-$FLAGS_statefulfs_mountpoint}" local espfs="${3-$FLAGS_espfs_mountpoint}" "${SCRIPTS_DIR}/mount_gpt_image.sh" \ -u -r "${rootfs}" -s "${statefs}" -e "${espfs}" } make_image_bootable() { local image="$1" local use_dev_keys= # Default to non-verified cros_root="PARTUUID=%U/PARTNROFF=1" if [[ ${FLAGS_enable_rootfs_verification} -eq ${FLAGS_TRUE} ]]; then cros_root=/dev/dm-0 fi trap "mount_gpt_cleanup" EXIT "${SCRIPTS_DIR}/mount_gpt_image.sh" --from "$(dirname "${image}")" \ --image "$(basename ${image})" -r "${FLAGS_rootfs_mountpoint}" \ -s "${FLAGS_statefulfs_mountpoint}" # The rootfs should never be mounted rw again after this point without # re-calling make_image_bootable. sudo mount -o remount,ro "${FLAGS_rootfs_mountpoint}" # Newer `mount` will decode the filename backing the loop device, # so we need to dig deeper and find the answer ourselves. root_dev=$(awk -v mnt="${FLAGS_rootfs_mountpoint}" \ '$2 == mnt { print $1 }' /proc/mounts) # Make the filesystem un-mountable as read-write. # mount_gpt_image.sh will undo this as needed. # TODO(wad) make sure there is parity in the signing scripts. if [ ${FLAGS_enable_rootfs_verification} -eq ${FLAGS_TRUE} ]; then # TODO(wad) this would be a good place to reset any other ext2 metadata. warn "Disabling r/w mount of the root filesystem" disable_rw_mount "$root_dev" fi if [ ${FLAGS_use_dev_keys} -eq ${FLAGS_TRUE} ]; then use_dev_keys="--use_dev_keys" fi if [ ${FLAGS_force_developer_mode} -eq ${FLAGS_TRUE} ]; then FLAGS_boot_args="${FLAGS_boot_args} cros_debug" fi local squash_sort_flag= if [ -n "${FLAGS_squash_sort_file}" ]; then squash_sort_flag="-sort ${FLAGS_squash_sort_file}" fi if [ $FLAGS_enable_squashfs -eq $FLAGS_TRUE ]; then local squashfs_img="${FLAGS_output_dir}/squashfs.image" sudo mksquashfs "${FLAGS_rootfs_mountpoint}" ${squashfs_img} -comp lzo \ -noI -noF -ef ${SCRIPTS_DIR}/exclude-list -wildcards ${squash_sort_flag} root_dev=$squashfs_img fi # Builds the kernel partition image. The temporary files are kept around # so that we can perform a load_kernel_test later on the final image. ${SCRIPTS_DIR}/build_kernel_image.sh \ --arch="${FLAGS_arch}" \ --to="${FLAGS_output_dir}/vmlinuz.image" \ --hd_vblock="${FLAGS_output_dir}/vmlinuz_hd.vblock" \ --vmlinuz="${FLAGS_rootfs_mountpoint}/boot/vmlinuz" \ --working_dir="${FLAGS_output_dir}" \ --boot_args="${FLAGS_boot_args}" \ --keep_work \ --rootfs_image=${root_dev} \ --rootfs_hash=${FLAGS_rootfs_hash} \ --verity_hash_alg=${FLAGS_verity_algorithm} \ --verity_max_ios=${FLAGS_verity_max_ios} \ --verity_error_behavior=${FLAGS_verity_error_behavior} \ --verity_salt=${FLAGS_verity_salt} \ --root=${cros_root} \ --keys_dir="${FLAGS_keys_dir}" \ ${use_dev_keys} # Check the size of kernel image and issue warning when image size is # near the limit. local kernel_image_size=$(stat -c '%s' ${FLAGS_output_dir}/vmlinuz.image) info "Kernel image size is ${kernel_image_size} bytes." if [[ ${kernel_image_size} -gt $((16 * 1024 * 1024)) ]]; then die "Kernel image is larger than 16 MB." elif [[ ${kernel_image_size} -gt $((14 * 1024 * 1024)) ]]; then warn "Kernel image is larger than 14 MB. Limit is 16 MB." fi local rootfs_hash_size=$(stat -c '%s' ${FLAGS_rootfs_hash}) info "Appending rootfs.hash (${rootfs_hash_size} bytes) to the root fs" if [[ ${rootfs_hash_size} -gt $((FLAGS_rootfs_hash_pad * 1024 * 1024)) ]] then die "--rootfs_hash_pad reserves less than the needed ${rootfs_hash_size}" fi # Unfortunately, mount_gpt_image uses mount and not losetup to create the # loop devices. This means that they are not the correct size. We have to # write directly to the image to append the hash tree data. local hash_offset="$(partoffset ${image} 3)" if [ $FLAGS_enable_squashfs -eq $FLAGS_TRUE ]; then rootfs_file_size=$(stat -c '%s' ${root_dev}) hash_offset=$((hash_offset + (${rootfs_file_size} / 512))) else hash_offset=$((hash_offset + ((1024 * 1024 * ${FLAGS_rootfs_size}) / 512))) fi sudo dd bs=512 \ seek=${hash_offset} \ if="${FLAGS_rootfs_hash}" \ of="${image}" \ conv=notrunc # Move the verification block needed for the hard disk install to the # stateful partition. Mount stateful fs, copy file, and umount fs. # In original CL: http://codereview.chromium.org/2868044, this was done in # create_base_image(). However, it could break the build if it is a clean # build because vmlinuz_hd.vblock hasn't been created by build_kernel_image.sh sudo cp "${FLAGS_output_dir}/vmlinuz_hd.vblock" \ "${FLAGS_statefulfs_mountpoint}" # START_KERN_A is set by the first call to install the gpt. local koffset="$(partoffset ${image} 2)" sudo dd if="${FLAGS_output_dir}/vmlinuz.image" of="${image}" \ conv=notrunc bs=512 seek=${koffset} # Update the bootloaders. The EFI system partition will be updated. local kernel_part= local usb_disk="${FLAGS_usb_disk}" # We should update the esp in place in the image. local bootloader_to="${image}" local esp_offset="$(partoffset ${image} 12)" esp_offset=$((esp_offset * 512)) # sectors to bytes local esp_size="$(partsize ${image} 12)" esp_size=$((esp_size * 512)) # sectors to bytes local bootloader_to_flags="--to_offset=${esp_offset} --to_size=${esp_size}" if [[ "${FLAGS_arch}" = "x86" || "${FLAGS_arch}" = "amd64" ]]; then # Use the kernel partition to acquire configuration flags. kernel_part="--kernel_partition='${FLAGS_output_dir}/vmlinuz.image'" # Install syslinux on the EFI System Partition. kernel_part="${kernel_part} --install_syslinux" elif [[ "${FLAGS_arch}" = "arm" ]]; then # These flags are not used for ARM update_bootloaders.sh kernel_part="" fi # Update partition 12 ${SCRIPTS_DIR}/update_bootloaders.sh \ --arch=${FLAGS_arch} \ --to="${bootloader_to}" \ --from="${FLAGS_rootfs_mountpoint}"/boot \ --vmlinuz="${FLAGS_rootfs_mountpoint}"/boot/vmlinuz \ --usb_disk="${usb_disk}" \ ${bootloader_to_flags} \ $kernel_part # We don't need to keep these files around anymore. sudo rm "${FLAGS_rootfs_hash}" "${FLAGS_output_dir}/vmlinuz.image" \ "${FLAGS_output_dir}/vmlinuz_hd.vblock" trap - EXIT ${SCRIPTS_DIR}/mount_gpt_image.sh -u -r "${FLAGS_rootfs_mountpoint}" \ -s "${FLAGS_statefulfs_mountpoint}" # I can only copy the squashfs image to the image only when it is umounted. if [ $FLAGS_enable_squashfs -eq $FLAGS_TRUE ]; then # copy the squashfs image to the partition info "copy the squashfs to the partition" local part_offset="$(partoffset ${image} 3)" sudo dd bs=512 if="${squashfs_img}" of="${image}" \ seek=${part_offset} conv=notrunc sudo rm "${squashfs_img}" fi } verify_image_rootfs() { local image=$1 local rootfs_offset="$(partoffset ${image} 3)" local rootfs_tmp_file=$(mktemp) trap "rm ${rootfs_tmp_file}" EXIT sudo dd if="${image}" of="${rootfs_tmp_file}" bs=512 skip="${rootfs_offset}" # This flips the read-only compatibility flag, so that # e2fsck does not complain about unknown file system capabilities. enable_rw_mount "${rootfs_tmp_file}" info "Running e2fsck to check root file system for errors" sudo e2fsck -fn "${rootfs_tmp_file}" || die "Root file system has errors, please ensure boot.desc and/or \ command line parameters are correct" } # Store output and temporary files next to image. FLAGS_output_dir="${IMAGE_DIR}" FLAGS_rootfs_hash="${IMAGE_DIR}/rootfs.hash" FLAGS_rootfs_mountpoint="${IMAGE_DIR}/rootfs_dir" FLAGS_statefulfs_mountpoint="${IMAGE_DIR}/stateful_dir" FLAGS_espfs_mountpoint="${IMAGE_DIR}/esp" # Create the directories if they don't exist. mkdir -p ${FLAGS_rootfs_mountpoint} mkdir -p ${FLAGS_statefulfs_mountpoint} mkdir -p ${FLAGS_espfs_mountpoint} make_image_bootable "${IMAGE}" # We can't verify the image if squashfs is enabled because the kernel # on the host does not support squashfs with LZO if [ ${FLAGS_fsck_rootfs} -eq ${FLAGS_TRUE} \ -a ${FLAGS_enable_squashfs} -eq ${FLAGS_FALSE} ]; then verify_image_rootfs "${IMAGE}" fi if [ ${FLAGS_cleanup_dirs} -eq ${FLAGS_TRUE} ]; then rmdir ${FLAGS_rootfs_mountpoint} rmdir ${FLAGS_statefulfs_mountpoint} rmdir ${FLAGS_espfs_mountpoint} fi