| // Copyright 2018 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <assert.h> |
| #include <stdint.h> |
| #include <stdlib.h> |
| #include <string.h> |
| |
| #include <fbl/auto_call.h> |
| #include <zircon/boot/image.h> |
| #include <zircon/compiler.h> |
| |
| #include <unittest/unittest.h> |
| |
| #include <libzbi/zbi-cpp.h> |
| |
| const char kTestCmdline[] = "0123"; |
| constexpr size_t kCmdlinePayloadLen = ZBI_ALIGN(sizeof(kTestCmdline)); |
| |
| const char kTestRD[] = "0123456789"; |
| constexpr size_t kRdPayloadLen = ZBI_ALIGN(sizeof(kTestRD)); |
| |
| const char kTestBootfs[] = "abcdefghijklmnopqrs"; |
| constexpr size_t kBootfsPayloadLen = ZBI_ALIGN(sizeof(kTestBootfs)); |
| |
| const char kAppendRD[] = "ABCDEFG"; |
| |
| typedef struct test_zbi { |
| // Bootdata header. |
| zbi_header_t header; |
| |
| zbi_header_t cmdline_hdr; |
| char cmdline_payload[kCmdlinePayloadLen]; |
| |
| zbi_header_t ramdisk_hdr; |
| char ramdisk_payload[kRdPayloadLen]; |
| |
| zbi_header_t bootfs_hdr; |
| char bootfs_payload[kBootfsPayloadLen]; |
| } __PACKED test_zbi_t; |
| |
| static_assert(sizeof(test_zbi_t) % ZBI_ALIGNMENT == 0, ""); |
| |
| static void init_zbi_header(zbi_header_t* hdr) { |
| hdr->flags = ZBI_FLAG_VERSION; |
| hdr->reserved0 = 0; |
| hdr->reserved1 = 0; |
| hdr->magic = ZBI_ITEM_MAGIC; |
| hdr->crc32 = ZBI_ITEM_NO_CRC32; |
| hdr->extra = 0; |
| } |
| |
| static uint8_t* get_test_zbi_extra(const size_t extra_bytes) { |
| const size_t kAllocSize = sizeof(test_zbi_t) + extra_bytes; |
| test_zbi_t* result = reinterpret_cast<test_zbi_t*>(malloc(kAllocSize)); |
| |
| if (!result) return nullptr; |
| |
| // Extra bytes are filled with non-zero bytes to test zero padding. |
| if (extra_bytes > 0) { |
| memset(result, 0xab, kAllocSize); |
| } |
| memset(result, 0, sizeof(*result)); |
| |
| init_zbi_header(&result->header); |
| result->header.type = ZBI_TYPE_CONTAINER; |
| result->header.extra = ZBI_CONTAINER_MAGIC; |
| |
| init_zbi_header(&result->cmdline_hdr); |
| result->cmdline_hdr.type = ZBI_TYPE_CMDLINE; |
| strcpy(result->cmdline_payload, kTestCmdline); |
| result->cmdline_hdr.length = static_cast<uint32_t>(sizeof(kTestCmdline)); |
| |
| init_zbi_header(&result->ramdisk_hdr); |
| result->ramdisk_hdr.type = ZBI_TYPE_STORAGE_RAMDISK; |
| strcpy(result->ramdisk_payload, kTestRD); |
| result->ramdisk_hdr.length = static_cast<uint32_t>(sizeof(kTestRD)); |
| |
| init_zbi_header(&result->bootfs_hdr); |
| result->bootfs_hdr.type = ZBI_TYPE_STORAGE_BOOTFS; |
| strcpy(result->bootfs_payload, kTestBootfs); |
| result->bootfs_hdr.length = static_cast<uint32_t>(sizeof(kTestBootfs)); |
| |
| // The container's length is always kept aligned, though each item |
| // header within the container might have an unaligned length and |
| // padding bytes after that item's payload so that the following header |
| // (or the end of the container) is aligned. |
| result->header.length = |
| static_cast<uint32_t>(sizeof(*result) - sizeof(zbi_header_t)); |
| return reinterpret_cast<uint8_t*>(result); |
| } |
| |
| static uint8_t* get_test_zbi() { |
| return get_test_zbi_extra(0); |
| } |
| |
| static zbi_result_t check_contents(zbi_header_t* hdr, void* payload, |
| void* cookie) { |
| const char* expected = nullptr; |
| const char* actual = reinterpret_cast<const char*>(payload); |
| |
| switch (hdr->type) { |
| case ZBI_TYPE_CMDLINE: |
| expected = kTestCmdline; |
| break; |
| case ZBI_TYPE_STORAGE_RAMDISK: |
| expected = kTestRD; |
| break; |
| case ZBI_TYPE_STORAGE_BOOTFS: |
| expected = kTestBootfs; |
| break; |
| default: |
| return ZBI_RESULT_ERROR; |
| } |
| |
| int* itemsProcessed = reinterpret_cast<int*>(cookie); |
| (*itemsProcessed)++; |
| |
| if (!strcmp(expected, actual)) { |
| return ZBI_RESULT_OK; |
| } else { |
| return ZBI_RESULT_ERROR; |
| } |
| } |
| |
| static bool zbi_test_basic(void) { |
| BEGIN_TEST; |
| uint8_t* test_zbi = get_test_zbi(); |
| |
| auto cleanup = fbl::MakeAutoCall([test_zbi]() { |
| free(test_zbi); |
| }); |
| |
| ASSERT_NONNULL(test_zbi, "failed to alloc test image"); |
| |
| zbi::Zbi image(test_zbi); |
| |
| zbi_header_t* trace = nullptr; |
| ASSERT_EQ(image.Check(&trace), ZBI_RESULT_OK, "malformed image"); |
| |
| // zbi.Check should only give us diagnostics about the error if there was |
| // an error in the first place. |
| ASSERT_NULL(trace, "bad header set but image reported okay?"); |
| |
| int count = 0; |
| zbi_result_t result = image.ForEach(check_contents, &count); |
| |
| ASSERT_EQ(result, ZBI_RESULT_OK, "content check failed"); |
| |
| ASSERT_EQ(count, 3, "bad bootdata item count"); |
| |
| END_TEST; |
| } |
| |
| static bool zbi_test_bad_container(void) { |
| BEGIN_TEST; |
| |
| uint8_t* test_zbi = get_test_zbi(); |
| |
| auto cleanup = fbl::MakeAutoCall([test_zbi]() { |
| free(test_zbi); |
| }); |
| |
| ASSERT_NONNULL(test_zbi, "failed to alloc test image"); |
| |
| zbi_header_t* bootdata_header = reinterpret_cast<zbi_header_t*>(test_zbi); |
| // Set to something arbitrary |
| bootdata_header->type = ZBI_TYPE_STORAGE_BOOTFS; |
| |
| zbi::Zbi image(test_zbi); |
| |
| zbi_header_t* problem_header = nullptr; |
| ASSERT_NE(image.Check(&problem_header), ZBI_RESULT_OK, |
| "bad container fault not detected"); |
| |
| // Make sure that the diagnostic information tells us that the container is |
| // bad. |
| ASSERT_EQ(problem_header, bootdata_header); |
| |
| END_TEST; |
| } |
| |
| static bool zbi_test_truncated(void) { |
| BEGIN_TEST; |
| uint8_t* test_zbi = get_test_zbi(); |
| |
| auto cleanup = fbl::MakeAutoCall([test_zbi]() { |
| free(test_zbi); |
| }); |
| |
| ASSERT_NONNULL(test_zbi, "failed to alloc test image"); |
| |
| zbi::Zbi image(test_zbi); |
| |
| zbi_header_t* bootdata_header = reinterpret_cast<zbi_header_t*>(test_zbi); |
| bootdata_header->length -= 8; // Truncate the image. |
| |
| zbi_header_t* trace = nullptr; |
| ASSERT_NE(image.Check(&trace), ZBI_RESULT_OK, |
| "Truncated image reported as okay"); |
| |
| // zbi.Check should only give us diagnostics about the error if there was |
| // an error in the first place. |
| ASSERT_NONNULL(trace, "Bad image with no trace diagnostics?"); |
| |
| int count = 0; |
| zbi_result_t result = image.ForEach(check_contents, &count); |
| |
| ASSERT_NE(result, ZBI_RESULT_OK, |
| "Truncated image not reported as truncated"); |
| |
| ASSERT_EQ(count, 3, "bad bootdata item count"); |
| |
| END_TEST; |
| } |
| |
| static bool zbi_test_append(void) { |
| BEGIN_TEST; |
| // Allocate an additional kExtraBytes at the end of the ZBI to test |
| // appending. |
| const size_t kExtraBytes = sizeof(zbi_header_t) + sizeof(kAppendRD); |
| uint8_t* test_zbi = get_test_zbi_extra(kExtraBytes); |
| uint8_t* reference_zbi = get_test_zbi(); |
| |
| test_zbi_t* test_image = reinterpret_cast<test_zbi_t*>(test_zbi); |
| test_zbi_t* reference_image = reinterpret_cast<test_zbi_t*>(reference_zbi); |
| |
| auto cleanup = fbl::MakeAutoCall([test_zbi, reference_zbi]() { |
| free(test_zbi); |
| free(reference_zbi); |
| }); |
| |
| ASSERT_NONNULL(test_zbi, "failed to alloc test image"); |
| |
| const size_t kBufferSize = sizeof(test_zbi_t) + kExtraBytes; |
| zbi::Zbi image(test_zbi, kBufferSize); |
| |
| zbi_result_t result = image.AppendSection( |
| static_cast<uint32_t>(sizeof(kAppendRD)), // Length |
| ZBI_TYPE_STORAGE_RAMDISK, // Type |
| 0, // Extra |
| 0, // Flags |
| reinterpret_cast<const void*>(kAppendRD) // Payload. |
| ); |
| |
| ASSERT_EQ(result, ZBI_RESULT_OK, "Append failed"); |
| |
| // Make sure the image is valid. |
| ASSERT_EQ(image.Check(nullptr), ZBI_RESULT_OK, |
| "append produced invalid images"); |
| |
| // Verify the integrity of the data. |
| reference_image->header.length = test_image->header.length; |
| ASSERT_EQ(memcmp(test_zbi, reference_zbi, sizeof(test_zbi_t)), 0, |
| "Append corrupted image"); |
| |
| END_TEST; |
| } |
| |
| // Make sure we never overflow the ZBI's buffer by appending. |
| static bool zbi_test_append_full(void) { |
| BEGIN_TEST; |
| |
| // Enough space for a small payload |
| const size_t kMaxAppendPayloadSize = ZBI_ALIGN(5); |
| const size_t kExtraBytes = sizeof(zbi_header_t) + kMaxAppendPayloadSize; |
| const size_t kZbiSize = sizeof(test_zbi_t) + kExtraBytes; |
| const size_t kExtraSentinelLength = 64; |
| |
| uint8_t* test_zbi = get_test_zbi_extra(kExtraBytes + kExtraSentinelLength); |
| |
| ASSERT_NONNULL(test_zbi, "failed to alloc test image"); |
| |
| auto cleanup = fbl::MakeAutoCall([test_zbi]{ |
| free(test_zbi); |
| }); |
| |
| // Fill the space after the buffer with sentinel bytes and make sure those |
| // bytes are never touched by the append operation. |
| const uint8_t kSentinelByte = 0xa5; // 0b1010 1010 0101 0101 |
| memset(test_zbi + kZbiSize, kSentinelByte, kExtraSentinelLength); |
| |
| zbi::Zbi image(test_zbi, kZbiSize); |
| |
| const uint8_t kDataByte = 0xc3; |
| uint8_t dataBuffer[kMaxAppendPayloadSize + 1]; |
| memset(dataBuffer, kDataByte, kMaxAppendPayloadSize); |
| |
| // Try to append a buffer that's one byte too big and make sure we reject |
| // it. |
| zbi_result_t res = image.AppendSection( |
| kMaxAppendPayloadSize + 1, // One more than the max length! |
| ZBI_TYPE_STORAGE_RAMDISK, |
| 0, |
| 0, |
| reinterpret_cast<const void*>(dataBuffer) |
| ); |
| |
| ASSERT_NE(res, ZBI_RESULT_OK, "zbi appended a section that was too big"); |
| |
| // Now try again with a section that is exactly the right size. Make sure |
| // we don't stomp on the sentinel. |
| res = image.AppendSection( |
| kMaxAppendPayloadSize, |
| ZBI_TYPE_STORAGE_RAMDISK, |
| 0, |
| 0, |
| reinterpret_cast<const void*>(dataBuffer) |
| ); |
| |
| ASSERT_EQ(res, ZBI_RESULT_OK, "zbi_append rejected a section that should " |
| "have fit."); |
| |
| for (size_t i = 0; i < kExtraSentinelLength; i++) { |
| ASSERT_EQ(test_zbi[kZbiSize + i], kSentinelByte, |
| "corrupt sentinel bytes, append section overflowed."); |
| } |
| |
| END_TEST; |
| } |
| |
| // Test that appending multiple sections to a ZBI works |
| static bool zbi_test_append_multi(void) { |
| BEGIN_TEST; |
| uint8_t* reference_zbi = get_test_zbi(); |
| ASSERT_NONNULL(reference_zbi); |
| auto cleanup = fbl::MakeAutoCall([reference_zbi]() { |
| free(reference_zbi); |
| }); |
| |
| |
| alignas(ZBI_ALIGNMENT) uint8_t test_zbi[sizeof(test_zbi_t)]; |
| zbi_header_t* hdr = reinterpret_cast<zbi_header_t*>(test_zbi); |
| |
| // Create an empty container. |
| init_zbi_header(hdr); |
| hdr->type = ZBI_TYPE_CONTAINER; |
| hdr->extra = ZBI_CONTAINER_MAGIC; |
| hdr->length = 0; |
| |
| zbi::Zbi image(test_zbi, sizeof(test_zbi)); |
| |
| ASSERT_EQ(image.Check(nullptr), ZBI_RESULT_OK); |
| |
| zbi_result_t result; |
| |
| result = image.AppendSection(sizeof(kTestCmdline), ZBI_TYPE_CMDLINE, 0, 0, kTestCmdline); |
| ASSERT_EQ(result, ZBI_RESULT_OK); |
| |
| result = image.AppendSection(sizeof(kTestRD), ZBI_TYPE_STORAGE_RAMDISK, 0, 0, kTestRD); |
| ASSERT_EQ(result, ZBI_RESULT_OK); |
| |
| result = image.AppendSection(sizeof(kTestBootfs), ZBI_TYPE_STORAGE_BOOTFS, 0, 0, kTestBootfs); |
| ASSERT_EQ(result, ZBI_RESULT_OK); |
| |
| ASSERT_EQ(memcmp(reference_zbi, test_zbi, image.Length()), 0); |
| |
| END_TEST; |
| } |
| |
| BEGIN_TEST_CASE(zbi_tests) |
| RUN_TEST(zbi_test_basic) |
| RUN_TEST(zbi_test_bad_container) |
| RUN_TEST(zbi_test_truncated) |
| RUN_TEST(zbi_test_append) |
| RUN_TEST(zbi_test_append_full) |
| RUN_TEST(zbi_test_append_multi) |
| END_TEST_CASE(zbi_tests) |
| |
| int main(int argc, char** argv) { |
| return unittest_run_all_tests(argc, argv) ? 0 : -1; |
| } |