From d995f4b04df46d9e9ac1be8d58ca961e94b783d1 Mon Sep 17 00:00:00 2001 From: David Zeuthen Date: Fri, 29 Jan 2016 16:59:17 -0500 Subject: [PATCH] Add support for Brillo Verified Boot. The following variables are introduced BOARD_BVB_ENABLE: can be set to true to build boot.img and system.img files compatible with Brillo Verfied Boot. BOARD_BVB_ROLLBACK_INDEX: can be set to an integer to use for the rollback index. BOARD_BVB_KEY_PATH, BOARD_BVB_ALGORITHM: If set, the former must be a path to the private key used to sign the boot image and the latter must be the algorithm to use. If unset, a test-key stored in the tree will be used. BOARD_BVB_MAKE_BOOT_IMAGE_ARGS: Extra options to pass to 'bvbtool make_boot_image'. BOARD_BVB_SIGN_BOOT_IMAGE_ARGS: Extra options to pass to 'bvbtool sign_boot_image'. BOARD_BVB_ADD_IMAGE_HASHES_ARGS: Extra options to pass to 'bvbtool add_image_hashes'. BOARD_CUSTOM_BVBTOOL: Can be set to specify what bvbtool program to use. The existing BOARD_KERNEL_CMDLINE variable is also used, as are existing kernel and initrd-related variables. Therefore, simply adding BOARD_BVB_ENABLE := true to an existing Makefile should do the trick. Bug: 26185038 TEST=Added 'BOARD_BVB_ENABLE := true' to hardware/bsp/intel/soc/edison/soc.mk and built an image and then ran bvbtool's info_boot_image and info_image_hashes commands on the resulting boot.img and system.img files and verified that the information was correct. Also ran 'm dist' and verified that the boot.img and system.img files in the resulting target_files.zip file had similar information. Change-Id: I08045ed8b0cbddc7c3acdd3a6f2c4bb75cb44bbc --- core/Makefile | 53 ++++++++ core/config.mk | 5 + tools/releasetools/add_img_to_target_files.py | 58 +++++++-- tools/releasetools/common.py | 119 +++++++++++++++++- 4 files changed, 224 insertions(+), 11 deletions(-) diff --git a/core/Makefile b/core/Makefile index 77b18b61c..6ec63e458 100644 --- a/core/Makefile +++ b/core/Makefile @@ -507,8 +507,28 @@ INTERNAL_BOOTIMAGE_ARGS := \ $(addprefix --second ,$(INSTALLED_2NDBOOTLOADER_TARGET)) \ --kernel $(INSTALLED_KERNEL_TARGET) +INTERNAL_BVBTOOL_MAKE_BOOT_IMAGE_ARGS := \ + --kernel $(INSTALLED_KERNEL_TARGET) \ + --rootfs_with_hashes $(PRODUCT_OUT)/system.img + +ifdef BOARD_BVB_ROLLBACK_INDEX +INTERNAL_BVBTOOL_MAKE_BOOT_IMAGE_ARGS += \ + --rollback_index $(BOARD_BVB_ROLLBACK_INDEX) +endif + +ifndef BOARD_BVB_KEY_PATH +# If key path isn't specified, use the 4096-bit test key. +INTERNAL_BVBTOOL_SIGN_BOOT_IMAGE_ARGS := --algorithm SHA256_RSA4096 \ + --key system/bvb/test/testkey_rsa4096.pem +else +INTERNAL_BVBTOOL_SIGN_BOOT_IMAGE_ARGS := \ + --algorithm $(BOARD_BVB_ALGORITHM) --key $(BOARD_BVB_KEY_PATH) +endif + + ifneq ($(BOARD_BUILD_SYSTEM_ROOT_IMAGE),true) INTERNAL_BOOTIMAGE_ARGS += --ramdisk $(INSTALLED_RAMDISK_TARGET) +INTERNAL_BVBTOOL_MAKE_BOOT_IMAGE_ARGS += --initrd $(INSTALLED_RAMDISK_TARGET) endif @@ -517,6 +537,7 @@ INTERNAL_BOOTIMAGE_FILES := $(filter-out --%,$(INTERNAL_BOOTIMAGE_ARGS)) BOARD_KERNEL_CMDLINE := $(strip $(BOARD_KERNEL_CMDLINE)) ifdef BOARD_KERNEL_CMDLINE INTERNAL_BOOTIMAGE_ARGS += --cmdline "$(BOARD_KERNEL_CMDLINE)" + INTERNAL_BVBTOOL_MAKE_BOOT_IMAGE_ARGS += --kernel_cmdline "$(BOARD_KERNEL_CMDLINE)" endif BOARD_KERNEL_BASE := $(strip $(BOARD_KERNEL_BASE)) @@ -542,6 +563,23 @@ ifneq ($(BOARD_BUILD_SYSTEM_ROOT_IMAGE),true) endif endif +ifeq ($(BOARD_BVB_ENABLE),true) + +$(INSTALLED_BOOTIMAGE_TARGET): $(BVBTOOL) $(INTERNAL_BOOTIMAGE_FILES) $(PRODUCT_OUT)/system.img + $(call pretty,"Target boot image: $@") + $(hide) $(BVBTOOL) make_boot_image $(INTERNAL_BVBTOOL_MAKE_BOOT_IMAGE_ARGS) $(BOARD_BVB_MAKE_BOOT_IMAGE_ARGS) --output $@ + $(hide) $(BVBTOOL) sign_boot_image $(INTERNAL_BVBTOOL_SIGN_BOOT_IMAGE_ARGS) $(BOARD_BVB_SIGN_BOOT_IMAGE_ARGS) --image $@ + $(hide) $(call assert-max-image-size,$@,$(BOARD_BOOTIMAGE_PARTITION_SIZE)) + +.PHONY: bootimage-nodeps +bootimage-nodeps: $(BVBTOOL) + @echo "make $@: ignoring dependencies" + $(hide) $(BVBTOOL) make_boot_image $(INTERNAL_BVBTOOL_MAKE_BOOT_IMAGE_ARGS) $(BOARD_BVB_MAKE_BOOT_IMAGE_ARGS) --output $(INSTALLED_BOOTIMAGE_TARGET) + $(hide) $(BVBTOOL) sign_boot_image $(INTERNAL_BVBTOOL_SIGN_BOOT_IMAGE_ARGS) $(BOARD_BVB_SIGN_BOOT_IMAGE_ARGS) --image $(INSTALLED_BOOTIMAGE_TARGET) + $(hide) $(call assert-max-image-size,$(INSTALLED_BOOTIMAGE_TARGET),$(BOARD_BOOTIMAGE_PARTITION_SIZE)) + +else # BOARD_BVB_ENABLE + # We build recovery as boot image if BOARD_USES_RECOVERY_AS_BOOT is true. ifneq ($(BOARD_USES_RECOVERY_AS_BOOT),true) ifeq ($(TARGET_BOOTIMAGE_USE_EXT2),true) @@ -591,6 +629,7 @@ bootimage-nodeps: $(MKBOOTIMG) endif # TARGET_BOOTIMAGE_USE_EXT2 endif # BOARD_USES_RECOVERY_AS_BOOT +endif # BOARD_BVB_ENABLE else # TARGET_NO_KERNEL # HACK: The top-level targets depend on the bootimage. Not all targets @@ -1117,8 +1156,13 @@ define build-systemimage-target fi; \ mkdir -p $(DIST_DIR); cp $(INSTALLED_FILES_FILE) $(DIST_DIR)/installed-files-rescued.txt; \ exit 1 ) + $(if $(BOARD_BVB_ENABLE), $(hide) $(BVBTOOL) add_image_hashes $(BOARD_BVB_ADD_IMAGE_HASHES_ARGS) --image $(1)) endef +ifeq ($(BOARD_BVB_ENABLE),true) +FULL_SYSTEMIMAGE_DEPS += $(BVBTOOL) +endif + $(BUILT_SYSTEMIMAGE): $(FULL_SYSTEMIMAGE_DEPS) $(INSTALLED_FILES_FILE) $(call build-systemimage-target,$@) @@ -1721,6 +1765,15 @@ ifneq ($(strip $(SANITIZE_TARGET)),) endif ifeq ($(BOARD_USES_FULL_RECOVERY_IMAGE),true) $(hide) echo "full_recovery_image=true" >> $(zip_root)/META/misc_info.txt +endif +ifeq ($(BOARD_BVB_ENABLE),true) + $(hide) echo "board_bvb_enable=true" >> $(zip_root)/META/misc_info.txt + $(hide) echo "board_bvb_make_boot_image_args=$(BOARD_BVB_MAKE_BOOT_IMAGE_ARGS)" >> $(zip_root)/META/misc_info.txt + $(hide) echo "board_bvb_sign_boot_image_args=$(BOARD_BVB_SIGN_BOOT_IMAGE_ARGS)" >> $(zip_root)/META/misc_info.txt + $(hide) echo "board_bvb_algorithm=$(BOARD_BVB_ALGORITHM)" >> $(zip_root)/META/misc_info.txt + $(hide) echo "board_bvb_key_path=$(BOARD_BVB_KEY_PATH)" >> $(zip_root)/META/misc_info.txt + $(hide) echo "board_bvb_rollback_index=$(BOARD_BVB_ROLLBACK_INDEX)" >> $(zip_root)/META/misc_info.txt + $(hide) echo "board_bvb_add_image_hashes_args=$(BOARD_BVB_ADD_IMAGE_HASHES_ARGS)" >> $(zip_root)/META/misc_info.txt endif $(call generate-userimage-prop-dictionary, $(zip_root)/META/misc_info.txt) ifneq ($(INSTALLED_RECOVERYIMAGE_TARGET),) diff --git a/core/config.mk b/core/config.mk index 8c0205526..a68c5b206 100644 --- a/core/config.mk +++ b/core/config.mk @@ -518,6 +518,11 @@ MKBOOTIMG := $(HOST_OUT_EXECUTABLES)/mkbootimg$(HOST_EXECUTABLE_SUFFIX) else MKBOOTIMG := $(BOARD_CUSTOM_MKBOOTIMG) endif +ifeq (,$(strip $(BOARD_CUSTOM_BVBTOOL))) +BVBTOOL := $(HOST_OUT_EXECUTABLES)/bvbtool$(HOST_EXECUTABLE_SUFFIX) +else +BVBTOOL := $(BOARD_CUSTOM_BVBTOOL) +endif APICHECK := $(HOST_OUT_EXECUTABLES)/apicheck$(HOST_EXECUTABLE_SUFFIX) FS_GET_STATS := $(HOST_OUT_EXECUTABLES)/fs_get_stats$(HOST_EXECUTABLE_SUFFIX) MAKE_EXT4FS := $(HOST_OUT_EXECUTABLES)/make_ext4fs$(HOST_EXECUTABLE_SUFFIX) diff --git a/tools/releasetools/add_img_to_target_files.py b/tools/releasetools/add_img_to_target_files.py index 7cb907260..d0027dc71 100755 --- a/tools/releasetools/add_img_to_target_files.py +++ b/tools/releasetools/add_img_to_target_files.py @@ -31,7 +31,9 @@ if sys.hexversion < 0x02070000: import datetime import errno import os +import shlex import shutil +import subprocess import tempfile import zipfile @@ -48,12 +50,12 @@ OPTIONS.verity_signer_path = None def AddSystem(output_zip, prefix="IMAGES/", recovery_img=None, boot_img=None): """Turn the contents of SYSTEM into a system image and store it in - output_zip.""" + output_zip. Returns the name of the system image file.""" prebuilt_path = os.path.join(OPTIONS.input_tmp, prefix, "system.img") if os.path.exists(prebuilt_path): print "system.img already exists in %s, no need to rebuild..." % (prefix,) - return + return prebuilt_path def output_sink(fn, data): ofile = open(os.path.join(OPTIONS.input_tmp, "SYSTEM", fn), "w") @@ -68,8 +70,23 @@ def AddSystem(output_zip, prefix="IMAGES/", recovery_img=None, boot_img=None): block_list = common.MakeTempFile(prefix="system-blocklist-", suffix=".map") imgname = BuildSystem(OPTIONS.input_tmp, OPTIONS.info_dict, block_list=block_list) + + # If requested, calculate and add dm-verity integrity hashes and + # metadata to system.img. + if OPTIONS.info_dict.get("board_bvb_enable", None) == "true": + bvbtool = os.getenv('BVBTOOL') or "bvbtool" + cmd = [bvbtool, "add_image_hashes", "--image", imgname] + args = OPTIONS.info_dict.get("board_bvb_add_image_hashes_args", None) + if args and args.strip(): + cmd.extend(shlex.split(args)) + p = common.Run(cmd, stdout=subprocess.PIPE) + p.communicate() + assert p.returncode == 0, "bvbtool add_image_hashes of %s image failed" % ( + os.path.basename(OPTIONS.input_tmp),) + common.ZipWrite(output_zip, imgname, prefix + "system.img") common.ZipWrite(output_zip, block_list, prefix + "system.map") + return imgname def BuildSystem(input_dir, info_dict, block_list=None): @@ -275,23 +292,40 @@ def AddImagesToTargetFiles(filename): compression=zipfile.ZIP_DEFLATED) has_recovery = (OPTIONS.info_dict.get("no_recovery") != "true") + system_root_image = (OPTIONS.info_dict.get("system_root_image", None) == "true") + board_bvb_enable = (OPTIONS.info_dict.get("board_bvb_enable", None) == "true") + + # Brillo Verified Boot is incompatible with certain + # configurations. Explicitly check for these. + if board_bvb_enable: + assert not has_recovery, "has_recovery incompatible with bvb" + assert not system_root_image, "system_root_image incompatible with bvb" + assert not OPTIONS.rebuild_recovery, "rebuild_recovery incompatible with bvb" + assert not has_vendor, "VENDOR images currently incompatible with bvb" def banner(s): print "\n\n++++ " + s + " ++++\n\n" - banner("boot") prebuilt_path = os.path.join(OPTIONS.input_tmp, "IMAGES", "boot.img") boot_image = None if os.path.exists(prebuilt_path): + banner("boot") print "boot.img already exists in IMAGES/, no need to rebuild..." if OPTIONS.rebuild_recovery: boot_image = common.GetBootableImage( "IMAGES/boot.img", "boot.img", OPTIONS.input_tmp, "BOOT") else: - boot_image = common.GetBootableImage( + if board_bvb_enable: + # With Brillo Verified Boot, we need to build system.img before + # boot.img since the latter includes the dm-verity root hash and + # salt for the former. + pass + else: + banner("boot") + boot_image = common.GetBootableImage( "IMAGES/boot.img", "boot.img", OPTIONS.input_tmp, "BOOT") - if boot_image: - boot_image.AddToZip(output_zip) + if boot_image: + boot_image.AddToZip(output_zip) recovery_image = None if has_recovery: @@ -310,7 +344,17 @@ def AddImagesToTargetFiles(filename): recovery_image.AddToZip(output_zip) banner("system") - AddSystem(output_zip, recovery_img=recovery_image, boot_img=boot_image) + system_img_path = AddSystem( + output_zip, recovery_img=recovery_image, boot_img=boot_image) + if OPTIONS.info_dict.get("board_bvb_enable", None) == "true": + # If we're using Brillo Verified Boot, we can now build boot.img + # given that we have system.img. + banner("boot") + boot_image = common.GetBootableImage( + "IMAGES/boot.img", "boot.img", OPTIONS.input_tmp, "BOOT", + system_img_path=system_img_path) + if boot_image: + boot_image.AddToZip(output_zip) if has_vendor: banner("vendor") AddVendor(output_zip) diff --git a/tools/releasetools/common.py b/tools/releasetools/common.py index 563ce31a1..62ccc452a 100644 --- a/tools/releasetools/common.py +++ b/tools/releasetools/common.py @@ -473,8 +473,114 @@ def _BuildBootableImage(sourcedir, fs_config_file, info_dict=None, return data +def _BuildBvbBootableImage(sourcedir, fs_config_file, system_img_path, + info_dict=None, has_ramdisk=False): + """Build a bootable image compatible with Brillo Verified Boot from the + specified sourcedir. + + Take a kernel, cmdline, system image path, and optionally a ramdisk + directory from the input (in 'sourcedir'), and turn them into a boot + image. Return the image data, or None if sourcedir does not appear + to contains files for building the requested image. + """ + + def make_ramdisk(): + ramdisk_img = tempfile.NamedTemporaryFile() + + if os.access(fs_config_file, os.F_OK): + cmd = ["mkbootfs", "-f", fs_config_file, + os.path.join(sourcedir, "RAMDISK")] + else: + cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")] + p1 = Run(cmd, stdout=subprocess.PIPE) + p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno()) + + p2.wait() + p1.wait() + assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,) + assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,) + + return ramdisk_img + + if not os.access(os.path.join(sourcedir, "kernel"), os.F_OK): + return None + + if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK): + return None + + if info_dict is None: + info_dict = OPTIONS.info_dict + + img = tempfile.NamedTemporaryFile() + + if has_ramdisk: + ramdisk_img = make_ramdisk() + + # use BVBTOOL from environ, or "bvbtool" if empty or not set + bvbtool = os.getenv('BVBTOOL') or "bvbtool" + + # First, create boot.img. + cmd = [bvbtool, "make_boot_image"] + + fn = os.path.join(sourcedir, "cmdline") + if os.access(fn, os.F_OK): + cmd.append("--kernel_cmdline") + cmd.append(open(fn).read().rstrip("\n")) + + cmd.extend(["--kernel", os.path.join(sourcedir, "kernel")]) + + if has_ramdisk: + cmd.extend(["--initrd", ramdisk_img.name]) + + cmd.extend(["--rootfs_with_hashes", system_img_path]) + + args = info_dict.get("board_bvb_make_boot_image_args", None) + if args and args.strip(): + cmd.extend(shlex.split(args)) + + rollback_index = info_dict.get("board_bvb_rollback_index", None) + if rollback_index and rollback_index.strip(): + cmd.extend(["--rollback_index", rollback_index.strip()]) + + cmd.extend(["--output", img.name]) + + p = Run(cmd, stdout=subprocess.PIPE) + p.communicate() + assert p.returncode == 0, "bvbtool make_boot_image of %s image failed" % ( + os.path.basename(sourcedir),) + + # Then, sign boot.img. + cmd = [bvbtool, "sign_boot_image", "--image", img.name] + + algorithm = info_dict.get("board_bvb_algorithm", None) + key_path = info_dict.get("board_bvb_key_path", None) + if algorithm and algorithm.strip() and key_path and key_path.strip(): + cmd.extend(["--algorithm", algorithm, "--key", key_path]) + else: + cmd.extend(["--algorithm", "SHA256_RSA4096"]) + cmd.extend(["--key", "system/bvb/test/testkey_rsa4096.pem"]) + + args = info_dict.get("board_bvb_sign_boot_image_args", None) + if args and args.strip(): + cmd.extend(shlex.split(args)) + + p = Run(cmd, stdout=subprocess.PIPE) + p.communicate() + assert p.returncode == 0, "bvbtool sign_boot_image of %s image failed" % ( + os.path.basename(sourcedir),) + + img.seek(os.SEEK_SET, 0) + data = img.read() + + if has_ramdisk: + ramdisk_img.close() + img.close() + + return data + + def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir, - info_dict=None): + info_dict=None, system_img_path=None): """Return a File object with the desired bootable image. Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name', @@ -504,9 +610,14 @@ def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir, info_dict.get("recovery_as_boot") == "true") fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt" - data = _BuildBootableImage(os.path.join(unpack_dir, tree_subdir), - os.path.join(unpack_dir, fs_config), - info_dict, has_ramdisk) + if info_dict.get("board_bvb_enable", None) == "true": + data = _BuildBvbBootableImage(os.path.join(unpack_dir, tree_subdir), + os.path.join(unpack_dir, fs_config), + system_img_path, info_dict, has_ramdisk) + else: + data = _BuildBootableImage(os.path.join(unpack_dir, tree_subdir), + os.path.join(unpack_dir, fs_config), + info_dict, has_ramdisk) if data: return File(name, data) return None