Merge "Update bootloader, u-boot tools to builds 7889294"
diff --git a/apex/Android.bp b/apex/Android.bp
index 88487e4..93d9f36 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -27,6 +27,7 @@
"microdroid_boot-5.10",
"microdroid_vendor_boot-5.10",
"microdroid_vbmeta",
+ "microdroid_vbmeta_bootconfig",
],
},
x86_64: {
@@ -110,6 +111,41 @@
],
}
+sh_test_host {
+ name: "sign_virt_apex_test",
+ src: "sign_virt_apex_test.sh",
+ test_config: "sign_virt_apex_test.xml",
+ data_bins: [
+ // deapexer
+ "deapexer",
+ "debugfs_static",
+
+ // sign_virt_apex
+ "avbtool",
+ "img2simg",
+ "lpmake",
+ "lpunpack",
+ "sign_virt_apex",
+ "simg2img",
+ ],
+ data_libs: [
+ "libbase",
+ "libc++",
+ "libcrypto_utils",
+ "libcrypto",
+ "libext4_utils",
+ "liblog",
+ "liblp",
+ "libsparse",
+ "libz",
+ ],
+ data: [
+ ":com.android.virt",
+ "test.com.android.virt.pem",
+ ],
+ test_suites: ["general-tests"],
+}
+
// custom tool to replace bytes in a file
python_binary_host {
name: "replace_bytes",
diff --git a/apex/replace_bytes.py b/apex/replace_bytes.py
index 44a47eb..b22f132 100644
--- a/apex/replace_bytes.py
+++ b/apex/replace_bytes.py
@@ -48,12 +48,13 @@
with open(new_file, 'rb') as f:
new_bytes = f.read()
- assert len(old_bytes) == len(new_bytes)
+ assert len(old_bytes) == len(new_bytes), 'Pubkeys should be the same size. (%d != %d)' % (
+ len(old_bytes), len(new_bytes))
# replace bytes in target_file
with open(target_file, 'r+b') as f:
pos = f.read().find(old_bytes)
- assert pos != -1
+ assert pos != -1, 'Pubkey not found'
f.seek(pos)
f.write(new_bytes)
diff --git a/apex/sign_virt_apex.py b/apex/sign_virt_apex.py
index 77f54c4..153b5dd 100644
--- a/apex/sign_virt_apex.py
+++ b/apex/sign_virt_apex.py
@@ -22,6 +22,8 @@
- lpmake, lpunpack, simg2img, img2simg
"""
import argparse
+import glob
+import hashlib
import os
import re
import shutil
@@ -32,6 +34,8 @@
def ParseArgs(argv):
parser = argparse.ArgumentParser(description='Sign the Virt APEX')
+ parser.add_argument('--verify', action='store_true',
+ help='Verify the Virt APEX')
parser.add_argument(
'-v', '--verbose',
action='store_true',
@@ -76,7 +80,12 @@
return int(value.removesuffix(' bytes'))
-def AvbInfo(args, image_path, descriptor_name=None):
+def ExtractAvbPubkey(args, key, output):
+ RunCommand(args, ['avbtool', 'extract_public_key',
+ '--key', key, '--output', output])
+
+
+def AvbInfo(args, image_path):
"""Parses avbtool --info image output
Args:
@@ -87,7 +96,7 @@
Returns:
A pair of
- a dict that contains VBMeta info. None if there's no VBMeta info.
- - a dict that contains target descriptor info. None if name is not specified or not found.
+ - a list of descriptors.
"""
if not os.path.exists(image_path):
raise ValueError('Failed to find image: {}'.format(image_path))
@@ -97,7 +106,7 @@
if ret_code == 1:
return None, None
- info, descriptor = {}, None
+ info, descriptors = {}, []
# Read `avbtool info_image` output as "key:value" lines
matcher = re.compile(r'^(\s*)([^:]+):\s*(.*)$')
@@ -110,30 +119,39 @@
yield line_info.group(1), line_info.group(2), line_info.group(3)
gen = IterateLine(output)
+
+ def ReadDescriptors(cur_indent, cur_name, cur_value):
+ descriptor = cur_value if cur_name == 'Prop' else {}
+ descriptors.append((cur_name, descriptor))
+ for indent, key, value in gen:
+ if indent <= cur_indent:
+ # read descriptors recursively to pass the read key as descriptor name
+ ReadDescriptors(indent, key, value)
+ break
+ descriptor[key] = value
+
# Read VBMeta info
for _, key, value in gen:
if key == 'Descriptors':
+ ReadDescriptors(*next(gen))
break
info[key] = value
- if descriptor_name:
- for indent, key, _ in gen:
- # Read a target descriptor
- if key == descriptor_name:
- cur_indent = indent
- descriptor = {}
- for indent, key, value in gen:
- if indent == cur_indent:
- break
- descriptor[key] = value
- break
+ return info, descriptors
- return info, descriptor
+
+# Look up a list of (key, value) with a key. Returns the value of the first matching pair.
+def LookUp(pairs, key):
+ for k, v in pairs:
+ if key == k:
+ return v
+ return None
def AddHashFooter(args, key, image_path):
- info, descriptor = AvbInfo(args, image_path, 'Hash descriptor')
+ info, descriptors = AvbInfo(args, image_path)
if info:
+ descriptor = LookUp(descriptors, 'Hash descriptor')
image_size = ReadBytesSize(info['Image size'])
algorithm = info['Algorithm']
partition_name = descriptor['Partition Name']
@@ -149,8 +167,9 @@
def AddHashTreeFooter(args, key, image_path):
- info, descriptor = AvbInfo(args, image_path, 'Hashtree descriptor')
+ info, descriptors = AvbInfo(args, image_path)
if info:
+ descriptor = LookUp(descriptors, 'Hashtree descriptor')
image_size = ReadBytesSize(info['Image size'])
algorithm = info['Algorithm']
partition_name = descriptor['Partition Name']
@@ -166,9 +185,12 @@
RunCommand(args, cmd)
-def MakeVbmetaImage(args, key, vbmeta_img, images):
- info, _ = AvbInfo(args, vbmeta_img)
- if info:
+def MakeVbmetaImage(args, key, vbmeta_img, images=None, chained_partitions=None):
+ info, descriptors = AvbInfo(args, vbmeta_img)
+ if info is None:
+ return
+
+ with TempDirectory() as work_dir:
algorithm = info['Algorithm']
rollback_index = info['Rollback Index']
rollback_index_location = info['Rollback Index Location']
@@ -179,8 +201,21 @@
'--rollback_index', rollback_index,
'--rollback_index_location', rollback_index_location,
'--output', vbmeta_img]
- for img in images:
- cmd.extend(['--include_descriptors_from_image', img])
+ if images:
+ for img in images:
+ cmd.extend(['--include_descriptors_from_image', img])
+
+ # replace pubkeys of chained_partitions as well
+ for name, descriptor in descriptors:
+ if name == 'Chain Partition descriptor':
+ part_name = descriptor['Partition Name']
+ ril = descriptor['Rollback Index Location']
+ part_key = chained_partitions[part_name]
+ avbpubkey = os.path.join(work_dir, part_name + '.avbpubkey')
+ ExtractAvbPubkey(args, part_key, avbpubkey)
+ cmd.extend(['--chain_partition', '%s:%s:%s' %
+ (part_name, ril, avbpubkey)])
+
RunCommand(args, cmd)
# libavb expects to be able to read the maximum vbmeta size, so we must provide a partition
# which matches this or the read will fail.
@@ -219,8 +254,8 @@
with open(bootloader_pubkey, 'rb') as f:
old_pubkey = f.read()
- # replace bootloader pubkey
- RunCommand(args, ['avbtool', 'extract_public_key', '--key', key, '--output', bootloader_pubkey])
+ # replace bootloader pubkey (overwrite the old one with the new one)
+ ExtractAvbPubkey(args, key, bootloader_pubkey)
# read new pubkey
with open(bootloader_pubkey, 'rb') as f:
@@ -241,13 +276,22 @@
input_dir = args.input_dir
# target files in the Virt APEX
- bootloader_pubkey = os.path.join(input_dir, 'etc', 'microdroid_bootloader.avbpubkey')
+ bootloader_pubkey = os.path.join(
+ input_dir, 'etc', 'microdroid_bootloader.avbpubkey')
bootloader = os.path.join(input_dir, 'etc', 'microdroid_bootloader')
boot_img = os.path.join(input_dir, 'etc', 'fs', 'microdroid_boot-5.10.img')
vendor_boot_img = os.path.join(
input_dir, 'etc', 'fs', 'microdroid_vendor_boot-5.10.img')
super_img = os.path.join(input_dir, 'etc', 'fs', 'microdroid_super.img')
vbmeta_img = os.path.join(input_dir, 'etc', 'fs', 'microdroid_vbmeta.img')
+ vbmeta_bootconfig_img = os.path.join(
+ input_dir, 'etc', 'fs', 'microdroid_vbmeta_bootconfig.img')
+ bootconfig_normal = os.path.join(
+ input_dir, 'etc', 'microdroid_bootconfig.normal')
+ bootconfig_app_debuggable = os.path.join(
+ input_dir, 'etc', 'microdroid_bootconfig.app_debuggable')
+ bootconfig_full_debuggable = os.path.join(
+ input_dir, 'etc', 'microdroid_bootconfig.full_debuggable')
# Key(pubkey) for bootloader should match with the one used to make VBmeta below
# while it's okay to use different keys for other image files.
@@ -280,14 +324,81 @@
# Ideally, making VBmeta should be done out of TempDirectory block. But doing it here
# to avoid unpacking re-signed super.img for system/vendor images which are available
# in this block.
- MakeVbmetaImage(args, key, vbmeta_img, [
+ MakeVbmetaImage(args, key, vbmeta_img, images=[
boot_img, vendor_boot_img, system_a_img, vendor_a_img])
+ # Re-sign bootconfigs with the same key
+ bootconfig_sign_key = key
+ AddHashFooter(args, bootconfig_sign_key, bootconfig_normal)
+ AddHashFooter(args, bootconfig_sign_key, bootconfig_app_debuggable)
+ AddHashFooter(args, bootconfig_sign_key, bootconfig_full_debuggable)
+
+ # Re-sign vbmeta_bootconfig with a chained_partition to "bootconfig"
+ # Note that, for now, `key` and `bootconfig_sign_key` are the same, but technically they
+ # can be different. Vbmeta records pubkeys which signed chained partitions.
+ MakeVbmetaImage(args, key, vbmeta_bootconfig_img, chained_partitions={
+ 'bootconfig': bootconfig_sign_key})
+
+
+def VerifyVirtApex(args):
+ # Generator to emit avbtool-signed items along with its pubkey digest.
+ # This supports lpmake-packed images as well.
+ def Recur(target_dir):
+ for file in glob.glob(os.path.join(target_dir, 'etc', '**', '*'), recursive=True):
+ cur_item = os.path.relpath(file, target_dir)
+
+ if not os.path.isfile(file):
+ continue
+
+ # avbpubkey
+ if cur_item == 'etc/microdroid_bootloader.avbpubkey':
+ with open(file, 'rb') as f:
+ yield (cur_item, hashlib.sha1(f.read()).hexdigest())
+ continue
+
+ # avbtool signed
+ info, _ = AvbInfo(args, file)
+ if info:
+ yield (cur_item, info['Public key (sha1)'])
+ continue
+
+ # logical partition
+ with TempDirectory() as tmp_dir:
+ unsparsed = os.path.join(tmp_dir, os.path.basename(file))
+ _, rc = RunCommand(
+ # exit with 255 if it's not sparsed
+ args, ['simg2img', file, unsparsed], expected_return_values={0, 255})
+ if rc == 0:
+ with TempDirectory() as unpack_dir:
+ # exit with 64 if it's not a logical partition.
+ _, rc = RunCommand(
+ args, ['lpunpack', unsparsed, unpack_dir], expected_return_values={0, 64})
+ if rc == 0:
+ nested_items = list(Recur(unpack_dir))
+ if len(nested_items) > 0:
+ for (item, key) in nested_items:
+ yield ('%s!/%s' % (cur_item, item), key)
+ continue
+ # Read pubkey digest
+ with TempDirectory() as tmp_dir:
+ pubkey_file = os.path.join(tmp_dir, 'avbpubkey')
+ ExtractAvbPubkey(args, args.key, pubkey_file)
+ with open(pubkey_file, 'rb') as f:
+ pubkey_digest = hashlib.sha1(f.read()).hexdigest()
+
+ # Check every avbtool-signed item against the input key
+ for (item, pubkey) in Recur(args.input_dir):
+ assert pubkey == pubkey_digest, '%s: key mismatch: %s != %s' % (
+ item, pubkey, pubkey_digest)
+
def main(argv):
try:
args = ParseArgs(argv)
- SignVirtApex(args)
+ if args.verify:
+ VerifyVirtApex(args)
+ else:
+ SignVirtApex(args)
except Exception as e:
print(e)
sys.exit(1)
diff --git a/apex/sign_virt_apex_test.sh b/apex/sign_virt_apex_test.sh
new file mode 100644
index 0000000..640a3d4
--- /dev/null
+++ b/apex/sign_virt_apex_test.sh
@@ -0,0 +1,35 @@
+#!/usr/bin/env bash
+
+# Copyright (C) 2021 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+set -e
+shopt -s extglob
+
+TMP_ROOT=$(mktemp -d -t sign_virt_apex-XXXXXXXX)
+TEST_DIR=$(dirname $0)
+
+# To access host tools
+PATH=$TEST_DIR:$PATH
+DEBUGFS=$TEST_DIR/debugfs_static
+
+deapexer --debugfs_path $DEBUGFS extract $TEST_DIR/com.android.virt.apex $TMP_ROOT
+
+if [ "$(ls -A $TMP_ROOT/etc/fs/)" ]; then
+ sign_virt_apex $TEST_DIR/test.com.android.virt.pem $TMP_ROOT
+ sign_virt_apex --verify $TEST_DIR/test.com.android.virt.pem $TMP_ROOT
+else
+ echo "No filesystem images. Skip."
+fi
+
diff --git a/apex/sign_virt_apex_test.xml b/apex/sign_virt_apex_test.xml
new file mode 100644
index 0000000..5ea84a1
--- /dev/null
+++ b/apex/sign_virt_apex_test.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Runs sign_virt_apex test">
+ <test class="com.android.tradefed.testtype.binary.ExecutableHostTest" >
+ <option name="binary" value="sign_virt_apex_test"/>
+ </test>
+</configuration>
diff --git a/apex/test.com.android.virt.pem b/apex/test.com.android.virt.pem
new file mode 100644
index 0000000..b0cfff4
--- /dev/null
+++ b/apex/test.com.android.virt.pem
@@ -0,0 +1,51 @@
+-----BEGIN RSA PRIVATE KEY-----
+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
+-----END RSA PRIVATE KEY-----
diff --git a/microdroid/Android.bp b/microdroid/Android.bp
index af61e82..eb19d85 100644
--- a/microdroid/Android.bp
+++ b/microdroid/Android.bp
@@ -477,13 +477,19 @@
":microdroid_bootloader_avbpubkey_gen", // new bytes
],
out: ["bootloader-pubkey-replaced"],
+ // 1. Copy the input to the output (replace_bytes modifies the file in-place)
+ // 2. Check if the file is big enough. For arm and x86 we have fake
+ // bootloader file whose size is 1. (replace_bytes fails if key not found)
+ // 3. Replace embedded pubkey with new one.
cmd: "cp $(location :microdroid_crosvm_bootloader) $(out) && " +
+ "if [ $$(stat --format=%s $(out)) -gt 4096 ]; then " +
"$(location replace_bytes) $(out) " +
// TODO(b/193504286) use the avbpubkey exposed from the prebuilt.
// For now, replacing it with the same key to ensure that "replace_bytes" works and
// that microdroid_crosvm_bootloader embeds the same pubkey of microdroid_sign_key.
"$(location :microdroid_bootloader_avbpubkey_gen) " +
- "$(location :microdroid_bootloader_avbpubkey_gen)",
+ "$(location :microdroid_bootloader_avbpubkey_gen)" +
+ "; fi",
}
// Apex keeps a copy of avbpubkey embedded in bootloader so that embedded avbpubkey can be replaced