flatcar-scripts/mod_image_for_recovery.sh
Nick Sanders b795deba76 Cherry-pick: ARM: enable kernel signing by default
This commit is a part of transition to enable ARM kernel signing. It is
at first an option that is enabled manually, and then (in this commit)
enabled by default. After more tests, the scripts that generate unsigned
ARM kernel partition will probably be removed.

BUG=chromium-os:12352
TEST=./build_image && load_kernel_test -b 2 /path/to/chromiumos_image.bin /usr/share/vboot/devkeys/recovery_key.vbpubk

Change-Id: I7d4ecc566f9c5cc0106a7af59255fc63fdfe017a
Tested-by: Che-Liang Chiou <clchiou@chromium.org>
Reviewed-by: Tom Wai-Hong Tam <waihong@chromium.org>
Reviewed-by: Che-Liang Chiou <clchiou@chromium.org>
Reviewed-by: Rong Chang <rongchang@chromium.org>
Tested-by: Tom Wai-Hong Tam <waihong@chromium.org>
Reviewed-on: http://gerrit.chromium.org/gerrit/1319
Tested-by: Nick Sanders <nsanders@chromium.org>
2011-05-25 02:56:21 -07:00

461 lines
14 KiB
Bash
Executable File

#!/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.
# This script modifies a base image to act as a recovery installer.
# If no kernel image is supplied, it will build a devkeys signed recovery
# kernel. Alternatively, a signed recovery kernel can be used to
# create a Chromium OS recovery image.
# --- 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=(/usr/lib/crosutils $(dirname "$(readlink -f "$0")"))
local path
SCRIPT_ROOT=
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" || (echo "Unable to load 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" || \
die "Unable to load /usr/lib/installer/chromeos-common.sh"
# For update_partition_table
. "${SCRIPT_ROOT}/resize_stateful_partition.sh" || \
die "Unable to load ${SCRIPT_ROOT}/resize_stateful_partition.sh"
get_default_board
DEFINE_string board "$DEFAULT_BOARD" "Board for which the image was built" b
DEFINE_integer statefulfs_sectors 4096 \
"Number of sectors to use for the stateful filesystem when minimizing"
# Skips the build steps and just does the kernel swap.
DEFINE_string kernel_image "" \
"Path to a pre-built recovery kernel"
DEFINE_string kernel_outfile "" \
"Filename and path to emit the kernel outfile to. \
If empty, emits to IMAGE_DIR."
DEFINE_string image "" "Path to the image to use"
DEFINE_string to "" \
"Path to the image to create. If empty, defaults to \
IMAGE_DIR/recovery_image.bin."
DEFINE_boolean kernel_image_only $FLAGS_FALSE \
"Emit the recovery kernel image only"
DEFINE_boolean sync_keys $FLAGS_TRUE \
"Update the kernel to be installed with the vblock from stateful"
DEFINE_boolean minimize_image $FLAGS_TRUE \
"Decides if the original image is used or a minimal recovery image is \
created."
DEFINE_boolean modify_in_place $FLAGS_FALSE \
"Modifies the source image in place. This cannot be used with \
--minimize_image."
DEFINE_integer jobs -1 \
"How many packages to build in parallel at maximum." j
DEFINE_string build_root "/build" \
"The root location for board sysroots."
DEFINE_string rootfs_hash "/tmp/rootfs.hash" \
"Path where the rootfs hash should be stored."
DEFINE_boolean verbose $FLAGS_FALSE \
"Log all commands to stdout." v
# Keep in sync with build_image.
DEFINE_string keys_dir "/usr/share/vboot/devkeys" \
"Directory containing the signing keys."
# TODO(clchiou): Remove this flag after arm verified boot is stable
DEFINE_boolean crosbug12352_arm_kernel_signing ${FLAGS_TRUE} \
"Sign kernel partition for ARM images (temporary hack)."
# Parse command line
FLAGS "$@" || exit 1
eval set -- "${FLAGS_ARGV}"
if [ $FLAGS_verbose -eq $FLAGS_FALSE ]; then
exec 2>/dev/null
# Redirecting to stdout instead of stderr since
# we silence stderr above.
die() {
echo -e "${V_BOLD_RED}ERROR : $1${V_VIDOFF}"
exit 1
}
fi
set -x # Make debugging with -v easy.
EMERGE_CMD="emerge"
EMERGE_BOARD_CMD="emerge-${FLAGS_board}"
# No board, no default and no image set then we can't find the image
if [ -z $FLAGS_image ] && [ -z $FLAGS_board ] ; then
setup_board_warning
die "mod_image_for_recovery failed. No board set and no image set"
fi
# We have a board name but no image set. Use image at default location
if [ -z $FLAGS_image ] ; then
IMAGES_DIR="${DEFAULT_BUILD_ROOT}/images/${FLAGS_board}"
FILENAME="chromiumos_image.bin"
FLAGS_image="${IMAGES_DIR}/$(ls -t $IMAGES_DIR 2>&-| head -1)/${FILENAME}"
fi
# Turn path into an absolute path.
FLAGS_image=`eval readlink -f ${FLAGS_image}`
# Abort early if we can't find the image
if [ ! -f $FLAGS_image ] ; then
echo "No image found at $FLAGS_image"
exit 1
fi
# What cross-build are we targeting?
. "${FLAGS_build_root}/${FLAGS_board}/etc/make.conf.board_setup"
# Figure out ARCH from the given toolchain.
# TODO: Move to common.sh as a function after scripts are switched over.
TC_ARCH=$(echo "${CHOST}" | awk -F'-' '{ print $1 }')
case "${TC_ARCH}" in
arm*)
ARCH="arm"
error "ARM recovery mode is still in the works. Use a normal image for now."
;;
*86)
ARCH="x86"
;;
*x86_64)
ARCH="amd64"
;;
*)
error "Unable to determine ARCH from toolchain: ${CHOST}"
exit 1
esac
if [[ ${FLAGS_crosbug12352_arm_kernel_signing} -eq ${FLAGS_TRUE} ]]; then
crosbug12352_flag="--crosbug12352_arm_kernel_signing"
else
crosbug12352_flag="--nocrosbug12352_arm_kernel_signing"
fi
get_install_vblock() {
# If it exists, we need to copy the vblock over to stateful
# This is the real vblock and not the recovery vblock.
local stateful_offset=$(partoffset "$FLAGS_image" 1)
local stateful_mnt=$(mktemp -d)
local out=$(mktemp)
set +e
sudo mount -o ro,loop,offset=$((stateful_offset * 512)) \
"$FLAGS_image" $stateful_mnt
sudo cp "$stateful_mnt/vmlinuz_hd.vblock" "$out"
sudo chown $USER "$out"
sudo umount -d "$stateful_mnt"
rmdir "$stateful_mnt"
set -e
echo "$out"
}
emerge_recovery_kernel() {
echo "Emerging custom recovery initramfs and kernel"
local emerge_flags="-uDNv1 --usepkg=n --selective=n"
$EMERGE_BOARD_CMD \
$emerge_flags --binpkg-respect-use=y \
chromeos-initramfs || die "no initramfs"
USE="fbconsole initramfs" $EMERGE_BOARD_CMD \
$emerge_flags --binpkg-respect-use=y \
virtual/kernel
}
create_recovery_kernel_image() {
local sysroot="${FLAGS_build_root}/${FLAGS_board}"
local vmlinuz="$sysroot/boot/vmlinuz"
local root_dev=$(sudo losetup -f)
local root_offset=$(partoffset "$FLAGS_image" 3)
local root_size=$(partsize "$FLAGS_image" 3)
sudo losetup \
-o $((root_offset * 512)) \
--sizelimit $((root_size * 512)) \
"$root_dev" \
"$FLAGS_image"
trap "sudo losetup -d $root_dev" EXIT
cros_root=/dev/sd%D%P # only used for non-verified images
if [[ "${ARCH}" = "arm" ]]; then
cros_root='/dev/${devname}${rootpart}'
fi
if grep -q enable_rootfs_verification "${IMAGE_DIR}/boot.desc"; then
cros_root=/dev/dm-0
fi
# TODO(wad) LOAD FROM IMAGE KERNEL AND NOT BOOT.DESC
local verity_args=$(grep -- '--verity_' "${IMAGE_DIR}/boot.desc")
# Convert the args to the right names and clean up extra quoting.
# TODO(wad) just update these everywhere
verity_args=$(echo $verity_args | sed \
-e 's/verity_algorithm/verity_hash_alg/g' \
-e 's/"//g')
# Tie the installed recovery kernel to the final kernel. If we don't
# do this, a normal recovery image could be used to drop an unsigned
# kernel on without a key-change check.
# Doing this here means that the kernel and initramfs creation can
# be done independently from the image to be modified as long as the
# chromeos-recovery interfaces are the same. It allows for the signer
# to just compute the new hash and update the kernel command line during
# recovery image generation. (Alternately, it means an image can be created,
# modified for recovery, then passed to a signer which can then sign both
# partitions appropriately without needing any external dependencies.)
local kern_offset=$(partoffset "$FLAGS_image" 2)
local kern_size=$(partsize "$FLAGS_image" 2)
local kern_tmp=$(mktemp)
local kern_hash=
dd if="$FLAGS_image" bs=512 count=$kern_size \
skip=$kern_offset of="$kern_tmp" 1>&2
# We're going to use the real signing block.
if [ $FLAGS_sync_keys -eq $FLAGS_TRUE ]; then
dd if="$INSTALL_VBLOCK" of="$kern_tmp" conv=notrunc 1>&2
fi
local kern_hash=$(sha1sum "$kern_tmp" | cut -f1 -d' ')
rm "$kern_tmp"
# TODO(wad) add FLAGS_boot_args support too.
${SCRIPTS_DIR}/build_kernel_image.sh \
--arch="${ARCH}" \
--to="$RECOVERY_KERNEL_IMAGE" \
--hd_vblock="$RECOVERY_KERNEL_VBLOCK" \
--vmlinuz="$vmlinuz" \
--working_dir="${IMAGE_DIR}" \
--boot_args="panic=60 cros_recovery kern_b_hash=$kern_hash" \
--keep_work \
--rootfs_image=${root_dev} \
--rootfs_hash=${FLAGS_rootfs_hash} \
--root=${cros_root} \
--keys_dir="${FLAGS_keys_dir}" \
--nouse_dev_keys \
${crosbug12352_flag} \
${verity_args} 1>&2
sudo rm "$FLAGS_rootfs_hash"
sudo losetup -d "$root_dev"
trap - RETURN
# Update the EFI System Partition configuration so that the kern_hash check
# passes.
local efi_dev=$(sudo losetup -f)
local efi_offset=$(partoffset "$FLAGS_image" 12)
local efi_size=$(partsize "$FLAGS_image" 12)
sudo losetup \
-o $((efi_offset * 512)) \
--sizelimit $((efi_size * 512)) \
"$efi_dev" \
"$FLAGS_image"
local efi_dir=$(mktemp -d)
trap "sudo losetup -d $efi_dev && rmdir \"$efi_dir\"" EXIT
sudo mount "$efi_dev" "$efi_dir"
sudo sed -i -e "s/cros_legacy/cros_legacy kern_b_hash=$kern_hash/g" \
"$efi_dir/syslinux/usb.A.cfg" || true
# This will leave the hash in the kernel for all boots, but that should be
# safe.
sudo sed -i -e "s/cros_efi/cros_efi kern_b_hash=$kern_hash/g" \
"$efi_dir/efi/boot/grub.cfg" || true
sudo umount "$efi_dir"
sudo losetup -d "$efi_dev"
rmdir "$efi_dir"
trap - EXIT
}
install_recovery_kernel() {
local kern_a_offset=$(partoffset "$RECOVERY_IMAGE" 2)
local kern_a_size=$(partsize "$RECOVERY_IMAGE" 2)
local kern_b_offset=$(partoffset "$RECOVERY_IMAGE" 4)
local kern_b_size=$(partsize "$RECOVERY_IMAGE" 4)
if [ $kern_b_size -eq 1 ]; then
echo "Image was created with no KERN-B partition reserved!" 1>&2
echo "Cannot proceed." 1>&2
return 1
fi
# Backup original kernel to KERN-B
dd if="$RECOVERY_IMAGE" of="$RECOVERY_IMAGE" bs=512 \
count=$kern_a_size \
skip=$kern_a_offset \
seek=$kern_b_offset \
conv=notrunc
# We're going to use the real signing block.
if [ $FLAGS_sync_keys -eq $FLAGS_TRUE ]; then
dd if="$INSTALL_VBLOCK" of="$RECOVERY_IMAGE" bs=512 \
seek=$kern_b_offset \
conv=notrunc
fi
# Install the recovery kernel as primary.
dd if="$RECOVERY_KERNEL_IMAGE" of="$RECOVERY_IMAGE" bs=512 \
seek=$kern_a_offset \
count=$kern_a_size \
conv=notrunc
# Set the 'Success' flag to 1 (to prevent the firmware from updating
# the 'Tries' flag).
sudo $GPT add -i 2 -S 1 "$RECOVERY_IMAGE"
# Repeat for the legacy bioses.
# Replace vmlinuz.A with the recovery version
local sysroot="${FLAGS_build_root}/${FLAGS_board}"
local vmlinuz="$sysroot/boot/vmlinuz"
local failed=0
if [ "$ARCH" = "x86" ]; then
# There is no syslinux on ARM, so this copy only makes sense for x86.
set +e
local esp_offset=$(partoffset "$RECOVERY_IMAGE" 12)
local esp_mnt=$(mktemp -d)
sudo mount -o loop,offset=$((esp_offset * 512)) "$RECOVERY_IMAGE" "$esp_mnt"
sudo cp "$vmlinuz" "$esp_mnt/syslinux/vmlinuz.A" || failed=1
sudo umount -d "$esp_mnt"
rmdir "$esp_mnt"
set -e
fi
if [ $failed -eq 1 ]; then
echo "Failed to copy recovery kernel to ESP"
return 1
fi
return 0
}
maybe_resize_stateful() {
# If we're not minimizing, then just copy and go.
if [ $FLAGS_minimize_image -eq $FLAGS_FALSE ]; then
if [ "$FLAGS_image" != "$RECOVERY_IMAGE" ]; then
cp "$FLAGS_image" "$RECOVERY_IMAGE"
fi
return 0
fi
# Rebuild the image with a 1 sector stateful partition
local err=0
local small_stateful=$(mktemp)
dd if=/dev/zero of="$small_stateful" bs=512 \
count=${FLAGS_statefulfs_sectors} 1>&2
trap "rm $small_stateful" RETURN
# Don't bother with ext3 for such a small image.
/sbin/mkfs.ext2 -F -b 4096 "$small_stateful" 1>&2
# If it exists, we need to copy the vblock over to stateful
# This is the real vblock and not the recovery vblock.
local new_stateful_mnt=$(mktemp -d)
set +e
sudo mount -o loop $small_stateful $new_stateful_mnt
sudo cp "$INSTALL_VBLOCK" "$new_stateful_mnt/vmlinuz_hd.vblock"
sudo mkdir "$new_stateful_mnt/var"
sudo umount -d "$new_stateful_mnt"
rmdir "$new_stateful_mnt"
set -e
# Create a recovery image of the right size
# TODO(wad) Make the developer script case create a custom GPT with
# just the kernel image and stateful.
update_partition_table "$FLAGS_image" "$small_stateful" 4096 \
"$RECOVERY_IMAGE" 1>&2
return $err
}
cleanup() {
set +e
if [ "$FLAGS_image" != "$RECOVERY_IMAGE" ]; then
rm "$RECOVERY_IMAGE"
fi
rm "$INSTALL_VBLOCK"
}
# main process begins here.
set -e
set -u
IMAGE_DIR="$(dirname "$FLAGS_image")"
IMAGE_NAME="$(basename "$FLAGS_image")"
RECOVERY_IMAGE="${FLAGS_to:-$IMAGE_DIR/recovery_image.bin}"
RECOVERY_KERNEL_IMAGE=\
"${FLAGS_kernel_outfile:-${IMAGE_DIR}/recovery_vmlinuz.image}"
RECOVERY_KERNEL_VBLOCK="${RECOVERY_KERNEL_IMAGE}.vblock"
STATEFUL_DIR="$IMAGE_DIR/stateful_partition"
SCRIPTS_DIR=${SCRIPT_ROOT}
# Mounts gpt image and sets up var, /usr/local and symlinks.
# If there's a dev payload, mount stateful
# offset=$(partoffset "${FLAGS_from}/${filename}" 1)
# sudo mount ${ro_flag} -o loop,offset=$(( offset * 512 )) \
# "${FLAGS_from}/${filename}" "${FLAGS_stateful_mountpt}"
# If not, resize stateful to 1 sector.
#
if [ $FLAGS_kernel_image_only -eq $FLAGS_TRUE -a \
-n "$FLAGS_kernel_image" ]; then
die "Cannot use --kernel_image_only with --kernel_image"
fi
if [ $FLAGS_modify_in_place -eq $FLAGS_TRUE ]; then
if [ $FLAGS_minimize_image -eq $FLAGS_TRUE ]; then
die "Cannot use --modify_in_place and --minimize_image together."
fi
RECOVERY_IMAGE="${FLAGS_image}"
fi
echo "Creating recovery image from ${FLAGS_image}"
INSTALL_VBLOCK=$(get_install_vblock)
if [ -z "$INSTALL_VBLOCK" ]; then
die "Could not copy the vblock from stateful."
fi
if [ -z "$FLAGS_kernel_image" ]; then
emerge_recovery_kernel
create_recovery_kernel_image
echo "Recovery kernel created at $RECOVERY_KERNEL_IMAGE"
else
RECOVERY_KERNEL_IMAGE="$FLAGS_kernel_image"
fi
if [ $FLAGS_kernel_image_only -eq $FLAGS_TRUE ]; then
echo "Kernel emitted. Stopping there."
rm "$INSTALL_VBLOCK"
exit 0
fi
if [ $FLAGS_modify_in_place -eq $FLAGS_FALSE ]; then
rm "$RECOVERY_IMAGE" || true # Start fresh :)
fi
trap cleanup EXIT
maybe_resize_stateful # Also copies the image if needed.
install_recovery_kernel
echo "Recovery image created at $RECOVERY_IMAGE"
print_time_elapsed
trap - EXIT