Merge "releasetools: Create VerityImageBuilder."

This commit is contained in:
Tao Bao 2018-11-06 23:37:15 +00:00 committed by Gerrit Code Review
commit d0b9758203
4 changed files with 427 additions and 319 deletions

View File

@ -248,20 +248,9 @@ def BuildImage(in_dir, prop_dict, out_file, target_out=None):
if fs_type.startswith("squash"): if fs_type.startswith("squash"):
fs_spans_partition = False fs_spans_partition = False
is_verity_partition = "verity_block_device" in prop_dict # Get a builder for creating an image that's to be verified by Verified Boot,
verity_supported = prop_dict.get("verity") == "true" # or None if not applicable.
verity_fec_supported = prop_dict.get("verity_fec") == "true" verity_image_builder = verity_utils.CreateVerityImageBuilder(prop_dict)
avb_footer_type = None
if prop_dict.get("avb_hash_enable") == "true":
avb_footer_type = "hash"
elif prop_dict.get("avb_hashtree_enable") == "true":
avb_footer_type = "hashtree"
if avb_footer_type:
avbtool = prop_dict.get("avb_avbtool")
avb_signing_args = prop_dict.get(
"avb_add_" + avb_footer_type + "_footer_args")
if (prop_dict.get("use_dynamic_partition_size") == "true" and if (prop_dict.get("use_dynamic_partition_size") == "true" and
"partition_size" not in prop_dict): "partition_size" not in prop_dict):
@ -273,13 +262,8 @@ def BuildImage(in_dir, prop_dict, out_file, target_out=None):
size += int(prop_dict.get("partition_reserved_size", BYTES_IN_MB * 16)) size += int(prop_dict.get("partition_reserved_size", BYTES_IN_MB * 16))
# Round this up to a multiple of 4K so that avbtool works # Round this up to a multiple of 4K so that avbtool works
size = common.RoundUpTo4K(size) size = common.RoundUpTo4K(size)
# Adjust partition_size to add more space for AVB footer, to prevent if verity_image_builder:
# it from consuming partition_reserved_size. size = verity_image_builder.CalculateDynamicPartitionSize(size)
if avb_footer_type:
size = verity_utils.AVBCalcMinPartitionSize(
size,
lambda x: verity_utils.AVBCalcMaxImageSize(
avbtool, avb_footer_type, x, avb_signing_args))
prop_dict["partition_size"] = str(size) prop_dict["partition_size"] = str(size)
if fs_type.startswith("ext"): if fs_type.startswith("ext"):
if "extfs_inode_count" not in prop_dict: if "extfs_inode_count" not in prop_dict:
@ -316,19 +300,8 @@ def BuildImage(in_dir, prop_dict, out_file, target_out=None):
prop_dict["image_size"] = prop_dict["partition_size"] prop_dict["image_size"] = prop_dict["partition_size"]
# Adjust the image size to make room for the hashes if this is to be verified. # Adjust the image size to make room for the hashes if this is to be verified.
if verity_supported and is_verity_partition: if verity_image_builder:
partition_size = int(prop_dict.get("partition_size")) max_image_size = verity_image_builder.CalculateMaxImageSize()
image_size, verity_size = verity_utils.AdjustPartitionSizeForVerity(
partition_size, verity_fec_supported)
prop_dict["image_size"] = str(image_size)
prop_dict["verity_size"] = str(verity_size)
# Adjust the image size for AVB hash footer or AVB hashtree footer.
if avb_footer_type:
partition_size = prop_dict["partition_size"]
# avb_add_hash_footer_args or avb_add_hashtree_footer_args.
max_image_size = verity_utils.AVBCalcMaxImageSize(
avbtool, avb_footer_type, partition_size, avb_signing_args)
prop_dict["image_size"] = str(max_image_size) prop_dict["image_size"] = str(max_image_size)
if fs_type.startswith("ext"): if fs_type.startswith("ext"):
@ -441,33 +414,12 @@ def BuildImage(in_dir, prop_dict, out_file, target_out=None):
if "partition_headroom" in prop_dict and fs_type.startswith("ext4"): if "partition_headroom" in prop_dict and fs_type.startswith("ext4"):
CheckHeadroom(mkfs_output, prop_dict) CheckHeadroom(mkfs_output, prop_dict)
if not fs_spans_partition: if not fs_spans_partition and verity_image_builder:
mount_point = prop_dict.get("mount_point") verity_image_builder.PadSparseImage(out_file)
image_size = int(prop_dict["image_size"])
sparse_image_size = verity_utils.GetSimgSize(out_file)
if sparse_image_size > image_size:
raise BuildImageError(
"Error: {} image size of {} is larger than partition size of "
"{}".format(mount_point, sparse_image_size, image_size))
if verity_supported and is_verity_partition:
verity_utils.ZeroPadSimg(out_file, image_size - sparse_image_size)
# Create the verified image if this is to be verified. # Create the verified image if this is to be verified.
if verity_supported and is_verity_partition: if verity_image_builder:
verity_utils.MakeVerityEnabledImage( verity_image_builder.Build(out_file)
out_file, verity_fec_supported, prop_dict)
# Add AVB HASH or HASHTREE footer (metadata).
if avb_footer_type:
partition_size = prop_dict["partition_size"]
partition_name = prop_dict["partition_name"]
# key_path and algorithm are only available when chain partition is used.
key_path = prop_dict.get("avb_key_path")
algorithm = prop_dict.get("avb_algorithm")
salt = prop_dict.get("avb_salt")
verity_utils.AVBAddFooter(
out_file, avbtool, avb_footer_type, partition_size, partition_name,
key_path, algorithm, salt, avb_signing_args)
if run_e2fsck and prop_dict.get("skip_fsck") != "true": if run_e2fsck and prop_dict.get("skip_fsck") != "true":
unsparse_image = UnsparseImage(out_file, replace=False) unsparse_image = UnsparseImage(out_file, replace=False)

