| /* |
| * Copyright (C) 2016 The Android Open Source Project |
| * |
| * Permission is hereby granted, free of charge, to any person |
| * obtaining a copy of this software and associated documentation |
| * files (the "Software"), to deal in the Software without |
| * restriction, including without limitation the rights to use, copy, |
| * modify, merge, publish, distribute, sublicense, and/or sell copies |
| * of the Software, and to permit persons to whom the Software is |
| * furnished to do so, subject to the following conditions: |
| * |
| * The above copyright notice and this permission notice shall be |
| * included in all copies or substantial portions of the Software. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
| * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
| * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
| * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS |
| * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN |
| * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN |
| * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
| * SOFTWARE. |
| */ |
| |
| #include <iostream> |
| |
| #include <endian.h> |
| #include <inttypes.h> |
| #include <string.h> |
| |
| #include <base/files/file_util.h> |
| #include <base/strings/string_util.h> |
| #include <base/strings/stringprintf.h> |
| |
| #include "avb_sha.h" |
| #include "avb_unittest_util.h" |
| #include "libavb.h" |
| |
| class AvbToolTest : public BaseAvbToolTest { |
| public: |
| AvbToolTest() {} |
| |
| void AddHashFooterTest(bool sparse_image); |
| void AddHashtreeFooterTest(bool sparse_image); |
| }; |
| |
| // This test ensure that the version is increased in both |
| // avb_boot_image.h and the avb tool. |
| TEST_F(AvbToolTest, AvbVersionInSync) { |
| base::FilePath path = testdir_.Append("version.txt"); |
| EXPECT_COMMAND(0, "./avbtool version > %s", path.value().c_str()); |
| std::string printed_version; |
| ASSERT_TRUE(base::ReadFileToString(path, &printed_version)); |
| base::TrimWhitespaceASCII(printed_version, base::TRIM_ALL, &printed_version); |
| std::string expected_version = |
| base::StringPrintf("%d.%d", AVB_MAJOR_VERSION, AVB_MINOR_VERSION); |
| EXPECT_EQ(printed_version, expected_version); |
| } |
| |
| TEST_F(AvbToolTest, ExtractPublicKey) { |
| GenerateVBMetaImage("vbmeta.img", "SHA256_RSA2048", 0, |
| base::FilePath("test/data/testkey_rsa2048.pem")); |
| |
| std::string key_data = |
| PublicKeyAVB(base::FilePath("test/data/testkey_rsa2048.pem")); |
| |
| AvbVBMetaImageHeader h; |
| avb_vbmeta_image_header_to_host_byte_order( |
| reinterpret_cast<AvbVBMetaImageHeader*>(vbmeta_image_.data()), &h); |
| uint8_t* d = reinterpret_cast<uint8_t*>(vbmeta_image_.data()); |
| size_t auxiliary_data_block_offset = |
| sizeof(AvbVBMetaImageHeader) + h.authentication_data_block_size; |
| EXPECT_GT(h.auxiliary_data_block_size, key_data.size()); |
| EXPECT_EQ(0, memcmp(key_data.data(), |
| d + auxiliary_data_block_offset + h.public_key_offset, |
| key_data.size())); |
| } |
| |
| TEST_F(AvbToolTest, CheckDescriptors) { |
| GenerateVBMetaImage("vbmeta.img", "SHA256_RSA2048", 0, |
| base::FilePath("test/data/testkey_rsa2048.pem"), |
| "--prop foo:brillo " |
| "--prop bar:chromeos " |
| "--prop prisoner:24601 " |
| "--prop hexnumber:0xcafe " |
| "--prop hexnumber_capital:0xCAFE " |
| "--prop large_hexnumber:0xfedcba9876543210 " |
| "--prop larger_than_uint64:0xfedcba98765432101 " |
| "--prop almost_a_number:423x " |
| "--prop_from_file blob:test/data/small_blob.bin "); |
| |
| AvbVBMetaImageHeader h; |
| avb_vbmeta_image_header_to_host_byte_order( |
| reinterpret_cast<AvbVBMetaImageHeader*>(vbmeta_image_.data()), &h); |
| |
| EXPECT_EQ(AVB_VBMETA_VERIFY_RESULT_OK, |
| avb_vbmeta_image_verify(vbmeta_image_.data(), vbmeta_image_.size(), |
| nullptr, nullptr)); |
| |
| const char* s; |
| size_t len; |
| uint64_t val; |
| |
| // Basic. |
| s = avb_property_lookup(vbmeta_image_.data(), vbmeta_image_.size(), "foo", 0, |
| &len); |
| EXPECT_EQ(0, strcmp(s, "brillo")); |
| EXPECT_EQ(6U, len); |
| s = avb_property_lookup(vbmeta_image_.data(), vbmeta_image_.size(), "bar", 0, |
| &len); |
| EXPECT_EQ(0, strcmp(s, "chromeos")); |
| EXPECT_EQ(8U, len); |
| s = avb_property_lookup(vbmeta_image_.data(), vbmeta_image_.size(), |
| "non-existant", 0, &len); |
| EXPECT_EQ(0U, len); |
| EXPECT_EQ(NULL, s); |
| |
| // Numbers. |
| EXPECT_NE( |
| 0, avb_property_lookup_uint64(vbmeta_image_.data(), vbmeta_image_.size(), |
| "prisoner", 0, &val)); |
| EXPECT_EQ(24601U, val); |
| |
| EXPECT_NE( |
| 0, avb_property_lookup_uint64(vbmeta_image_.data(), vbmeta_image_.size(), |
| "hexnumber", 0, &val)); |
| EXPECT_EQ(0xcafeU, val); |
| |
| EXPECT_NE( |
| 0, avb_property_lookup_uint64(vbmeta_image_.data(), vbmeta_image_.size(), |
| "hexnumber_capital", 0, &val)); |
| EXPECT_EQ(0xcafeU, val); |
| |
| EXPECT_NE( |
| 0, avb_property_lookup_uint64(vbmeta_image_.data(), vbmeta_image_.size(), |
| "large_hexnumber", 0, &val)); |
| EXPECT_EQ(0xfedcba9876543210U, val); |
| |
| // We could catch overflows and return an error ... but we currently don't. |
| EXPECT_NE( |
| 0, avb_property_lookup_uint64(vbmeta_image_.data(), vbmeta_image_.size(), |
| "larger_than_uint64", 0, &val)); |
| EXPECT_EQ(0xedcba98765432101U, val); |
| |
| // Number-parsing failures. |
| EXPECT_EQ(0, avb_property_lookup_uint64( |
| vbmeta_image_.data(), vbmeta_image_.size(), "foo", 0, &val)); |
| |
| EXPECT_EQ( |
| 0, avb_property_lookup_uint64(vbmeta_image_.data(), vbmeta_image_.size(), |
| "almost_a_number", 0, &val)); |
| |
| // Blobs. |
| // |
| // test/data/small_blob.bin is 21 byte file full of NUL-bytes except |
| // for the string "brillo ftw!" at index 2 and '\n' at the last |
| // byte. |
| s = avb_property_lookup(vbmeta_image_.data(), vbmeta_image_.size(), "blob", 0, |
| &len); |
| EXPECT_EQ(21U, len); |
| EXPECT_EQ(0, memcmp(s, "\0\0", 2)); |
| EXPECT_EQ(0, memcmp(s + 2, "brillo ftw!", 11)); |
| EXPECT_EQ(0, memcmp(s + 13, "\0\0\0\0\0\0\0", 7)); |
| EXPECT_EQ('\n', s[20]); |
| } |
| |
| TEST_F(AvbToolTest, CheckRollbackIndex) { |
| uint64_t rollback_index = 42; |
| GenerateVBMetaImage("vbmeta.img", "SHA256_RSA2048", rollback_index, |
| base::FilePath("test/data/testkey_rsa2048.pem")); |
| |
| AvbVBMetaImageHeader h; |
| avb_vbmeta_image_header_to_host_byte_order( |
| reinterpret_cast<AvbVBMetaImageHeader*>(vbmeta_image_.data()), &h); |
| |
| EXPECT_EQ(rollback_index, h.rollback_index); |
| } |
| |
| TEST_F(AvbToolTest, CheckPubkeyReturned) { |
| GenerateVBMetaImage("vbmeta.img", "SHA256_RSA2048", 0, |
| base::FilePath("test/data/testkey_rsa2048.pem")); |
| |
| const uint8_t* pubkey = NULL; |
| size_t pubkey_length = 0; |
| |
| EXPECT_EQ(AVB_VBMETA_VERIFY_RESULT_OK, |
| avb_vbmeta_image_verify(vbmeta_image_.data(), vbmeta_image_.size(), |
| &pubkey, &pubkey_length)); |
| |
| AvbVBMetaImageHeader h; |
| avb_vbmeta_image_header_to_host_byte_order( |
| reinterpret_cast<AvbVBMetaImageHeader*>(vbmeta_image_.data()), &h); |
| |
| EXPECT_EQ(pubkey_length, h.public_key_size); |
| |
| const uint8_t* expected_pubkey = |
| vbmeta_image_.data() + sizeof(AvbVBMetaImageHeader) + |
| h.authentication_data_block_size + h.public_key_offset; |
| EXPECT_EQ(pubkey, expected_pubkey); |
| } |
| |
| TEST_F(AvbToolTest, Info) { |
| GenerateVBMetaImage("vbmeta.img", "SHA256_RSA2048", 0, |
| base::FilePath("test/data/testkey_rsa2048.pem"), |
| "--prop foo:brillo " |
| "--prop bar:chromeos " |
| "--prop prisoner:24601 " |
| "--prop hexnumber:0xcafe " |
| "--prop hexnumber_capital:0xCAFE " |
| "--prop large_hexnumber:0xfedcba9876543210 " |
| "--prop larger_than_uint64:0xfedcba98765432101 " |
| "--prop almost_a_number:423x " |
| "--prop_from_file blob:test/data/small_blob.bin " |
| "--prop_from_file large_blob:test/data/large_blob.bin"); |
| |
| ASSERT_EQ( |
| "VBMeta image version: 1.0\n" |
| "Header Block: 256 bytes\n" |
| "Authentication Block: 576 bytes\n" |
| "Auxiliary Block: 3200 bytes\n" |
| "Algorithm: SHA256_RSA2048\n" |
| "Rollback Index: 0\n" |
| "Descriptors:\n" |
| " Prop: foo -> 'brillo'\n" |
| " Prop: bar -> 'chromeos'\n" |
| " Prop: prisoner -> '24601'\n" |
| " Prop: hexnumber -> '0xcafe'\n" |
| " Prop: hexnumber_capital -> '0xCAFE'\n" |
| " Prop: large_hexnumber -> '0xfedcba9876543210'\n" |
| " Prop: larger_than_uint64 -> '0xfedcba98765432101'\n" |
| " Prop: almost_a_number -> '423x'\n" |
| " Prop: blob -> '\\x00\\x00brillo " |
| "ftw!\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\n'\n" |
| " Prop: large_blob -> (2048 bytes)\n", |
| InfoImage(vbmeta_image_path_)); |
| } |
| |
| static bool collect_descriptors(const AvbDescriptor* descriptor, |
| void* user_data) { |
| std::vector<const AvbDescriptor*>* descriptors = |
| reinterpret_cast<std::vector<const AvbDescriptor*>*>(user_data); |
| descriptors->push_back(descriptor); |
| return true; // Keep iterating. |
| } |
| |
| static std::string mem_to_hexstring(const uint8_t* data, size_t len) { |
| std::string ret; |
| char digits[17] = "0123456789abcdef"; |
| for (size_t n = 0; n < len; n++) { |
| ret.push_back(digits[data[n] >> 4]); |
| ret.push_back(digits[data[n] & 0x0f]); |
| } |
| return ret; |
| } |
| |
| void AvbToolTest::AddHashFooterTest(bool sparse_image) { |
| const size_t rootfs_size = 1028 * 1024; |
| const size_t partition_size = 1536 * 1024; |
| |
| // Generate a 1028 KiB file with known content. Some content have |
| // been arranged to ensure FILL_DATA segments in the sparse file. |
| std::vector<uint8_t> rootfs; |
| rootfs.resize(rootfs_size); |
| for (size_t n = 0; n < rootfs_size; n++) { |
| if ((n >= 5 * 1000 && n < 105 * 1000) || |
| (n >= 205 * 1000 && n < 305 * 1000) || |
| (n >= 505 * 1000 && n < 605 * 1000)) { |
| rootfs[n] = uint8_t(n) & 0x03; |
| } else { |
| rootfs[n] = uint8_t(n); |
| } |
| } |
| base::FilePath rootfs_path = testdir_.Append("rootfs.bin"); |
| EXPECT_EQ(rootfs_size, |
| static_cast<const size_t>(base::WriteFile( |
| rootfs_path, reinterpret_cast<const char*>(rootfs.data()), |
| rootfs.size()))); |
| |
| if (sparse_image) { |
| EXPECT_COMMAND(0, "mv %s %s.unsparse", rootfs_path.value().c_str(), |
| rootfs_path.value().c_str()); |
| EXPECT_COMMAND(0, "img2simg %s.unsparse %s", rootfs_path.value().c_str(), |
| rootfs_path.value().c_str()); |
| EXPECT_COMMAND(0, "rm -f %s.unsparse", rootfs_path.value().c_str()); |
| } |
| |
| /* Do this twice to check that 'add_hash_footer' is idempotent. */ |
| for (int n = 0; n < 2; n++) { |
| EXPECT_COMMAND(0, |
| "./avbtool add_hash_footer --salt d00df00d " |
| "--hash_algorithm sha256 --image %s " |
| "--partition_size %d --partition_name foobar " |
| "--algorithm SHA256_RSA2048 " |
| "--key test/data/testkey_rsa2048.pem", |
| rootfs_path.value().c_str(), (int)partition_size); |
| |
| ASSERT_EQ(base::StringPrintf("Footer version: 1.0\n" |
| "Image size: 1572864 bytes\n" |
| "Original image size: 1052672 bytes\n" |
| "VBMeta offset: 1052672\n" |
| "VBMeta size: 1472 bytes\n" |
| "--\n" |
| "VBMeta image version: 1.0%s\n" |
| "Header Block: 256 bytes\n" |
| "Authentication Block: 576 bytes\n" |
| "Auxiliary Block: 640 bytes\n" |
| "Algorithm: SHA256_RSA2048\n" |
| "Rollback Index: 0\n" |
| "Descriptors:\n" |
| " Hash descriptor:\n" |
| " Image Size: 1052672 bytes\n" |
| " Hash Algorithm: sha256\n" |
| " Partition Name: foobar\n" |
| " Salt: d00df00d\n" |
| " Digest: " |
| "9a58cc996d405e08a1e00f96dbfe9104fedf41cb83b1f" |
| "5e4ed357fbcf58d88d9\n", |
| sparse_image ? " (Sparse)" : ""), |
| InfoImage(rootfs_path)); |
| } |
| |
| if (sparse_image) { |
| EXPECT_COMMAND(0, "mv %s %s.sparse", rootfs_path.value().c_str(), |
| rootfs_path.value().c_str()); |
| EXPECT_COMMAND(0, "simg2img %s.sparse %s", rootfs_path.value().c_str(), |
| rootfs_path.value().c_str()); |
| EXPECT_COMMAND(0, "rm -f %s.sparse", rootfs_path.value().c_str()); |
| } |
| |
| // Manually calculate the hash to check that it agrees with avbtool. |
| AvbSHA256Ctx hasher_ctx; |
| const uint8_t hasher_salt[4] = {0xd0, 0x0d, 0xf0, 0x0d}; |
| avb_sha256_init(&hasher_ctx); |
| avb_sha256_update(&hasher_ctx, hasher_salt, 4); |
| avb_sha256_update(&hasher_ctx, rootfs.data(), rootfs_size); |
| uint8_t* hasher_digest = avb_sha256_final(&hasher_ctx); |
| EXPECT_EQ("9a58cc996d405e08a1e00f96dbfe9104fedf41cb83b1f5e4ed357fbcf58d88d9", |
| mem_to_hexstring(hasher_digest, AVB_SHA256_DIGEST_SIZE)); |
| |
| // Now check that we can find the VBMeta block again from the footer. |
| std::string part_data; |
| ASSERT_TRUE(base::ReadFileToString(rootfs_path, &part_data)); |
| |
| // Check footer contains correct data. |
| AvbFooter f; |
| EXPECT_NE(0, avb_footer_validate_and_byteswap( |
| reinterpret_cast<const AvbFooter*>( |
| part_data.data() + part_data.size() - AVB_FOOTER_SIZE), |
| &f)); |
| EXPECT_EQ( |
| std::string(reinterpret_cast<const char*>(f.magic), AVB_FOOTER_MAGIC_LEN), |
| AVB_FOOTER_MAGIC); |
| EXPECT_EQ(AVB_FOOTER_MAJOR_VERSION, (int)f.version_major); |
| EXPECT_EQ(AVB_FOOTER_MINOR_VERSION, (int)f.version_minor); |
| EXPECT_EQ(1052672UL, f.original_image_size); |
| EXPECT_EQ(1052672UL, f.vbmeta_offset); |
| EXPECT_EQ(1472UL, f.vbmeta_size); |
| |
| // Check that the vbmeta image at |f.vbmeta_offset| checks out. |
| const uint8_t* vbmeta_data = |
| reinterpret_cast<const uint8_t*>(part_data.data() + f.vbmeta_offset); |
| EXPECT_EQ(AVB_VBMETA_VERIFY_RESULT_OK, |
| avb_vbmeta_image_verify(vbmeta_data, f.vbmeta_size, NULL, NULL)); |
| |
| // Collect all descriptors. |
| std::vector<const AvbDescriptor*> descriptors; |
| avb_descriptor_foreach(vbmeta_data, f.vbmeta_size, collect_descriptors, |
| &descriptors); |
| |
| // We should only have a single descriptor and it should be a |
| // hash descriptor. |
| EXPECT_EQ(1UL, descriptors.size()); |
| EXPECT_EQ(AVB_DESCRIPTOR_TAG_HASH, avb_be64toh(descriptors[0]->tag)); |
| AvbHashDescriptor d; |
| EXPECT_NE( |
| 0, avb_hash_descriptor_validate_and_byteswap( |
| reinterpret_cast<const AvbHashDescriptor*>(descriptors[0]), &d)); |
| EXPECT_EQ(1052672UL, d.image_size); |
| EXPECT_EQ(6UL, d.partition_name_len); |
| EXPECT_EQ(4UL, d.salt_len); |
| EXPECT_EQ(32UL, d.digest_len); |
| const uint8_t* desc_end = reinterpret_cast<const uint8_t*>(descriptors[0]) + |
| sizeof(AvbHashDescriptor); |
| uint64_t o = 0; |
| EXPECT_EQ("foobar", std::string(reinterpret_cast<const char*>(desc_end + o), |
| d.partition_name_len)); |
| o += d.partition_name_len; |
| EXPECT_EQ("d00df00d", mem_to_hexstring(desc_end + o, d.salt_len)); |
| o += d.salt_len; |
| EXPECT_EQ("9a58cc996d405e08a1e00f96dbfe9104fedf41cb83b1f5e4ed357fbcf58d88d9", |
| mem_to_hexstring(desc_end + o, d.digest_len)); |
| |
| // Check that the footer is correctly erased. |
| EXPECT_COMMAND(0, "./avbtool erase_footer --image %s", |
| rootfs_path.value().c_str()); |
| int64_t erased_footer_file_size; |
| ASSERT_TRUE(base::GetFileSize(rootfs_path, &erased_footer_file_size)); |
| EXPECT_EQ(static_cast<size_t>(erased_footer_file_size), rootfs_size); |
| } |
| |
| TEST_F(AvbToolTest, AddHashFooter) { AddHashFooterTest(false); } |
| |
| TEST_F(AvbToolTest, AddHashFooterSparse) { AddHashFooterTest(true); } |
| |
| TEST_F(AvbToolTest, AddHashFooterSparseWithHoleAtTheEnd) { |
| const size_t partition_size = 10 * 1024 * 1024; |
| const size_t metadata_size = 128 * 1024; |
| |
| // It's not enough to run img2simg on a file with a lot of zeroes at |
| // the end since that will turn up as "Fill with value (for value = |
| // 0x00000000)" and not "Don't care". Instead, use make_ext4fs for |
| // this since it will put a big hole (e.g. "Don't care" chunk) at |
| // the end. |
| base::FilePath partition_path = testdir_.Append("partition.bin"); |
| EXPECT_COMMAND(0, "make_ext4fs -s -L test -l %zd %s", |
| partition_size - metadata_size, |
| partition_path.value().c_str()); |
| |
| EXPECT_COMMAND(0, |
| "./avbtool add_hash_footer --salt d00df00d " |
| "--hash_algorithm sha256 --image %s " |
| "--partition_size %d --partition_name foobar " |
| "--algorithm SHA256_RSA2048 " |
| "--key test/data/testkey_rsa2048.pem", |
| partition_path.value().c_str(), (int)partition_size); |
| |
| // NOTE: This could start failing if there are changes to |
| // make_ext4fs. If that happens, just update this test. |
| ASSERT_EQ( |
| "Footer version: 1.0\n" |
| "Image size: 10485760 bytes\n" |
| "Original image size: 10354688 bytes\n" |
| "VBMeta offset: 10354688\n" |
| "VBMeta size: 1472 bytes\n" |
| "--\n" |
| "VBMeta image version: 1.0 (Sparse)\n" |
| "Header Block: 256 bytes\n" |
| "Authentication Block: 576 bytes\n" |
| "Auxiliary Block: 640 bytes\n" |
| "Algorithm: SHA256_RSA2048\n" |
| "Rollback Index: 0\n" |
| "Descriptors:\n" |
| " Hash descriptor:\n" |
| " Image Size: 10354688 bytes\n" |
| " Hash Algorithm: sha256\n" |
| " Partition Name: foobar\n" |
| " Salt: d00df00d\n" |
| " Digest: " |
| "f086a7b8f6c4e9b8c7607423f81cb4be7aca9865f65b37fb8d2ee544eb27e141\n", |
| InfoImage(partition_path)); |
| |
| EXPECT_COMMAND(0, "mv %s %s.sparse", partition_path.value().c_str(), |
| partition_path.value().c_str()); |
| EXPECT_COMMAND(0, "simg2img %s.sparse %s", partition_path.value().c_str(), |
| partition_path.value().c_str()); |
| EXPECT_COMMAND(0, "rm -f %s.sparse", partition_path.value().c_str()); |
| } |
| |
| void AvbToolTest::AddHashtreeFooterTest(bool sparse_image) { |
| const size_t rootfs_size = 1028 * 1024; |
| const size_t partition_size = 1536 * 1024; |
| |
| // Generate a 1028 KiB file with known content. |
| std::vector<uint8_t> rootfs; |
| rootfs.resize(rootfs_size); |
| for (size_t n = 0; n < rootfs_size; n++) rootfs[n] = uint8_t(n); |
| base::FilePath rootfs_path = testdir_.Append("rootfs.bin"); |
| EXPECT_EQ(rootfs_size, |
| static_cast<const size_t>(base::WriteFile( |
| rootfs_path, reinterpret_cast<const char*>(rootfs.data()), |
| rootfs.size()))); |
| |
| if (sparse_image) { |
| EXPECT_COMMAND(0, "mv %s %s.unsparse", rootfs_path.value().c_str(), |
| rootfs_path.value().c_str()); |
| EXPECT_COMMAND(0, "img2simg %s.unsparse %s", rootfs_path.value().c_str(), |
| rootfs_path.value().c_str()); |
| EXPECT_COMMAND(0, "rm -f %s.unsparse", rootfs_path.value().c_str()); |
| } |
| |
| /* Do this twice to check that 'add_hashtree_footer' is idempotent. */ |
| for (int n = 0; n < 2; n++) { |
| EXPECT_COMMAND(0, |
| "./avbtool add_hashtree_footer --salt d00df00d --image %s " |
| "--partition_size %d --partition_name foobar " |
| "--algorithm SHA256_RSA2048 " |
| "--key test/data/testkey_rsa2048.pem", |
| rootfs_path.value().c_str(), (int)partition_size); |
| |
| ASSERT_EQ(base::StringPrintf("Footer version: 1.0\n" |
| "Image size: 1572864 bytes\n" |
| "Original image size: 1052672 bytes\n" |
| "VBMeta offset: 1069056\n" |
| "VBMeta size: 1536 bytes\n" |
| "--\n" |
| "VBMeta image version: 1.0%s\n" |
| "Header Block: 256 bytes\n" |
| "Authentication Block: 576 bytes\n" |
| "Auxiliary Block: 704 bytes\n" |
| "Algorithm: SHA256_RSA2048\n" |
| "Rollback Index: 0\n" |
| "Descriptors:\n" |
| " Hashtree descriptor:\n" |
| " Version of dm-verity: 1\n" |
| " Image Size: 1052672 bytes\n" |
| " Tree Offset: 1052672\n" |
| " Tree Size: 16384 bytes\n" |
| " Data Block Size: 4096 bytes\n" |
| " Hash Block Size: 4096 bytes\n" |
| " Hash Algorithm: sha1\n" |
| " Partition Name: foobar\n" |
| " Salt: d00df00d\n" |
| " Root Digest: " |
| "e811611467dcd6e8dc4324e45f706c2bdd51db67\n", |
| sparse_image ? " (Sparse)" : ""), |
| InfoImage(rootfs_path)); |
| } |
| |
| if (sparse_image) { |
| EXPECT_COMMAND(0, "mv %s %s.sparse", rootfs_path.value().c_str(), |
| rootfs_path.value().c_str()); |
| EXPECT_COMMAND(0, "simg2img %s.sparse %s", rootfs_path.value().c_str(), |
| rootfs_path.value().c_str()); |
| EXPECT_COMMAND(0, "rm -f %s.sparse", rootfs_path.value().c_str()); |
| } |
| |
| // To check that we generate the correct hashtree we can use |
| // veritysetup(1) - another codebase for working with dm-verity |
| // hashtrees - to verify it. |
| // |
| // If we don't want to impose the requirement of having the |
| // veritysetup(1) command available on builders we can comment this |
| // out. |
| EXPECT_COMMAND(0, |
| "veritysetup --no-superblock --format=1 --hash=sha1 " |
| "--data-block-size=4096 --hash-block-size=4096 " |
| "--salt=d00df00d " |
| "--data-blocks=257 " |
| "--hash-offset=1052672 " |
| "verify " |
| "%s %s " |
| "e811611467dcd6e8dc4324e45f706c2bdd51db67", |
| rootfs_path.value().c_str(), rootfs_path.value().c_str()); |
| |
| // Now check that we can find the VBMeta block again from the footer. |
| std::string part_data; |
| ASSERT_TRUE(base::ReadFileToString(rootfs_path, &part_data)); |
| |
| // Check footer contains correct data. |
| AvbFooter f; |
| EXPECT_NE(0, avb_footer_validate_and_byteswap( |
| reinterpret_cast<const AvbFooter*>( |
| part_data.data() + part_data.size() - AVB_FOOTER_SIZE), |
| &f)); |
| EXPECT_EQ( |
| std::string(reinterpret_cast<const char*>(f.magic), AVB_FOOTER_MAGIC_LEN), |
| AVB_FOOTER_MAGIC); |
| EXPECT_EQ(AVB_FOOTER_MAJOR_VERSION, (int)f.version_major); |
| EXPECT_EQ(AVB_FOOTER_MINOR_VERSION, (int)f.version_minor); |
| EXPECT_EQ(1052672UL, f.original_image_size); |
| EXPECT_EQ(1069056UL, f.vbmeta_offset); |
| EXPECT_EQ(1536UL, f.vbmeta_size); |
| |
| // Check that the vbmeta image at |f.vbmeta_offset| checks out. |
| const uint8_t* vbmeta_data = |
| reinterpret_cast<const uint8_t*>(part_data.data() + f.vbmeta_offset); |
| EXPECT_EQ(AVB_VBMETA_VERIFY_RESULT_OK, |
| avb_vbmeta_image_verify(vbmeta_data, f.vbmeta_size, NULL, NULL)); |
| |
| // Collect all descriptors. |
| std::vector<const AvbDescriptor*> descriptors; |
| avb_descriptor_foreach(vbmeta_data, f.vbmeta_size, collect_descriptors, |
| &descriptors); |
| |
| // We should only have a single descriptor and it should be a |
| // hashtree descriptor. |
| EXPECT_EQ(1UL, descriptors.size()); |
| EXPECT_EQ(AVB_DESCRIPTOR_TAG_HASHTREE, avb_be64toh(descriptors[0]->tag)); |
| AvbHashtreeDescriptor d; |
| EXPECT_NE( |
| 0, |
| avb_hashtree_descriptor_validate_and_byteswap( |
| reinterpret_cast<const AvbHashtreeDescriptor*>(descriptors[0]), &d)); |
| EXPECT_EQ(1UL, d.dm_verity_version); |
| EXPECT_EQ(1052672UL, d.image_size); |
| EXPECT_EQ(1052672UL, d.tree_offset); |
| EXPECT_EQ(16384UL, d.tree_size); |
| EXPECT_EQ(4096UL, d.data_block_size); |
| EXPECT_EQ(4096UL, d.hash_block_size); |
| EXPECT_EQ(6UL, d.partition_name_len); |
| EXPECT_EQ(4UL, d.salt_len); |
| EXPECT_EQ(20UL, d.root_digest_len); |
| const uint8_t* desc_end = reinterpret_cast<const uint8_t*>(descriptors[0]) + |
| sizeof(AvbHashtreeDescriptor); |
| uint64_t o = 0; |
| EXPECT_EQ("foobar", std::string(reinterpret_cast<const char*>(desc_end + o), |
| d.partition_name_len)); |
| o += d.partition_name_len; |
| EXPECT_EQ("d00df00d", mem_to_hexstring(desc_end + o, d.salt_len)); |
| o += d.salt_len; |
| EXPECT_EQ("e811611467dcd6e8dc4324e45f706c2bdd51db67", |
| mem_to_hexstring(desc_end + o, d.root_digest_len)); |
| |
| // Check that we correctly generate dm-verity kernel cmdline |
| // snippets, if requested. |
| base::FilePath vbmeta_dmv_path = testdir_.Append("vbmeta_dm_verity_desc.bin"); |
| EXPECT_COMMAND(0, |
| "./avbtool make_vbmeta_image " |
| "--output %s " |
| "--generate_dm_verity_cmdline_from_hashtree %s " |
| "--algorithm SHA256_RSA2048 " |
| "--key test/data/testkey_rsa2048.pem", |
| vbmeta_dmv_path.value().c_str(), rootfs_path.value().c_str()); |
| |
| ASSERT_EQ( |
| "VBMeta image version: 1.0\n" |
| "Header Block: 256 bytes\n" |
| "Authentication Block: 576 bytes\n" |
| "Auxiliary Block: 768 bytes\n" |
| "Algorithm: SHA256_RSA2048\n" |
| "Rollback Index: 0\n" |
| "Descriptors:\n" |
| " Kernel Cmdline descriptor:\n" |
| " Kernel Cmdline: 'dm=\"1 vroot none ro 1,0 2056 verity 1 " |
| "PARTUUID=$(ANDROID_SYSTEM_PARTUUID) PARTUUID=$(ANDROID_SYSTEM_PARTUUID) " |
| "4096 4096 257 257 sha1 e811611467dcd6e8dc4324e45f706c2bdd51db67 " |
| "d00df00d\"'\n", |
| InfoImage(vbmeta_dmv_path)); |
| |
| // Check that the footer is correctly erased and the hashtree |
| // remains - see above for why the constant 1069056 is used. |
| EXPECT_COMMAND(0, "./avbtool erase_footer --image %s --keep_hashtree", |
| rootfs_path.value().c_str()); |
| int64_t erased_footer_file_size; |
| ASSERT_TRUE(base::GetFileSize(rootfs_path, &erased_footer_file_size)); |
| EXPECT_EQ(static_cast<size_t>(erased_footer_file_size), 1069056UL); |
| } |
| |
| TEST_F(AvbToolTest, AddHashtreeFooter) { AddHashtreeFooterTest(false); } |
| |
| TEST_F(AvbToolTest, AddHashtreeFooterSparse) { AddHashtreeFooterTest(true); } |
| |
| TEST_F(AvbToolTest, AddHashtreeFooterCalcMaxImageSize) { |
| const size_t partition_size = 10 * 1024 * 1024; |
| base::FilePath output_path = testdir_.Append("max_size.txt"); |
| |
| EXPECT_COMMAND(0, |
| "./avbtool add_hashtree_footer " |
| "--partition_size %zd --calc_max_image_size > %s", |
| partition_size, output_path.value().c_str()); |
| std::string max_image_size_data; |
| EXPECT_TRUE(base::ReadFileToString(output_path, &max_image_size_data)); |
| EXPECT_EQ("10330112\n", max_image_size_data); |
| size_t max_image_size = atoll(max_image_size_data.c_str()); |
| |
| // Hashtree and metadata takes up 152 KiB. |
| EXPECT_EQ(152 * 1024ULL, partition_size - max_image_size); |
| |
| // Check that we can add a hashtree with an image this size for such |
| // a partition size. |
| base::FilePath system_path = GenerateImage("system", max_image_size); |
| EXPECT_COMMAND(0, |
| "./avbtool add_hashtree_footer" |
| " --image %s" |
| " --partition_name system" |
| " --partition_size %zd" |
| " --salt deadbeef" |
| " --algorithm SHA512_RSA4096 " |
| " --key test/data/testkey_rsa4096.pem", |
| system_path.value().c_str(), partition_size); |
| } |
| |
| TEST_F(AvbToolTest, KernelCmdlineDescriptor) { |
| base::FilePath vbmeta_path = |
| testdir_.Append("vbmeta_kernel_cmdline_desc.bin"); |
| |
| EXPECT_COMMAND(0, |
| "./avbtool make_vbmeta_image " |
| "--output %s " |
| "--kernel_cmdline 'foo bar baz' " |
| "--kernel_cmdline 'second cmdline' " |
| "--algorithm SHA256_RSA2048 " |
| "--key test/data/testkey_rsa2048.pem", |
| vbmeta_path.value().c_str()); |
| |
| ASSERT_EQ( |
| "VBMeta image version: 1.0\n" |
| "Header Block: 256 bytes\n" |
| "Authentication Block: 576 bytes\n" |
| "Auxiliary Block: 640 bytes\n" |
| "Algorithm: SHA256_RSA2048\n" |
| "Rollback Index: 0\n" |
| "Descriptors:\n" |
| " Kernel Cmdline descriptor:\n" |
| " Kernel Cmdline: 'foo bar baz'\n" |
| " Kernel Cmdline descriptor:\n" |
| " Kernel Cmdline: 'second cmdline'\n", |
| InfoImage(vbmeta_path)); |
| |
| // Now check the VBMeta image. |
| std::string image_data; |
| ASSERT_TRUE(base::ReadFileToString(vbmeta_path, &image_data)); |
| |
| const uint8_t* vbmeta_data = |
| reinterpret_cast<const uint8_t*>(image_data.data()); |
| const size_t vbmeta_size = image_data.length(); |
| EXPECT_EQ(AVB_VBMETA_VERIFY_RESULT_OK, |
| avb_vbmeta_image_verify(vbmeta_data, vbmeta_size, NULL, NULL)); |
| |
| // Collect all descriptors. |
| std::vector<const AvbDescriptor*> descriptors; |
| avb_descriptor_foreach(vbmeta_data, vbmeta_size, collect_descriptors, |
| &descriptors); |
| |
| // We should have two descriptors - check them. |
| EXPECT_EQ(2UL, descriptors.size()); |
| AvbKernelCmdlineDescriptor d; |
| EXPECT_EQ(AVB_DESCRIPTOR_TAG_KERNEL_CMDLINE, |
| avb_be64toh(descriptors[0]->tag)); |
| EXPECT_NE( |
| 0, |
| avb_kernel_cmdline_descriptor_validate_and_byteswap( |
| reinterpret_cast<const AvbKernelCmdlineDescriptor*>(descriptors[0]), |
| &d)); |
| EXPECT_EQ("foo bar baz", |
| std::string(reinterpret_cast<const char*>(descriptors[0]) + |
| sizeof(AvbKernelCmdlineDescriptor), |
| d.kernel_cmdline_length)); |
| EXPECT_EQ(AVB_DESCRIPTOR_TAG_KERNEL_CMDLINE, |
| avb_be64toh(descriptors[1]->tag)); |
| EXPECT_NE( |
| 0, |
| avb_kernel_cmdline_descriptor_validate_and_byteswap( |
| reinterpret_cast<const AvbKernelCmdlineDescriptor*>(descriptors[1]), |
| &d)); |
| EXPECT_EQ("second cmdline", |
| std::string(reinterpret_cast<const char*>(descriptors[1]) + |
| sizeof(AvbKernelCmdlineDescriptor), |
| d.kernel_cmdline_length)); |
| } |
| |
| TEST_F(AvbToolTest, IncludeDescriptor) { |
| base::FilePath vbmeta1_path = testdir_.Append("vbmeta_id1.bin"); |
| base::FilePath vbmeta2_path = testdir_.Append("vbmeta_id2.bin"); |
| base::FilePath vbmeta3_path = testdir_.Append("vbmeta_id3.bin"); |
| |
| EXPECT_COMMAND(0, |
| "./avbtool make_vbmeta_image " |
| "--output %s " |
| "--kernel_cmdline 'something' " |
| "--prop name:value ", |
| vbmeta1_path.value().c_str()); |
| |
| EXPECT_COMMAND(0, |
| "./avbtool make_vbmeta_image " |
| "--output %s " |
| "--prop name2:value2 " |
| "--prop name3:value3 ", |
| vbmeta2_path.value().c_str()); |
| |
| EXPECT_COMMAND(0, |
| "./avbtool make_vbmeta_image " |
| "--output %s " |
| "--prop name4:value4 " |
| "--include_descriptors_from_image %s " |
| "--include_descriptors_from_image %s ", |
| vbmeta3_path.value().c_str(), vbmeta1_path.value().c_str(), |
| vbmeta2_path.value().c_str()); |
| |
| ASSERT_EQ( |
| "VBMeta image version: 1.0\n" |
| "Header Block: 256 bytes\n" |
| "Authentication Block: 0 bytes\n" |
| "Auxiliary Block: 256 bytes\n" |
| "Algorithm: NONE\n" |
| "Rollback Index: 0\n" |
| "Descriptors:\n" |
| " Prop: name4 -> 'value4'\n" |
| " Prop: name -> 'value'\n" |
| " Kernel Cmdline descriptor:\n" |
| " Kernel Cmdline: 'something'\n" |
| " Prop: name2 -> 'value2'\n" |
| " Prop: name3 -> 'value3'\n", |
| InfoImage(vbmeta3_path)); |
| } |
| |
| TEST_F(AvbToolTest, ChainedPartition) { |
| base::FilePath vbmeta_path = testdir_.Append("vbmeta_cp.bin"); |
| |
| base::FilePath pk_path = testdir_.Append("testkey_rsa2048.avbpubkey"); |
| |
| EXPECT_COMMAND( |
| 0, |
| "./avbtool extract_public_key --key test/data/testkey_rsa2048.pem" |
| " --output %s", |
| pk_path.value().c_str()); |
| |
| EXPECT_COMMAND( |
| 0, |
| "./avbtool make_vbmeta_image " |
| "--output %s " |
| "--chain_partition system:1:%s " |
| "--algorithm SHA256_RSA2048 --key test/data/testkey_rsa2048.pem", |
| vbmeta_path.value().c_str(), pk_path.value().c_str()); |
| |
| ASSERT_EQ( |
| "VBMeta image version: 1.0\n" |
| "Header Block: 256 bytes\n" |
| "Authentication Block: 576 bytes\n" |
| "Auxiliary Block: 1088 bytes\n" |
| "Algorithm: SHA256_RSA2048\n" |
| "Rollback Index: 0\n" |
| "Descriptors:\n" |
| " Chain Partition descriptor:\n" |
| " Partition Name: system\n" |
| " Rollback Index Slot: 1\n" |
| " Public key (sha1): cdbb77177f731920bbe0a0f94f84d9038ae0617d\n", |
| InfoImage(vbmeta_path)); |
| |
| // Now check the VBMeta image. |
| std::string image_data; |
| ASSERT_TRUE(base::ReadFileToString(vbmeta_path, &image_data)); |
| |
| const uint8_t* vbmeta_data = |
| reinterpret_cast<const uint8_t*>(image_data.data()); |
| const size_t vbmeta_size = image_data.length(); |
| EXPECT_EQ(AVB_VBMETA_VERIFY_RESULT_OK, |
| avb_vbmeta_image_verify(vbmeta_data, vbmeta_size, NULL, NULL)); |
| |
| // Collect all descriptors. |
| std::vector<const AvbDescriptor*> descriptors; |
| avb_descriptor_foreach(vbmeta_data, vbmeta_size, collect_descriptors, |
| &descriptors); |
| |
| // We should have one descriptor - check it. |
| EXPECT_EQ(1UL, descriptors.size()); |
| |
| std::string pk_data; |
| ASSERT_TRUE(base::ReadFileToString(pk_path, &pk_data)); |
| |
| AvbChainPartitionDescriptor d; |
| EXPECT_EQ(AVB_DESCRIPTOR_TAG_CHAIN_PARTITION, |
| avb_be64toh(descriptors[0]->tag)); |
| EXPECT_NE( |
| 0, |
| avb_chain_partition_descriptor_validate_and_byteswap( |
| reinterpret_cast<const AvbChainPartitionDescriptor*>(descriptors[0]), |
| &d)); |
| const uint8_t* desc_end = reinterpret_cast<const uint8_t*>(descriptors[0]) + |
| sizeof(AvbChainPartitionDescriptor); |
| uint64_t o = 0; |
| EXPECT_EQ("system", std::string(reinterpret_cast<const char*>(desc_end + o), |
| d.partition_name_len)); |
| o += d.partition_name_len; |
| EXPECT_EQ(pk_data, std::string(reinterpret_cast<const char*>(descriptors[0]) + |
| sizeof(AvbChainPartitionDescriptor) + o, |
| d.public_key_len)); |
| } |