blob: baff0569952b9f34e05d261248b22170923aa018 [file] [log] [blame]
// 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 <string_view>
#include <utility>
#include <fbl/algorithm.h>
#include <fbl/string.h>
#include <zxtest/zxtest.h>
namespace {
constexpr uint64_t kVmoSize = 1024 * 1024;
struct BootfsEntry {
fbl::String name;
std::string_view 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