| // 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 "src/lib/bootfs/parser.h" |
| |
| #include <lib/zx/vmo.h> |
| #include <zircon/boot/bootfs.h> |
| #include <zircon/errors.h> |
| |
| #include <cstdint> |
| #include <iterator> |
| #include <limits> |
| #include <utility> |
| |
| #include <fbl/algorithm.h> |
| #include <fbl/string.h> |
| #include <fbl/string_piece.h> |
| #include <zxtest/zxtest.h> |
| |
| namespace { |
| |
| constexpr uint64_t kVmoSize = 1024 * 1024; |
| |
| struct BootfsEntry { |
| fbl::String name; |
| fbl::StringPiece data; |
| }; |
| |
| // helper for creating a bootfs to use |
| void CreateBootfs(BootfsEntry* entries, size_t num_entries, zx::vmo* vmo_out) { |
| zx::vmo vmo; |
| ASSERT_OK(zx::vmo::create(kVmoSize, 0, &vmo)); |
| |
| uint32_t offset = static_cast<uint32_t>(sizeof(zbi_bootfs_header_t)); |
| for (size_t i = 0; i < num_entries; ++i) { |
| auto& entry = entries[i]; |
| // Must be page-aligned |
| const uint32_t data_offset = static_cast<uint32_t>(ZBI_BOOTFS_PAGE_SIZE * (i + 1)); |
| |
| zbi_bootfs_dirent_t dirent = { |
| .name_len = static_cast<uint32_t>(entry.name.size() + 1), |
| .data_len = static_cast<uint32_t>(entry.data.size()), |
| .data_off = data_offset, |
| }; |
| |
| // Write header & name |
| ASSERT_OK(vmo.write(&dirent, offset, sizeof(dirent))); |
| ASSERT_OK(vmo.write(entry.name.c_str(), offset + sizeof(dirent), dirent.name_len)); |
| // Entries must be 32-bit aligned |
| offset += ZBI_BOOTFS_DIRENT_SIZE(dirent.name_len); |
| |
| // Write data |
| ASSERT_OK(vmo.write(entry.data.data(), data_offset, entry.data.size())); |
| } |
| |
| zbi_bootfs_header_t header = {}; |
| header.magic = ZBI_BOOTFS_MAGIC; |
| header.dirsize = static_cast<uint32_t>(offset - sizeof(header)); |
| |
| ASSERT_OK(vmo.write(&header, 0, sizeof(header))); |
| *vmo_out = std::move(vmo); |
| } |
| |
| TEST(ParserTestCase, ParseWithoutInit) { |
| bootfs::Parser parser; |
| |
| ASSERT_EQ(parser.Parse([](const zbi_bootfs_dirent_t* entry) { return ZX_OK; }), ZX_ERR_BAD_STATE); |
| } |
| |
| TEST(ParserTestCase, InitTwice) { |
| zx::vmo vmo; |
| ASSERT_NO_FAILURES(CreateBootfs(nullptr, 0, &vmo)); |
| |
| bootfs::Parser parser; |
| ASSERT_OK(parser.Init(zx::unowned_vmo(vmo))); |
| ASSERT_EQ(parser.Init(zx::unowned_vmo(vmo)), ZX_ERR_BAD_STATE); |
| } |
| |
| TEST(ParserTestCase, InitBadMagic) { |
| zx::vmo vmo; |
| ASSERT_OK(zx::vmo::create(kVmoSize, 0, &vmo)); |
| |
| zbi_bootfs_header_t header = {}; |
| header.magic = ZBI_BOOTFS_MAGIC ^ 1; |
| header.dirsize = 0; |
| |
| ASSERT_OK(vmo.write(&header, 0, sizeof(header))); |
| |
| bootfs::Parser parser; |
| ASSERT_EQ(parser.Init(zx::unowned_vmo(vmo)), ZX_ERR_IO); |
| } |
| |
| TEST(ParserTestCase, InitShortHeader) { |
| zx::vmo vmo; |
| ASSERT_OK(zx::vmo::create(0, 0, &vmo)); |
| |
| bootfs::Parser parser; |
| ASSERT_EQ(parser.Init(zx::unowned_vmo(vmo)), ZX_ERR_OUT_OF_RANGE); |
| } |
| |
| TEST(ParserTestCase, InitCantMap) { |
| zx::vmo vmo; |
| ASSERT_NO_FAILURES(CreateBootfs(nullptr, 0, &vmo)); |
| ASSERT_OK(vmo.replace(ZX_RIGHT_READ, &vmo)); |
| |
| bootfs::Parser parser; |
| ASSERT_EQ(parser.Init(zx::unowned_vmo(vmo)), ZX_ERR_ACCESS_DENIED); |
| } |
| |
| TEST(ParserTestCase, ExtraHeaderData) { |
| // This must be built manually rather than with CreateBootfs because it has an invalid format. |
| zx::vmo vmo; |
| ASSERT_OK(zx::vmo::create(2 * ZBI_BOOTFS_PAGE_SIZE, 0, &vmo)); |
| |
| // Set dirsize such that there's data remaining after parsing all full dirents |
| zbi_bootfs_header_t header = {.magic = ZBI_BOOTFS_MAGIC, .dirsize = 1}; |
| ASSERT_OK(vmo.write(&header, 0, sizeof(header))); |
| |
| bootfs::Parser parser; |
| ASSERT_OK(parser.Init(zx::unowned_vmo(vmo))); |
| ASSERT_EQ(parser.Parse([](const zbi_bootfs_dirent_t* entry) { return ZX_OK; }), ZX_ERR_IO); |
| } |
| |
| TEST(ParserTestCase, DirentNameLengthOutOfBounds) { |
| // This must be built manually rather than with CreateBootfs because it has an invalid format. |
| zx::vmo vmo; |
| ASSERT_OK(zx::vmo::create(2 * ZBI_BOOTFS_PAGE_SIZE, 0, &vmo)); |
| |
| // Create a dirent header with a name_len that is out of the dirsize bounds, by not including the |
| // right name_len in dirsize |
| zbi_bootfs_header_t header = {.magic = ZBI_BOOTFS_MAGIC, |
| .dirsize = sizeof(zbi_bootfs_dirent_t) + 1}; |
| zbi_bootfs_dirent_t dirent = {.name_len = 2, .data_len = 10, .data_off = ZBI_BOOTFS_PAGE_SIZE}; |
| |
| ASSERT_OK(vmo.write(&header, 0, sizeof(header))); |
| ASSERT_OK(vmo.write(&dirent, sizeof(header), sizeof(dirent))); |
| |
| bootfs::Parser parser; |
| ASSERT_OK(parser.Init(zx::unowned_vmo(vmo))); |
| ASSERT_EQ(parser.Parse([](const zbi_bootfs_dirent_t* entry) { return ZX_OK; }), ZX_ERR_IO); |
| } |
| |
| TEST(ParserTestCase, DirentDataNotPageAligned) { |
| // This must be built manually rather than with CreateBootfs because it has an invalid format. |
| zx::vmo vmo; |
| ASSERT_OK(zx::vmo::create(2 * ZBI_BOOTFS_PAGE_SIZE, 0, &vmo)); |
| |
| // Create a dirent header that tries to put its data right after the header. |
| const char* dirent_name = "foo"; |
| uint32_t name_len = static_cast<uint32_t>(strlen(dirent_name)) + 1; |
| uint32_t dirsize = ZBI_BOOTFS_DIRENT_SIZE(name_len); |
| zbi_bootfs_header_t header = {.magic = ZBI_BOOTFS_MAGIC, .dirsize = dirsize}; |
| // Note .data_off is not page aligned here |
| zbi_bootfs_dirent_t dirent = {.name_len = name_len, .data_len = 10, .data_off = dirsize}; |
| |
| ASSERT_OK(vmo.write(&header, 0, sizeof(header))); |
| ASSERT_OK(vmo.write(&dirent, sizeof(header), sizeof(dirent))); |
| ASSERT_OK(vmo.write(dirent_name, sizeof(header) + sizeof(dirent), name_len)); |
| |
| bootfs::Parser parser; |
| ASSERT_OK(parser.Init(zx::unowned_vmo(vmo))); |
| ASSERT_EQ(parser.Parse([](const zbi_bootfs_dirent_t* entry) { return ZX_OK; }), ZX_ERR_IO); |
| } |
| |
| TEST(ParserTestCase, DirentDataOutOfBounds) { |
| // This must be built manually rather than with CreateBootfs because it has an invalid format. |
| zx::vmo vmo; |
| ASSERT_OK(zx::vmo::create(2 * ZBI_BOOTFS_PAGE_SIZE, 0, &vmo)); |
| |
| // Create a dirent header whose data length extends beyond the end of the VMO |
| const char* dirent_name = "foo"; |
| uint32_t name_len = static_cast<uint32_t>(strlen(dirent_name)) + 1; |
| uint32_t dirsize = ZBI_BOOTFS_DIRENT_SIZE(name_len); |
| zbi_bootfs_header_t header = {.magic = ZBI_BOOTFS_MAGIC, .dirsize = dirsize}; |
| // Note .data_len extends past the VMO size of 2*page_size |
| zbi_bootfs_dirent_t dirent = { |
| .name_len = name_len, .data_len = ZBI_BOOTFS_PAGE_SIZE + 1, .data_off = ZBI_BOOTFS_PAGE_SIZE}; |
| |
| ASSERT_OK(vmo.write(&header, 0, sizeof(header))); |
| ASSERT_OK(vmo.write(&dirent, sizeof(header), sizeof(dirent))); |
| ASSERT_OK(vmo.write(dirent_name, sizeof(header) + sizeof(dirent), name_len)); |
| |
| bootfs::Parser parser; |
| ASSERT_OK(parser.Init(zx::unowned_vmo(vmo))); |
| ASSERT_EQ(parser.Parse([](const zbi_bootfs_dirent_t* entry) { return ZX_OK; }), ZX_ERR_IO); |
| } |
| |
| TEST(ParserTestCase, DirentDataFieldsOverflowProtected) { |
| // This must be built manually rather than with CreateBootfs because it has an invalid format. |
| zx::vmo vmo; |
| ASSERT_OK(zx::vmo::create(2 * ZBI_BOOTFS_PAGE_SIZE, 0, &vmo)); |
| |
| // Create a dirent header whose data length extends beyond the end of the VMO |
| const char* dirent_name = "foo"; |
| uint32_t name_len = static_cast<uint32_t>(strlen(dirent_name)) + 1; |
| uint32_t dirsize = ZBI_BOOTFS_DIRENT_SIZE(name_len); |
| zbi_bootfs_header_t header = {.magic = ZBI_BOOTFS_MAGIC, .dirsize = dirsize}; |
| // Note .data_off + .data_len overflows uint32_t |
| zbi_bootfs_dirent_t dirent = {.name_len = name_len, |
| .data_len = std::numeric_limits<uint32_t>::max(), |
| .data_off = ZBI_BOOTFS_PAGE_SIZE}; |
| |
| ASSERT_OK(vmo.write(&header, 0, sizeof(header))); |
| ASSERT_OK(vmo.write(&dirent, sizeof(header), sizeof(dirent))); |
| ASSERT_OK(vmo.write(dirent_name, sizeof(header) + sizeof(dirent), name_len)); |
| |
| bootfs::Parser parser; |
| ASSERT_OK(parser.Init(zx::unowned_vmo(vmo))); |
| ASSERT_EQ(parser.Parse([](const zbi_bootfs_dirent_t* entry) { return ZX_OK; }), ZX_ERR_IO); |
| } |
| |
| TEST(ParserTestCase, DirentNameNotNulTerminated) { |
| // This must be built manually rather than with CreateBootfs because it has an invalid format. |
| zx::vmo vmo; |
| ASSERT_OK(zx::vmo::create(2 * ZBI_BOOTFS_PAGE_SIZE, 0, &vmo)); |
| |
| // Create a dirent header with a non-nul terminated name. |
| const char* dirent_name = "foo"; |
| // Note name_len does not include nul terminator. |
| uint32_t name_len = static_cast<uint32_t>(strlen(dirent_name)); |
| uint32_t dirsize = ZBI_BOOTFS_DIRENT_SIZE(name_len); |
| zbi_bootfs_header_t header = {.magic = ZBI_BOOTFS_MAGIC, .dirsize = dirsize}; |
| zbi_bootfs_dirent_t dirent = { |
| .name_len = name_len, .data_len = 10, .data_off = ZBI_BOOTFS_PAGE_SIZE}; |
| |
| ASSERT_OK(vmo.write(&header, 0, sizeof(header))); |
| ASSERT_OK(vmo.write(&dirent, sizeof(header), sizeof(dirent))); |
| ASSERT_OK(vmo.write(dirent_name, sizeof(header) + sizeof(dirent), name_len)); |
| |
| bootfs::Parser parser; |
| ASSERT_OK(parser.Init(zx::unowned_vmo(vmo))); |
| ASSERT_EQ(parser.Parse([](const zbi_bootfs_dirent_t* entry) { return ZX_OK; }), |
| ZX_ERR_INVALID_ARGS); |
| } |
| |
| TEST(ParserTestCase, PathSeparatorAtStartOfDirentName) { |
| BootfsEntry entries[] = { |
| { |
| .name = "/foo", |
| .data = "lorem ipsum", |
| }, |
| }; |
| zx::vmo vmo; |
| ASSERT_NO_FAILURES(CreateBootfs(entries, std::size(entries), &vmo)); |
| |
| bootfs::Parser parser; |
| ASSERT_OK(parser.Init(zx::unowned_vmo(vmo))); |
| ASSERT_EQ(parser.Parse([](const zbi_bootfs_dirent_t* entry) { return ZX_OK; }), |
| ZX_ERR_INVALID_ARGS); |
| } |
| |
| TEST(ParserTestCase, ParseSuccess) { |
| BootfsEntry entries[] = { |
| { |
| .name = "file 3", |
| .data = "lorem ipsum", |
| }, |
| { |
| .name = "File 1", |
| .data = "", |
| }, |
| { |
| .name = "file2", |
| .data = "0123456789", |
| }, |
| }; |
| zx::vmo vmo; |
| ASSERT_NO_FAILURES(CreateBootfs(entries, std::size(entries), &vmo)); |
| |
| bootfs::Parser parser; |
| ASSERT_OK(parser.Init(zx::unowned_vmo(vmo))); |
| |
| const zbi_bootfs_dirent_t* parsed_entries[3]; |
| size_t seen = 0; |
| EXPECT_OK(parser.Parse([&entries, &parsed_entries, &seen](const zbi_bootfs_dirent_t* entry) { |
| if (seen >= std::size(entries)) { |
| return ZX_ERR_BAD_STATE; |
| } |
| parsed_entries[seen] = entry; |
| ++seen; |
| return ZX_OK; |
| })); |
| ASSERT_EQ(seen, std::size(entries)); |
| |
| for (size_t i = 0; i < seen; ++i) { |
| const auto& real_entry = entries[i]; |
| const auto& parsed_entry = parsed_entries[i]; |
| ASSERT_EQ(parsed_entry->name_len, real_entry.name.size() + 1); |
| ASSERT_EQ(parsed_entry->data_len, real_entry.data.size()); |
| ASSERT_BYTES_EQ(reinterpret_cast<const uint8_t*>(parsed_entry->name), |
| reinterpret_cast<const uint8_t*>(real_entry.name.c_str()), |
| parsed_entry->name_len); |
| |
| uint8_t buffer[parsed_entry->data_len]; |
| ASSERT_OK(vmo.read(buffer, parsed_entry->data_off, sizeof(buffer))); |
| ASSERT_BYTES_EQ(static_cast<const uint8_t*>(buffer), |
| reinterpret_cast<const uint8_t*>(real_entry.data.data()), sizeof(buffer)); |
| } |
| } |
| |
| } // namespace |