View File

@ -24,6 +24,7 @@ import common
import test_utils import test_utils
import verity_utils import verity_utils
from validate_target_files import ValidateVerifiedBootImages from validate_target_files import ValidateVerifiedBootImages
from verity_utils import CreateVerityImageBuilder
class ValidateTargetFilesTest(test_utils.ReleaseToolsTestCase): class ValidateTargetFilesTest(test_utils.ReleaseToolsTestCase):
@ -107,10 +108,16 @@ class ValidateTargetFilesTest(test_utils.ReleaseToolsTestCase):
options) options)
def _generate_system_image(self, output_file): def _generate_system_image(self, output_file):
verity_fec = True prop_dict = {
partition_size = 1024 * 1024 'partition_size': str(1024 * 1024),
image_size, verity_size = verity_utils.AdjustPartitionSizeForVerity( 'verity': 'true',
partition_size, verity_fec) 'verity_block_device': '/dev/block/system',
'verity_key' : os.path.join(self.testdata_dir, 'testkey'),
'verity_fec': "true",
'verity_signer_cmd': 'verity_signer',
}
verity_image_builder = CreateVerityImageBuilder(prop_dict)
image_size = verity_image_builder.CalculateMaxImageSize()
# Use an empty root directory. # Use an empty root directory.
system_root = common.MakeTempDir() system_root = common.MakeTempDir()
@ -124,15 +131,7 @@ class ValidateTargetFilesTest(test_utils.ReleaseToolsTestCase):
stdoutdata)) stdoutdata))
# Append the verity metadata. # Append the verity metadata.
prop_dict = { verity_image_builder.Build(output_file)
'partition_size' : str(partition_size),
'image_size' : str(image_size),
'verity_block_device' : '/dev/block/system',
'verity_key' : os.path.join(self.testdata_dir, 'testkey'),
'verity_signer_cmd' : 'verity_signer',
'verity_size' : str(verity_size),
}
verity_utils.MakeVerityEnabledImage(output_file, verity_fec, prop_dict)
def test_ValidateVerifiedBootImages_systemImage(self): def test_ValidateVerifiedBootImages_systemImage(self):
input_tmp = common.MakeTempDir() input_tmp = common.MakeTempDir()

View File

