blob: cea29048bbc10180b3d8940c8910dc28f4f54b02 [file] [log] [blame]
// Copyright 2020 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 <lib/async-loop/cpp/loop.h>
#include <lib/async-loop/default.h>
#include <cinttypes>
#include <vector>
#include <fuzzer/FuzzedDataProvider.h>
#include "src/storage/lib/block_client/cpp/fake_block_device.h"
#include "src/storage/minfs/runner.h"
namespace minfs {
namespace {
constexpr uint64_t kBlockCount = (1u << 17);
constexpr size_t kMaxOpenFiles = 10u;
constexpr size_t kMaxReadWriteBytes = (1u << 16);
constexpr uint32_t kExpectedAllocBlockCount = 2u;
constexpr uint32_t kExpectedAllocInodeCount = 2u;
using ::block_client::FakeBlockDevice;
enum class Operation {
kFinished, // Must be first to ensure each case finishes.
kCreate,
kRead,
kWrite,
kMaxValue = kWrite
};
} // namespace
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread);
std::unique_ptr<Runner> runner;
{
std::unique_ptr device = std::make_unique<FakeBlockDevice>(kBlockCount, kMinfsBlockSize);
zx::result bcache_or = Bcache::Create(std::move(device), kBlockCount);
ZX_ASSERT_MSG(bcache_or.is_ok(), "Failed to create Bcache: %s", bcache_or.status_string());
zx::result mkfs_result = Mkfs(bcache_or.value().get());
ZX_ASSERT_MSG(mkfs_result.is_ok(), "Mkfs failure: %s", mkfs_result.status_string());
MountOptions options = {};
zx::result fs_or = Runner::Create(loop.dispatcher(), std::move(bcache_or.value()), options);
ZX_ASSERT_MSG(fs_or.is_ok(), "Failed to create Runner: %s", fs_or.status_string());
runner = std::move(fs_or.value());
}
ZX_ASSERT(runner != nullptr);
Minfs& fs = runner->minfs();
fbl::RefPtr<fs::Vnode> root_node;
{
zx::result root = fs.VnodeGet(kMinfsRootIno);
ZX_ASSERT_MSG(root.is_ok(), "Failed to get root node: %s", root.status_string());
root_node = std::move(root.value());
}
std::vector<fbl::RefPtr<fs::Vnode>> open_files;
open_files.reserve(kMaxOpenFiles);
std::vector<std::string> created_files;
std::vector<uint8_t> buffer(kMaxReadWriteBytes);
FuzzedDataProvider fuzzed_data(data, size);
for (;;) {
auto operation = fuzzed_data.ConsumeEnum<Operation>();
switch (operation) {
case Operation::kFinished: {
for (auto& file : open_files) {
zx_status_t status = file->Close();
ZX_ASSERT_MSG(status == ZX_OK, "Failed to close file: %s", zx_status_get_string(status));
}
// Make sure we drop the file node refcounts before destroying Minfs.
open_files = {};
for (const auto& name : created_files) {
zx_status_t status = root_node->Unlink(name, /*must_be_dir=*/false);
ZX_ASSERT_MSG(status == ZX_OK, "Failed to unlink file: %s", zx_status_get_string(status));
}
// Make sure we don't hold onto the root Vnode after we destroy Minfs.
root_node = nullptr;
// Fsck should always pass regardless of if we flushed any outstanding transactions or not.
if (fuzzed_data.ConsumeBool()) {
zx::result status = fs.BlockingJournalSync();
ZX_ASSERT_MSG(status.is_ok(), "Failed to sync: %s", status.status_string());
}
loop.RunUntilIdle();
// Validate the final allocated block/inode counts in the superblock.
ZX_ASSERT_MSG(fs.Info().alloc_block_count == kExpectedAllocBlockCount,
"Incorrect allocated block count - actual: %" PRIu32 ", expected: %" PRIu32,
fs.Info().alloc_block_count, kExpectedAllocBlockCount);
ZX_ASSERT_MSG(fs.Info().alloc_inode_count == kExpectedAllocInodeCount,
"Incorrect allocated block count - actual: %" PRIu32 ", expected: %" PRIu32,
fs.Info().alloc_inode_count, kExpectedAllocInodeCount);
// Destroy Minfs and run Fsck.
std::unique_ptr<Bcache> bcache = Runner::Destroy(std::move(runner));
auto fsck_result = Fsck(std::move(bcache), FsckOptions{.read_only = true, .quiet = true});
ZX_ASSERT_MSG(fsck_result.is_ok(), "Fsck failure: %s", fsck_result.status_string());
return 0;
}
case Operation::kCreate: {
ZX_ASSERT(open_files.size() <= kMaxOpenFiles);
// If we have |kMaxOpenFiles| files still open, close and remove one at random.
if (open_files.size() == kMaxOpenFiles) {
ssize_t file_index = fuzzed_data.ConsumeIntegralInRange<ssize_t>(0, kMaxOpenFiles - 1);
zx_status_t status = open_files[file_index]->Close();
ZX_ASSERT_MSG(status == ZX_OK, "Failed to close file: %s", zx_status_get_string(status));
open_files.erase(open_files.begin() + file_index);
}
// Try to create a file with a randomized path and node type.
std::string name = fuzzed_data.ConsumeRandomLengthString(NAME_MAX + 2);
fs::CreationType type = fuzzed_data.ConsumeEnum<fs::CreationType>();
zx::result vnode = root_node->Create(name, type);
// It's okay if we failed to create the file, since name or node type may be invalid.
if (vnode.is_ok()) {
created_files.push_back(std::move(name));
open_files.push_back(std::move(*vnode));
}
break;
}
case Operation::kRead: {
if (open_files.empty())
break;
size_t file_index = fuzzed_data.ConsumeIntegralInRange<size_t>(0, open_files.size() - 1);
size_t read_len = fuzzed_data.ConsumeIntegralInRange<size_t>(0, buffer.size());
size_t offset = fuzzed_data.ConsumeIntegral<size_t>();
open_files[file_index]->Read(buffer.data(), read_len, offset, &read_len);
break;
}
case Operation::kWrite: {
if (open_files.empty())
break;
size_t file_index = fuzzed_data.ConsumeIntegralInRange<size_t>(0, open_files.size() - 1);
size_t write_len = fuzzed_data.ConsumeIntegralInRange<size_t>(0, buffer.size());
size_t offset = fuzzed_data.ConsumeIntegral<size_t>();
open_files[file_index]->Write(buffer.data(), write_len, offset, &write_len);
break;
}
}
} // for (;;)
}
} // namespace minfs