glove/fuzz/library_bundle_fuzzer.cpp

149 lines
4.3 KiB
C++

#include "glove/container/digest.hpp"
#include "glove/supervisor/library_bundle.hpp"
#include <fcntl.h>
#include <unistd.h>
#include <algorithm>
#include <array>
#include <cerrno>
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <span>
#include <string>
#include <utility>
namespace {
constexpr std::size_t maximum_bundle_bytes = 1024U * 1024U;
class unique_fd {
public:
explicit unique_fd(int descriptor) noexcept : descriptor_{descriptor} {}
unique_fd(const unique_fd&) = delete;
auto operator=(const unique_fd&) -> unique_fd& = delete;
~unique_fd() {
if (descriptor_ >= 0) {
(void)::close(descriptor_);
}
}
[[nodiscard]] auto get() const noexcept -> int { return descriptor_; }
private:
int descriptor_ = -1;
};
class temporary_bundle_root {
public:
temporary_bundle_root() {
constexpr char directory_template[] = "/tmp/glove-library-bundle-fuzzer-XXXXXX";
std::array<char, sizeof(directory_template)> directory_buffer{};
std::copy_n(directory_template, directory_buffer.size(), directory_buffer.begin());
const char* const created_directory = ::mkdtemp(directory_buffer.data());
if (created_directory == nullptr) {
std::abort();
}
root_ = created_directory;
}
temporary_bundle_root(const temporary_bundle_root&) = delete;
auto operator=(const temporary_bundle_root&) -> temporary_bundle_root& = delete;
~temporary_bundle_root() {
if (!current_path_.empty()) {
(void)::unlink(current_path_.c_str());
}
if (!root_.empty()) {
(void)::rmdir(root_.c_str());
}
}
[[nodiscard]] auto
overwrite(std::string_view digest, const std::uint8_t* data, std::size_t size) -> bool {
if (!current_path_.empty() && ::unlink(current_path_.c_str()) != 0) {
return false;
}
current_path_ = root_ + "/" + std::string{digest} + ".json";
const unique_fd descriptor{::open(
current_path_.c_str(), O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC | O_NOFOLLOW, 0600
)};
if (descriptor.get() < 0) {
return false;
}
std::size_t written = 0;
while (written < size) {
const auto result = ::write(descriptor.get(), data + written, size - written);
if (result < 0 && errno == EINTR) {
continue;
}
if (result <= 0) {
return false;
}
written += static_cast<std::size_t>(result);
}
return true;
}
[[nodiscard]] auto path() const -> const std::string& { return root_; }
private:
std::string root_;
std::string current_path_;
};
auto bundle_root() -> temporary_bundle_root& {
static temporary_bundle_root root;
return root;
}
auto bundle_store() -> glove::supervisor::library_bundle_store& {
static glove::supervisor::library_bundle_store store = [] {
auto opened = glove::supervisor::library_bundle_store::open(bundle_root().path());
if (!opened) {
std::abort();
}
return std::move(*opened);
}();
return store;
}
auto digest_for(const std::uint8_t* data, std::size_t size) -> std::string {
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
const std::span bytes{reinterpret_cast<const unsigned char*>(data), size};
auto digest = glove::container::sha256_hex(bytes);
if (!digest) {
std::abort();
}
return std::move(*digest);
}
} // namespace
extern "C" auto LLVMFuzzerTestOneInput(const std::uint8_t* data, std::size_t size) -> int {
if (size > maximum_bundle_bytes) {
return 0;
}
const std::string digest = digest_for(data, size);
auto& root = bundle_root();
if (!root.overwrite(digest, data, size)) {
std::abort();
}
// Bundles remain opaque until prompt-library expansion exists. Exercise
// the production descriptor, metadata, digest, and identity checks on
// arbitrary staged content rather than inventing an unpublished format.
auto resolved = bundle_store().resolve(digest);
if (size == 0U) {
if (resolved) {
std::abort();
}
} else if (!resolved || !resolved->verify_identity()) {
std::abort();
}
return 0;
}