@ -25,10 +25,11 @@ import sparse_img
from rangelib import RangeSet from rangelib import RangeSet
from test_utils import get_testdata_dir, ReleaseToolsTestCase from test_utils import get_testdata_dir, ReleaseToolsTestCase
from verity_utils import ( from verity_utils import (
AdjustPartitionSizeForVerity, AVBCalcMinPartitionSize, BLOCK_SIZE, CreateHashtreeInfoGenerator, CreateVerityImageBuilder, HashtreeInfo,
CreateHashtreeInfoGenerator, HashtreeInfo, MakeVerityEnabledImage,
VerifiedBootVersion1HashtreeInfoGenerator) VerifiedBootVersion1HashtreeInfoGenerator)
BLOCK_SIZE = common.BLOCK_SIZE
class VerifiedBootVersion1HashtreeInfoGeneratorTest(ReleaseToolsTestCase): class VerifiedBootVersion1HashtreeInfoGeneratorTest(ReleaseToolsTestCase):
@ -64,8 +65,17 @@ class VerifiedBootVersion1HashtreeInfoGeneratorTest(ReleaseToolsTestCase):
def _generate_image(self): def _generate_image(self):
partition_size = 1024 * 1024 partition_size = 1024 * 1024
adjusted_size, verity_size = AdjustPartitionSizeForVerity( prop_dict = {
partition_size, True) 'partition_size': str(partition_size),
'verity': 'true',
'verity_block_device': '/dev/block/system',
'verity_key': os.path.join(self.testdata_dir, 'testkey'),
'verity_fec': 'true',
'verity_signer_cmd': 'verity_signer',
}
verity_image_builder = CreateVerityImageBuilder(prop_dict)
self.assertIsNotNone(verity_image_builder)
adjusted_size = verity_image_builder.CalculateMaxImageSize()
raw_image = "" raw_image = ""
for i in range(adjusted_size): for i in range(adjusted_size):
@ -74,15 +84,7 @@ class VerifiedBootVersion1HashtreeInfoGeneratorTest(ReleaseToolsTestCase):
output_file = self._create_simg(raw_image) output_file = self._create_simg(raw_image)
# Append the verity metadata. # Append the verity metadata.
prop_dict = { verity_image_builder.Build(output_file)
'partition_size': str(partition_size),
'image_size': str(adjusted_size),
'verity_block_device': '/dev/block/system',
'verity_key': os.path.join(self.testdata_dir, 'testkey'),
'verity_signer_cmd': 'verity_signer',
'verity_size': str(verity_size),
}
MakeVerityEnabledImage(output_file, True, prop_dict)
return output_file return output_file
@ -163,23 +165,33 @@ class VerifiedBootVersion1HashtreeInfoGeneratorTest(ReleaseToolsTestCase):
self.assertEqual(self.expected_root_hash, info.root_hash) self.assertEqual(self.expected_root_hash, info.root_hash)
class VerityUtilsTest(ReleaseToolsTestCase): class VerifiedBootVersion2VerityImageBuilderTest(ReleaseToolsTestCase):
def setUp(self): def setUp(self):
# To test AVBCalcMinPartitionSize(), by using 200MB to 2GB image size. # To test CalculateMinPartitionSize(), by using 200MB to 2GB image size.
# - 51200 = 200MB * 1024 * 1024 / 4096 # - 51200 = 200MB * 1024 * 1024 / 4096
# - 524288 = 2GB * 1024 * 1024 * 1024 / 4096 # - 524288 = 2GB * 1024 * 1024 * 1024 / 4096
self._image_sizes = [BLOCK_SIZE * random.randint(51200, 524288) + offset self._image_sizes = [BLOCK_SIZE * random.randint(51200, 524288) + offset
for offset in range(BLOCK_SIZE)] for offset in range(BLOCK_SIZE)]
def test_AVBCalcMinPartitionSize_LinearFooterSize(self): prop_dict = {
'partition_size': None,
'partition_name': 'system',
'avb_avbtool': 'avbtool',
'avb_hashtree_enable': 'true',
'avb_add_hashtree_footer_args': None,
}
self.builder = CreateVerityImageBuilder(prop_dict)
self.assertEqual(2, self.builder.version)
def test_CalculateMinPartitionSize_LinearFooterSize(self):
"""Tests with footer size which is linear to partition size.""" """Tests with footer size which is linear to partition size."""
for image_size in self._image_sizes: for image_size in self._image_sizes:
for ratio in 0.95, 0.56, 0.22: for ratio in 0.95, 0.56, 0.22:
expected_size = common.RoundUpTo4K(int(math.ceil(image_size / ratio))) expected_size = common.RoundUpTo4K(int(math.ceil(image_size / ratio)))
self.assertEqual( self.assertEqual(
expected_size, expected_size,
AVBCalcMinPartitionSize( self.builder.CalculateMinPartitionSize(
image_size, lambda x, ratio=ratio: int(x * ratio))) image_size, lambda x, ratio=ratio: int(x * ratio)))
def test_AVBCalcMinPartitionSize_SlowerGrowthFooterSize(self): def test_AVBCalcMinPartitionSize_SlowerGrowthFooterSize(self):
@ -191,7 +203,8 @@ class VerityUtilsTest(ReleaseToolsTestCase):
return partition_size - int(math.pow(partition_size, 0.95)) return partition_size - int(math.pow(partition_size, 0.95))
for image_size in self._image_sizes: for image_size in self._image_sizes:
min_partition_size = AVBCalcMinPartitionSize(image_size, _SizeCalculator) min_partition_size = self.builder.CalculateMinPartitionSize(
image_size, _SizeCalculator)
# Checks min_partition_size can accommodate image_size. # Checks min_partition_size can accommodate image_size.
self.assertGreaterEqual( self.assertGreaterEqual(
_SizeCalculator(min_partition_size), _SizeCalculator(min_partition_size),
@ -201,7 +214,7 @@ class VerityUtilsTest(ReleaseToolsTestCase):
_SizeCalculator(min_partition_size - BLOCK_SIZE), _SizeCalculator(min_partition_size - BLOCK_SIZE),
image_size) image_size)
def test_AVBCalcMinPartitionSize_FasterGrowthFooterSize(self): def test_CalculateMinPartitionSize_FasterGrowthFooterSize(self):
"""Tests with footer size which grows faster than partition size.""" """Tests with footer size which grows faster than partition size."""
def _SizeCalculator(partition_size): def _SizeCalculator(partition_size):
@ -211,7 +224,8 @@ class VerityUtilsTest(ReleaseToolsTestCase):
return int(math.pow(partition_size, 0.95)) return int(math.pow(partition_size, 0.95))
for image_size in self._image_sizes: for image_size in self._image_sizes:
min_partition_size = AVBCalcMinPartitionSize(image_size, _SizeCalculator) min_partition_size = self.builder.CalculateMinPartitionSize(
image_size, _SizeCalculator)
# Checks min_partition_size can accommodate image_size. # Checks min_partition_size can accommodate image_size.
self.assertGreaterEqual( self.assertGreaterEqual(
_SizeCalculator(min_partition_size), _SizeCalculator(min_partition_size),

View File

@ -39,30 +39,30 @@ class BuildVerityImageError(Exception):
Exception.__init__(self, message) Exception.__init__(self, message)
def GetVerityFECSize(partition_size): def GetVerityFECSize(image_size):
cmd = ["fec", "-s", str(partition_size)] cmd = ["fec", "-s", str(image_size)]
output = common.RunAndCheckOutput(cmd, verbose=False) output = common.RunAndCheckOutput(cmd, verbose=False)
return int(output) return int(output)
def GetVerityTreeSize(partition_size): def GetVerityTreeSize(image_size):
cmd = ["build_verity_tree", "-s", str(partition_size)] cmd = ["build_verity_tree", "-s", str(image_size)]
output = common.RunAndCheckOutput(cmd, verbose=False) output = common.RunAndCheckOutput(cmd, verbose=False)
return int(output) return int(output)
def GetVerityMetadataSize(partition_size): def GetVerityMetadataSize(image_size):
cmd = ["build_verity_metadata.py", "size", str(partition_size)] cmd = ["build_verity_metadata.py", "size", str(image_size)]
output = common.RunAndCheckOutput(cmd, verbose=False) output = common.RunAndCheckOutput(cmd, verbose=False)
return int(output) return int(output)
def GetVeritySize(partition_size, fec_supported): def GetVeritySize(image_size, fec_supported):
verity_tree_size = GetVerityTreeSize(partition_size) verity_tree_size = GetVerityTreeSize(image_size)
verity_metadata_size = GetVerityMetadataSize(partition_size) verity_metadata_size = GetVerityMetadataSize(image_size)
verity_size = verity_tree_size + verity_metadata_size verity_size = verity_tree_size + verity_metadata_size
if fec_supported: if fec_supported:
fec_size = GetVerityFECSize(partition_size + verity_size) fec_size = GetVerityFECSize(image_size + verity_size)
return verity_size + fec_size return verity_size + fec_size
return verity_size return verity_size
@ -79,54 +79,6 @@ def ZeroPadSimg(image_file, pad_size):
simg.AppendFillChunk(0, blocks) simg.AppendFillChunk(0, blocks)
def AdjustPartitionSizeForVerity(partition_size, fec_supported):
"""Modifies the provided partition size to account for the verity metadata.
This information is used to size the created image appropriately.
Args:
partition_size: the size of the partition to be verified.
Returns:
A tuple of the size of the partition adjusted for verity metadata, and
the size of verity metadata.
"""
key = "%d %d" % (partition_size, fec_supported)
if key in AdjustPartitionSizeForVerity.results:
return AdjustPartitionSizeForVerity.results[key]
hi = partition_size
if hi % BLOCK_SIZE != 0:
hi = (hi // BLOCK_SIZE) * BLOCK_SIZE
# verity tree and fec sizes depend on the partition size, which
# means this estimate is always going to be unnecessarily small
verity_size = GetVeritySize(hi, fec_supported)
lo = partition_size - verity_size
result = lo
# do a binary search for the optimal size
while lo < hi:
i = ((lo + hi) // (2 * BLOCK_SIZE)) * BLOCK_SIZE
v = GetVeritySize(i, fec_supported)
if i + v <= partition_size:
if result < i:
result = i
verity_size = v
lo = i + BLOCK_SIZE
else:
hi = i
logger.info(
"Adjusted partition size for verity, partition_size: %s, verity_size: %s",
result, verity_size)
AdjustPartitionSizeForVerity.results[key] = (result, verity_size)
return (result, verity_size)
AdjustPartitionSizeForVerity.results = {}
def BuildVerityFEC(sparse_image_path, verity_path, verity_fec_path, def BuildVerityFEC(sparse_image_path, verity_path, verity_fec_path,
padding_size): padding_size):
cmd = ["fec", "-e", "-p", str(padding_size), sparse_image_path, cmd = ["fec", "-e", "-p", str(padding_size), sparse_image_path,
@ -187,103 +139,243 @@ def Append(target, file_to_append, error_message):
raise BuildVerityImageError(error_message) raise BuildVerityImageError(error_message)
def BuildVerifiedImage(data_image_path, verity_image_path, def CreateVerityImageBuilder(prop_dict):
verity_metadata_path, verity_fec_path, """Returns a verity image builder based on the given build properties.
padding_size, fec_supported):
Append(
verity_image_path, verity_metadata_path,
"Could not append verity metadata!")
if fec_supported:
# Build FEC for the entire partition, including metadata.
BuildVerityFEC(
data_image_path, verity_image_path, verity_fec_path, padding_size)
Append(verity_image_path, verity_fec_path, "Could not append FEC!")
Append2Simg(
data_image_path, verity_image_path, "Could not append verity data!")
def MakeVerityEnabledImage(out_file, fec_supported, prop_dict):
"""Creates an image that is verifiable using dm-verity.
Args: Args:
out_file: the location to write the verifiable image at prop_dict: A dict that contains the build properties. In particular, it will
prop_dict: a dictionary of properties required for image creation and look for verity-related property values.
verification
Raises: Returns:
AssertionError: On invalid partition sizes. A VerityImageBuilder instance for Verified Boot 1.0 or Verified Boot 2.0; or
None if the given build doesn't support Verified Boot.
""" """
# get properties partition_size = prop_dict.get("partition_size")
image_size = int(prop_dict["image_size"]) # partition_size could be None at this point, if using dynamic partitions.
block_dev = prop_dict["verity_block_device"] if partition_size:
signer_key = prop_dict["verity_key"] + ".pk8" partition_size = int(partition_size)
# Verified Boot 1.0
verity_supported = prop_dict.get("verity") == "true"
is_verity_partition = "verity_block_device" in prop_dict
if verity_supported and is_verity_partition:
if OPTIONS.verity_signer_path is not None: if OPTIONS.verity_signer_path is not None:
signer_path = OPTIONS.verity_signer_path signer_path = OPTIONS.verity_signer_path
else: else:
signer_path = prop_dict["verity_signer_cmd"] signer_path = prop_dict["verity_signer_cmd"]
signer_args = OPTIONS.verity_signer_args return Version1VerityImageBuilder(
partition_size,
prop_dict["verity_block_device"],
prop_dict.get("verity_fec") == "true",
signer_path,
prop_dict["verity_key"] + ".pk8",
OPTIONS.verity_signer_args,
"verity_disable" in prop_dict)
# Verified Boot 2.0
if (prop_dict.get("avb_hash_enable") == "true" or
prop_dict.get("avb_hashtree_enable") == "true"):
# key_path and algorithm are only available when chain partition is used.
key_path = prop_dict.get("avb_key_path")
algorithm = prop_dict.get("avb_algorithm")
if prop_dict.get("avb_hash_enable") == "true":
return VerifiedBootVersion2VerityImageBuilder(
prop_dict["partition_name"],
partition_size,
VerifiedBootVersion2VerityImageBuilder.AVB_HASH_FOOTER,
prop_dict["avb_avbtool"],
key_path,
algorithm,
prop_dict.get("avb_salt"),
prop_dict["avb_add_hash_footer_args"])
else:
return VerifiedBootVersion2VerityImageBuilder(
prop_dict["partition_name"],
partition_size,
VerifiedBootVersion2VerityImageBuilder.AVB_HASHTREE_FOOTER,
prop_dict["avb_avbtool"],
key_path,
algorithm,
prop_dict.get("avb_salt"),
prop_dict["avb_add_hashtree_footer_args"])
return None
class VerityImageBuilder(object):
"""A builder that generates an image with verity metadata for Verified Boot.
A VerityImageBuilder instance handles the works for building an image with
verity metadata for supporting Android Verified Boot. This class defines the
common interface between Verified Boot 1.0 and Verified Boot 2.0. A matching
builder will be returned based on the given build properties.
More info on the verity image generation can be found at the following link.
https://source.android.com/security/verifiedboot/dm-verity#implementation
"""
def CalculateMaxImageSize(self, partition_size):
"""Calculates the filesystem image size for the given partition size."""
raise NotImplementedError
def CalculateDynamicPartitionSize(self, image_size):
"""Calculates and sets the partition size for a dynamic partition."""
raise NotImplementedError
def PadSparseImage(self, out_file):
"""Adds padding to the generated sparse image."""
raise NotImplementedError
def Build(self, out_file):
"""Builds the verity image and writes it to the given file."""
raise NotImplementedError
class Version1VerityImageBuilder(VerityImageBuilder):
"""A VerityImageBuilder for Verified Boot 1.0."""
def __init__(self, partition_size, block_dev, fec_supported, signer_path,
signer_key, signer_args, verity_disable):
self.version = 1
self.partition_size = partition_size
self.block_device = block_dev
self.fec_supported = fec_supported
self.signer_path = signer_path
self.signer_key = signer_key
self.signer_args = signer_args
self.verity_disable = verity_disable
self.image_size = None
self.verity_size = None
def CalculateDynamicPartitionSize(self, image_size):
# This needs to be implemented. Note that returning the given image size as
# the partition size doesn't make sense, as it will fail later.
raise NotImplementedError
def CalculateMaxImageSize(self, partition_size=None):
"""Calculates the max image size by accounting for the verity metadata.
Args:
partition_size: The partition size, which defaults to self.partition_size
if unspecified.
Returns:
The size of the image adjusted for verity metadata.
"""
if partition_size is None:
partition_size = self.partition_size
assert partition_size > 0, \
"Invalid partition size: {}".format(partition_size)
hi = partition_size
if hi % BLOCK_SIZE != 0:
hi = (hi // BLOCK_SIZE) * BLOCK_SIZE
# verity tree and fec sizes depend on the partition size, which
# means this estimate is always going to be unnecessarily small
verity_size = GetVeritySize(hi, self.fec_supported)
lo = partition_size - verity_size
result = lo
# do a binary search for the optimal size
while lo < hi:
i = ((lo + hi) // (2 * BLOCK_SIZE)) * BLOCK_SIZE
v = GetVeritySize(i, self.fec_supported)
if i + v <= partition_size:
if result < i:
result = i
verity_size = v
lo = i + BLOCK_SIZE
else:
hi = i
self.image_size = result
self.verity_size = verity_size
logger.info(
"Calculated image size for verity: partition_size %d, image_size %d, "
"verity_size %d", partition_size, result, verity_size)
return result
def Build(self, out_file):
"""Creates an image that is verifiable using dm-verity.
Args:
out_file: the location to write the verifiable image at
Returns:
AssertionError: On invalid partition sizes.
BuildVerityImageError: On other errors.
"""
image_size = int(self.image_size)
tempdir_name = common.MakeTempDir(suffix="_verity_images") tempdir_name = common.MakeTempDir(suffix="_verity_images")
# Get partial image paths. # Get partial image paths.
verity_image_path = os.path.join(tempdir_name, "verity.img") verity_image_path = os.path.join(tempdir_name, "verity.img")
verity_metadata_path = os.path.join(tempdir_name, "verity_metadata.img") verity_metadata_path = os.path.join(tempdir_name, "verity_metadata.img")
verity_fec_path = os.path.join(tempdir_name, "verity_fec.img")
# Build the verity tree and get the root hash and salt. # Build the verity tree and get the root hash and salt.
root_hash, salt = BuildVerityTree(out_file, verity_image_path) root_hash, salt = BuildVerityTree(out_file, verity_image_path)
# Build the metadata blocks. # Build the metadata blocks.
verity_disable = "verity_disable" in prop_dict
BuildVerityMetadata( BuildVerityMetadata(
image_size, verity_metadata_path, root_hash, salt, block_dev, signer_path, image_size, verity_metadata_path, root_hash, salt, self.block_device,
signer_key, signer_args, verity_disable) self.signer_path, self.signer_key, self.signer_args,
self.verity_disable)
# Build the full verified image. padding_size = self.partition_size - self.image_size - self.verity_size
partition_size = int(prop_dict["partition_size"])
verity_size = int(prop_dict["verity_size"])
padding_size = partition_size - image_size - verity_size
assert padding_size >= 0 assert padding_size >= 0
BuildVerifiedImage( # Build the full verified image.
out_file, verity_image_path, verity_metadata_path, verity_fec_path, Append(
padding_size, fec_supported) verity_image_path, verity_metadata_path,
"Failed to append verity metadata")
if self.fec_supported:
# Build FEC for the entire partition, including metadata.
verity_fec_path = os.path.join(tempdir_name, "verity_fec.img")
BuildVerityFEC(
out_file, verity_image_path, verity_fec_path, padding_size)
Append(verity_image_path, verity_fec_path, "Failed to append FEC")
def AVBCalcMaxImageSize(avbtool, footer_type, partition_size, additional_args): Append2Simg(
"""Calculates max image size for a given partition size. out_file, verity_image_path, "Failed to append verity data")
Args: def PadSparseImage(self, out_file):
avbtool: String with path to avbtool. sparse_image_size = GetSimgSize(out_file)
footer_type: 'hash' or 'hashtree' for generating footer. if sparse_image_size > self.image_size:
partition_size: The size of the partition in question.
additional_args: Additional arguments to pass to "avbtool add_hash_footer"
or "avbtool add_hashtree_footer".
Returns:
The maximum image size.
Raises:
BuildVerityImageError: On invalid image size.
"""
cmd = [avbtool, "add_%s_footer" % footer_type,
"--partition_size", str(partition_size), "--calc_max_image_size"]
cmd.extend(shlex.split(additional_args))
output = common.RunAndCheckOutput(cmd)
image_size = int(output)
if image_size <= 0:
raise BuildVerityImageError( raise BuildVerityImageError(
"Invalid max image size: {}".format(output)) "Error: image size of {} is larger than partition size of "
return image_size "{}".format(sparse_image_size, self.image_size))
ZeroPadSimg(out_file, self.image_size - sparse_image_size)
def AVBCalcMinPartitionSize(image_size, size_calculator): class VerifiedBootVersion2VerityImageBuilder(VerityImageBuilder):
"""A VerityImageBuilder for Verified Boot 2.0."""
AVB_HASH_FOOTER = 1
AVB_HASHTREE_FOOTER = 2
def __init__(self, partition_name, partition_size, footer_type, avbtool,
key_path, algorithm, salt, signing_args):
self.version = 2
self.partition_name = partition_name
self.partition_size = partition_size
self.footer_type = footer_type
self.avbtool = avbtool
self.algorithm = algorithm
self.key_path = key_path
self.salt = salt
self.signing_args = signing_args
self.image_size = None
def CalculateMinPartitionSize(self, image_size, size_calculator=None):
"""Calculates min partition size for a given image size. """Calculates min partition size for a given image size.
This is used when determining the partition size for a dynamic partition,
which should be cover the given image size (for filesystem files) as well as
the verity metadata size.
Args: Args:
image_size: The size of the image in question. image_size: The size of the image in question.
size_calculator: The function to calculate max image size size_calculator: The function to calculate max image size
@ -292,6 +384,9 @@ def AVBCalcMinPartitionSize(image_size, size_calculator):
Returns: Returns:
The minimum partition size required to accommodate the image size. The minimum partition size required to accommodate the image size.
""" """
if size_calculator is None:
size_calculator = self.CalculateMaxImageSize
# Use image size as partition size to approximate final partition size. # Use image size as partition size to approximate final partition size.
image_ratio = size_calculator(image_size) / float(image_size) image_ratio = size_calculator(image_size) / float(image_size)
@ -332,42 +427,77 @@ def AVBCalcMinPartitionSize(image_size, size_calculator):
lo = mid + BLOCK_SIZE lo = mid + BLOCK_SIZE
logger.info( logger.info(
"AVBCalcMinPartitionSize(%d): partition_size: %d.", "CalculateMinPartitionSize(%d): partition_size %d.", image_size,
image_size, partition_size) partition_size)
return partition_size return partition_size
def CalculateDynamicPartitionSize(self, image_size):
self.partition_size = self.CalculateMinPartitionSize(image_size)
return self.partition_size
def AVBAddFooter(image_path, avbtool, footer_type, partition_size, def CalculateMaxImageSize(self, partition_size=None):
partition_name, key_path, algorithm, salt, """Calculates max image size for a given partition size.
additional_args):
Args:
partition_size: The partition size, which defaults to self.partition_size
if unspecified.
Returns:
The maximum image size.
Raises:
BuildVerityImageError: On error or getting invalid image size.
"""
if partition_size is None:
partition_size = self.partition_size
assert partition_size > 0, \
"Invalid partition size: {}".format(partition_size)
add_footer = ("add_hash_footer" if self.footer_type == self.AVB_HASH_FOOTER
else "add_hashtree_footer")
cmd = [self.avbtool, add_footer, "--partition_size",
str(partition_size), "--calc_max_image_size"]
cmd.extend(shlex.split(self.signing_args))
proc = common.Run(cmd)
output, _ = proc.communicate()
if proc.returncode != 0:
raise BuildVerityImageError(
"Failed to calculate max image size:\n{}".format(output))
image_size = int(output)
if image_size <= 0:
raise BuildVerityImageError(
"Invalid max image size: {}".format(output))
self.image_size = image_size
return image_size
def PadSparseImage(self, out_file):
# No-op as the padding is taken care of by avbtool.
pass
def Build(self, out_file):
"""Adds dm-verity hashtree and AVB metadata to an image. """Adds dm-verity hashtree and AVB metadata to an image.
Args: Args:
image_path: Path to image to modify. out_file: Path to image to modify.
avbtool: String with path to avbtool.
footer_type: 'hash' or 'hashtree' for generating footer.
partition_size: The size of the partition in question.
partition_name: The name of the partition - will be embedded in metadata.
key_path: Path to key to use or None.
algorithm: Name of algorithm to use or None.
salt: The salt to use (a hexadecimal string) or None.
additional_args: Additional arguments to pass to "avbtool add_hash_footer"
or "avbtool add_hashtree_footer".
""" """
cmd = [avbtool, "add_%s_footer" % footer_type, add_footer = ("add_hash_footer" if self.footer_type == self.AVB_HASH_FOOTER
"--partition_size", partition_size, else "add_hashtree_footer")
"--partition_name", partition_name, cmd = [self.avbtool, add_footer,
"--image", image_path] "--partition_size", str(self.partition_size),
"--partition_name", self.partition_name,
"--image", out_file]
if self.key_path and self.algorithm:
cmd.extend(["--key", self.key_path, "--algorithm", self.algorithm])
if self.salt:
cmd.extend(["--salt", self.salt])
cmd.extend(shlex.split(self.signing_args))
if key_path and algorithm: proc = common.Run(cmd)
cmd.extend(["--key", key_path, "--algorithm", algorithm]) output, _ = proc.communicate()
if salt: if proc.returncode != 0:
cmd.extend(["--salt", salt]) raise BuildVerityImageError("Failed to add AVB footer: {}".format(output))
cmd.extend(shlex.split(additional_args))
common.RunAndCheckOutput(cmd)
class HashtreeInfoGenerationError(Exception): class HashtreeInfoGenerationError(Exception):
@ -431,6 +561,20 @@ class VerifiedBootVersion1HashtreeInfoGenerator(HashtreeInfoGenerator):
self.hashtree_size = None self.hashtree_size = None
self.metadata_size = None self.metadata_size = None
prop_dict = {
'partition_size': str(partition_size),
'verity': 'true',
'verity_fec': 'true' if fec_supported else None,
# 'verity_block_device' needs to be present to indicate a verity-enabled
# partition.
'verity_block_device': '',
# We don't need the following properties that are needed for signing the
# verity metadata.
'verity_key': '',
'verity_signer_cmd': None,
}
self.verity_image_builder = CreateVerityImageBuilder(prop_dict)
self.hashtree_info = HashtreeInfo() self.hashtree_info = HashtreeInfo()
def DecomposeSparseImage(self, image): def DecomposeSparseImage(self, image):
@ -447,8 +591,7 @@ class VerifiedBootVersion1HashtreeInfoGenerator(HashtreeInfoGenerator):
"partition size {} doesn't match with the calculated image size." \ "partition size {} doesn't match with the calculated image size." \
" total_blocks: {}".format(self.partition_size, image.total_blocks) " total_blocks: {}".format(self.partition_size, image.total_blocks)
adjusted_size, _ = AdjustPartitionSizeForVerity( adjusted_size = self.verity_image_builder.CalculateMaxImageSize()
self.partition_size, self.fec_supported)
assert adjusted_size % self.block_size == 0 assert adjusted_size % self.block_size == 0
verity_tree_size = GetVerityTreeSize(adjusted_size) verity_tree_size = GetVerityTreeSize(adjusted_size)
@ -504,7 +647,7 @@ class VerifiedBootVersion1HashtreeInfoGenerator(HashtreeInfoGenerator):
def ValidateHashtree(self): def ValidateHashtree(self):
"""Checks that we can reconstruct the verity hash tree.""" """Checks that we can reconstruct the verity hash tree."""
# Writes the file system section to a temp file; and calls the executable # Writes the filesystem section to a temp file; and calls the executable
# build_verity_tree to construct the hash tree. # build_verity_tree to construct the hash tree.
adjusted_partition = common.MakeTempFile(prefix="adjusted_partition") adjusted_partition = common.MakeTempFile(prefix="adjusted_partition")
with open(adjusted_partition, "wb") as fd: with open(adjusted_partition, "wb") as fd: