Skip to content

Commit

Permalink
[sw] Add a fuzzer for the common bootstrap library
Browse files Browse the repository at this point in the history
+ Adds @rules_fuzzing dependency to Bazel
+ Adds command-line flag --config=asan-libfuzzer to .bazelrc
+ Adds a fuzzer for lib:bootstrap that throws SPI commands at the event
  loop.

To run the fuzzer:

    ./bazelisk.sh run --config=asan-libfuzzer \
      //sw/device/silicon_creator/lib:bootstrap_fuzz_test_run

Signed-off-by: Dan McArdle <dmcardle@opentitan.org>
  • Loading branch information
dmcardle committed Jul 17, 2023
1 parent 296ff41 commit 2a40313
Show file tree
Hide file tree
Showing 8 changed files with 214 additions and 0 deletions.
8 changes: 8 additions & 0 deletions .bazelrc
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@ build --workspace_status_command=util/get_workspace_status.sh
# --config=riscv32
build:riscv32 --platforms=@crt//platforms/riscv32:opentitan

# These options are required to build `cc_fuzz_test` targets. Enable with
# --config=asan-libfuzzer
build:asan-libfuzzer --action_env=CC=clang
build:asan-libfuzzer --action_env=CXX=clang++
build:asan-libfuzzer --@rules_fuzzing//fuzzing:cc_engine=@rules_fuzzing//fuzzing/engines:libfuzzer
build:asan-libfuzzer --@rules_fuzzing//fuzzing:cc_engine_instrumentation=libfuzzer
build:asan-libfuzzer --@rules_fuzzing//fuzzing:cc_engine_sanitizer=asan

# Shared configuration for clang's source-based coverage instrumentation.
# Bazel seems to support this only partially, thus we have to perform some
# additional processing. See
Expand Down
20 changes: 20 additions & 0 deletions sw/device/silicon_creator/lib/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ load("//rules:opentitan.bzl", "OPENTITAN_CPU")
load("//rules:autogen.bzl", "autogen_chip_info")
load("//rules:opentitan_test.bzl", "cw310_params", "opentitan_functest", "verilator_params")
load("//rules:cross_platform.bzl", "dual_cc_device_library_of", "dual_cc_library", "dual_inputs")
load("@rules_fuzzing//fuzzing:cc_defs.bzl", "cc_fuzz_test")

package(default_visibility = ["//visibility:public"])

Expand Down Expand Up @@ -437,3 +438,22 @@ cc_library(
"//sw/device/silicon_creator/lib/base:chip",
],
)

# To test this target, you must specify `--config=asan-libfuzzer`.
cc_fuzz_test(
name = "bootstrap_fuzz_test",
srcs = ["bootstrap_fuzz_test.cc"],
tags = [
"fuzzer",
"manual",
],
deps = [
":bootstrap_test_util",
"//sw/device/lib/base:hardened",
"//sw/device/lib/base:macros",
"//sw/device/silicon_creator/lib/drivers:flash_ctrl",
"//sw/device/silicon_creator/lib/drivers:spi_device",
"@com_google_absl//absl/types:optional",
"@com_google_absl//absl/types:span",
],
)
171 changes: 171 additions & 0 deletions sw/device/silicon_creator/lib/bootstrap_fuzz_test.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0

#include <stddef.h>
#include <stdint.h>
#include <type_traits>

#include "absl/types/optional.h"
#include "absl/types/span.h"
#include "gmock/gmock.h"
#include "sw/device/lib/base/hardened.h"
#include "sw/device/lib/base/macros.h"
#include "sw/device/silicon_creator/lib/bootstrap.h"
#include "sw/device/silicon_creator/lib/bootstrap_test_util.h"
#include "sw/device/silicon_creator/lib/drivers/spi_device.h"

namespace {

// When `true`, the fuzzer will print the inputs it generates as human-readable
// strings. This is useful when debugging a crash, but is unnecessary overhead
// during exploratory fuzzing.
constexpr bool kVerbose = false;

void Crash() { __builtin_trap(); }

bool IsRomExtAddress(uint32_t addr) {
if (addr < CHIP_ROM_EXT_SIZE_MAX) {
return true;
}
if (addr >= FLASH_CTRL_PARAM_BYTES_PER_BANK &&
addr < FLASH_CTRL_PARAM_BYTES_PER_BANK + CHIP_ROM_EXT_SIZE_MAX) {
return true;
}
return false;
}

class StreamParser {
public:
// For speed, the constructor does not make a copy of `data`. Ensure that
// `data` outlives `this`.
StreamParser(absl::Span<const uint8_t> data) : data_(data) {}

absl::optional<spi_device_cmd_t> ParseCmd() {
auto opcode = ParseInt<uint8_t>();
auto address = ParseInt<uint32_t>();
auto payload_byte_count = ParseInt<size_t>();
if (!opcode || !address || !payload_byte_count) {
return absl::nullopt;
}
return spi_device_cmd_t{
.opcode = static_cast<spi_device_opcode_t>(*opcode & UINT8_MAX),
.address = *address,
.payload_byte_count =
*payload_byte_count % (kSpiDevicePayloadAreaNumBytes + 1),
};
}

template <typename IntType>
absl::optional<IntType> ParseInt() {
static_assert(
std::is_integral<IntType>::value || std::is_enum<IntType>::value,
"IntType must be an integral or enum type");
if (data_.length() < sizeof(IntType)) {
return absl::nullopt;
}
IntType n;
memcpy(&n, data_.data(), sizeof(IntType));
data_ = data_.subspan(sizeof(IntType));
return n;
}

private:
absl::Span<const uint8_t> data_;
};

class SpiFlashFuzzContext {
public:
void Fuzz(StreamParser stream);

::rom_test::NiceMockSpiDevice spi_device_;
::rom_test::NiceMockRstmgr rstmgr_;
::rom_test::NiceMockFlashCtrl flash_ctrl_;
::rom_test::NiceMockOtp otp_;
};

void SpiFlashFuzzContext::Fuzz(StreamParser stream) {
using bootstrap_test_util::ChipEraseCmd;
using bootstrap_test_util::ResetCmd;
using ::testing::_;

const bool enable_bounds_check =
stream.ParseInt<uint8_t>().value_or(false) % 2;
if (kVerbose) {
printf("enable_bounds_check: %s\n", enable_bounds_check ? "true" : "false");
}

ON_CALL(rstmgr_, ReasonGet())
.WillByDefault(::testing::Return(1 << kRstmgrReasonPowerOn |
1 << kRstmgrReasonSoftwareRequest));

ON_CALL(spi_device_, FlashStatusGet())
.WillByDefault(testing::Return(1 << kSpiDeviceWelBit));

bool first_spi_cmd = true;
ON_CALL(spi_device_, CmdGet(testing::NotNull()))
.WillByDefault([&](spi_device_cmd_t *cmd) -> rom_error_t {
// The first time that bootstrap reads a command, it will get
// ChipEraseCmd(). Subsequent commands will be parsed on-demand from a
// byte buffer generated by the fuzzer engine.
if (first_spi_cmd) {
first_spi_cmd = false;
*cmd = ChipEraseCmd();
return kErrorOk;
}
// When we've run out of fuzzer-generated bytes to parse, inject a
// synthetic reset command.
*cmd = stream.ParseCmd().value_or(ResetCmd());
if (kVerbose) {
printf(
"spi_device_cmd_t: {opcode=0x%x, address=0x%x, "
"payload_byte_count=%zu}\n",
cmd->opcode, cmd->address, cmd->payload_byte_count);
}
return kErrorOk;
});

ON_CALL(flash_ctrl_, DataErase(_, _))
.WillByDefault([&](uint32_t addr, flash_ctrl_erase_type_t type) {
if (enable_bounds_check && IsRomExtAddress(addr)) {
Crash();
}
return kErrorOk;
});

ON_CALL(flash_ctrl_, DataWrite(_, _, _))
.WillByDefault([&](uint32_t addr, uint32_t word_count, const void *data) {
if (enable_bounds_check && IsRomExtAddress(addr)) {
Crash();
}
return kErrorOk;
});

ON_CALL(flash_ctrl_, DataEraseVerify(_, _))
.WillByDefault(::testing::Return(kErrorOk));

(void)enter_bootstrap(/*enable_bounds_check=*/enable_bounds_check
? kHardenedBoolTrue
: kHardenedBoolFalse);
}
} // namespace

// This function disallows any operations on ROM_EXT memory. It implements the
// prototype declared by bootstrap.h.
hardened_bool_t bootstrap_bounds_check(spi_device_opcode_t opcode,
uint32_t page_addr) {
return IsRomExtAddress(page_addr) ? kHardenedBoolFalse : kHardenedBoolTrue;
}

extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
// Only initialize the environment once.
static struct FuzzerEnvironment {
FuzzerEnvironment() { testing::InitGoogleMock(); }
} env;

SpiFlashFuzzContext fuzz_context;
StreamParser stream(absl::MakeSpan(data, size));
fuzz_context.Fuzz(stream);

return 0; // Values other than 0 and -1 are reserved for future use.
}
1 change: 1 addition & 0 deletions sw/device/silicon_creator/lib/drivers/mock_flash_ctrl.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ class MockFlashCtrl : public global_mock::GlobalMock<MockFlashCtrl> {
} // namespace internal

using MockFlashCtrl = testing::StrictMock<internal::MockFlashCtrl>;
using NiceMockFlashCtrl = testing::NiceMock<internal::MockFlashCtrl>;

} // namespace rom_test

Expand Down
1 change: 1 addition & 0 deletions sw/device/silicon_creator/lib/drivers/mock_rstmgr.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ class MockRstmgr : public global_mock::GlobalMock<MockRstmgr> {
} // namespace internal

using MockRstmgr = testing::StrictMock<internal::MockRstmgr>;
using NiceMockRstmgr = testing::NiceMock<internal::MockRstmgr>;

} // namespace rom_test

Expand Down
1 change: 1 addition & 0 deletions sw/device/silicon_creator/lib/drivers/mock_spi_device.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ class MockSpiDevice : public global_mock::GlobalMock<MockSpiDevice> {
} // namespace internal

using MockSpiDevice = testing::StrictMock<internal::MockSpiDevice>;
using NiceMockSpiDevice = testing::NiceMock<internal::MockSpiDevice>;

} // namespace rom_test

Expand Down
5 changes: 5 additions & 0 deletions third_party/google/deps.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,15 @@

load("@rules_pkg//:deps.bzl", "rules_pkg_dependencies")
load("@rules_foreign_cc//foreign_cc:repositories.bzl", "rules_foreign_cc_dependencies")
load("@rules_fuzzing//fuzzing:repositories.bzl", "rules_fuzzing_dependencies")
load("@rules_fuzzing//fuzzing:init.bzl", "rules_fuzzing_init")

def google_deps():
rules_pkg_dependencies()

# Finish setting up rules_foreign_cc, per instructions:
# https://bazelbuild.github.io/rules_foreign_cc/0.9.0/index.html
rules_foreign_cc_dependencies()

rules_fuzzing_dependencies()
rules_fuzzing_init()
7 changes: 7 additions & 0 deletions third_party/google/repos.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,10 @@ def google_repos(
sha256 = "2a4d07cd64b0719b39a7c12218a3e507672b82a97b98c6a89d38565894cf7c51",
url = "https://github.com/bazelbuild/rules_foreign_cc/archive/refs/tags/0.9.0.tar.gz",
)

http_archive_or_local(
name = "rules_fuzzing",
sha256 = "f85dc70bb9672af0e350686461fe6fdd0d61e10e75645f9e44fedf549b21e369",
strip_prefix = "rules_fuzzing-0.3.2",
urls = ["https://github.com/bazelbuild/rules_fuzzing/archive/v0.3.2.tar.gz"],
)

0 comments on commit 2a40313

Please sign in to comment.