Skip to content

Commit

Permalink
[sw] Refactor and add fuzzer for ROM bootstrap
Browse files Browse the repository at this point in the history
To run the fuzzer:

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

Signed-off-by: Dan McArdle <dmcardle@opentitan.org>
  • Loading branch information
dmcardle committed Aug 2, 2023
1 parent 6e168d9 commit 006fc28
Show file tree
Hide file tree
Showing 8 changed files with 363 additions and 175 deletions.
1 change: 1 addition & 0 deletions sw/device/lib/base/mock_abs_mmio.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ class MockAbsMmio : public global_mock::GlobalMock<MockAbsMmio> {
} // namespace internal

using MockAbsMmio = testing::StrictMock<internal::MockAbsMmio>;
using NiceMockAbsMmio = testing::NiceMock<internal::MockAbsMmio>;

/**
* Expect an abs_mmio read at the given address, returning the given 8-bit
Expand Down
17 changes: 17 additions & 0 deletions sw/device/silicon_creator/lib/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,23 @@ filegroup(
],
)

cc_library(
name = "bootstrap_fuzzer_util",
srcs = ["bootstrap_fuzzer_util.cc"],
hdrs = ["bootstrap_fuzzer_util.h"],
deps = [
"//hw/top_earlgrey/ip/flash_ctrl/data/autogen:flash_ctrl_regs",
"//hw/top_earlgrey/sw/autogen:top_earlgrey",
"//sw/device/lib/base:hardened",
"//sw/device/silicon_creator/lib:bootstrap_unittest_util",
"//sw/device/silicon_creator/lib/drivers:flash_ctrl",
"//sw/device/silicon_creator/lib/drivers:otp",
"//sw/device/silicon_creator/lib/drivers:spi_device",
"@com_google_absl//absl/types:optional",
"@com_google_absl//absl/types:span",
],
)

cc_library(
name = "bootstrap_unittest_util",
srcs = ["bootstrap_unittest_util.cc"],
Expand Down
101 changes: 101 additions & 0 deletions sw/device/silicon_creator/lib/bootstrap_fuzzer_util.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
#include "sw/device/silicon_creator/lib/bootstrap_fuzzer_util.h"

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

#include "absl/types/optional.h"
#include "absl/types/span.h"
#include "gmock/gmock.h"
#include "sw/device/lib/base/hardened.h"
#include "sw/device/silicon_creator/lib/base/chip.h"
#include "sw/device/silicon_creator/lib/bootstrap_unittest_util.h"
#include "sw/device/silicon_creator/lib/drivers/mock_flash_ctrl.h"
#include "sw/device/silicon_creator/lib/drivers/mock_otp.h"
#include "sw/device/silicon_creator/lib/drivers/mock_rstmgr.h"
#include "sw/device/silicon_creator/lib/drivers/mock_spi_device.h"

#include "flash_ctrl_regs.h"
#include "hw/ip/otp_ctrl/data/otp_ctrl_regs.h"
#include "hw/top_earlgrey/sw/autogen/top_earlgrey.h"

namespace bootstrap_fuzzer {

namespace {
void Crash() { __builtin_trap(); }
} // namespace

StaticFuzzerEnvironment::StaticFuzzerEnvironment() {
testing::InitGoogleMock();
}

AbstractBootstrapMockGroup::AbstractBootstrapMockGroup(StreamParser stream,
bool verbose)
: verbose_(verbose), stream_(std::move(stream)) {}

void AbstractBootstrapMockGroup::ConfigureMocks() {
using ::testing::_;
using ::testing::Return;

ON_CALL(flash_ctrl_, DataErase(_, _)).WillByDefault(Return(kErrorOk));
ON_CALL(flash_ctrl_, DataWrite(_, _, _)).WillByDefault(Return(kErrorOk));
ON_CALL(flash_ctrl_, DataEraseVerify(_, _)).WillByDefault(Return(kErrorOk));

ON_CALL(rstmgr_, ReasonGet()).WillByDefault([&] {
return stream_.ParseIntOr<uint32_t>(
"reset_reason",
1 << kRstmgrReasonPowerOn | 1 << kRstmgrReasonSoftwareRequest);
});

// It's possible that the bootstrap code will get stuck in a loop asking for
// new SPI commands. The bootstrap loop will ignore RESET commands unless it
// has first seen an erase command. It will also ignore RESET commands unless
// the flash status's WEL bit is enabled.

max_spi_cmd_count_ =
(stream_.ParseIntOr<size_t>("max_spi_cmd_count_", 1024) % 1024) + 16;
if (verbose_) {
std::cout << "Clamped max_spi_cmd_count_: " << max_spi_cmd_count_
<< std::endl;
}

ON_CALL(spi_device_, FlashStatusGet()).WillByDefault([&] {
return flash_status_override_
? uint32_t{1 << kSpiDeviceWelBit}
: stream_.ParseIntOr<uint32_t>("flash_status", 0);
});

ON_CALL(spi_device_, CmdGet(testing::NotNull()))
.WillByDefault([&](spi_device_cmd_t *cmd) -> rom_error_t {
spi_cmd_count_++;

if (spi_cmd_count_ < max_spi_cmd_count_) {
*cmd = stream_.ParseCmdOr(bootstrap_unittest_util::ChipEraseCmd());
return kErrorOk;
}
if (spi_cmd_count_ == max_spi_cmd_count_) {
*cmd = bootstrap_unittest_util::ChipEraseCmd();
flash_status_override_ = true;
if (verbose_) {
std::cout << "Attempting to end session: CHIP_ERASE" << std::endl;
}
return kErrorOk;
}
if (spi_cmd_count_ == max_spi_cmd_count_ + 1) {
*cmd = bootstrap_unittest_util::ResetCmd();
if (verbose_) {
std::cout << "Attempting to end session: RESET" << std::endl;
}
return kErrorOk;
}

// We've already synthesized CHIP_ERASE and RESET commands, so the
// bootstrap code should not have requested another SPI command.
Crash();
return kErrorUnknown;
});
}

} // namespace bootstrap_fuzzer
153 changes: 153 additions & 0 deletions sw/device/silicon_creator/lib/bootstrap_fuzzer_util.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0

#ifndef OPENTITAN_SW_DEVICE_SILICON_CREATOR_LIB_BOOTSTRAP_FUZZER_UTIL_H_
#define OPENTITAN_SW_DEVICE_SILICON_CREATOR_LIB_BOOTSTRAP_FUZZER_UTIL_H_

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

#include "absl/types/optional.h"
#include "absl/types/span.h"
#include "sw/device/silicon_creator/lib/drivers/mock_flash_ctrl.h"
#include "sw/device/silicon_creator/lib/drivers/mock_otp.h"
#include "sw/device/silicon_creator/lib/drivers/mock_rstmgr.h"
#include "sw/device/silicon_creator/lib/drivers/mock_spi_device.h"

namespace bootstrap_fuzzer {

/**
* Consumes values on demand from a non-owning span.
*/
class StreamParser {
public:
/**
* For speed, the constructor does not make a copy of `data`. Ensure that
* `data` points to memory that outlives `this`.
*
* @param data Non-owning view into fuzzer-generated data.
* @param verbose Whether verbose logging is enabled.
*/
StreamParser(absl::Span<const uint8_t> data, bool verbose)
: data_(data), verbose_(verbose) {}

/**
* Attempts to parse a `spi_device_cmd_t`. If there are not enough bytes, it
* returns `default_value`. When `verbose_` is true, it describes the returned
* value in a human-readable string written to stdout
*/
spi_device_cmd_t ParseCmdOr(spi_device_cmd_t default_value) {
const spi_device_cmd_t cmd = ParseCmd().value_or(default_value);
if (verbose_) {
std::cout << std::hex << "Parsed spi_device_cmd_t: {"
<< "opcode=0x" << cmd.opcode << ", address=0x" << cmd.address
<< ", payload_byte_count=0x" << cmd.payload_byte_count << "}"
<< std::endl;
}
return cmd;
}

/**
* Attempts to parse an `IntType` from the stream. If there are not enough
* bytes, it returns `default_value`. When `verbose_` is true, it uses
* `log_label` to write a human-readable message to stdout.
*/
template <typename IntType>
IntType ParseIntOr(const char *log_label, IntType default_value) {
const IntType value = ParseInt<IntType>().value_or(default_value);
if (verbose_) {
std::cout << std::hex << "Parsed " << log_label << ": 0x" << value
<< std::endl;
}
return value;
}

private:
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;
}

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;
}
const spi_device_cmd_t cmd{
.opcode = static_cast<spi_device_opcode_t>(*opcode & UINT8_MAX),
.address = *address,
.payload_byte_count =
*payload_byte_count % (kSpiDevicePayloadAreaNumBytes + 1),
};
return cmd;
}

absl::Span<const uint8_t> data_;
bool verbose_{false};
};

/**
* This class is responsible for one-time startup tasks. Instantiate it as a
* static local variable in `LLVMFuzzerTestOneInput()`.
*/
class StaticFuzzerEnvironment {
public:
StaticFuzzerEnvironment();

StaticFuzzerEnvironment(StaticFuzzerEnvironment&) = delete;
StaticFuzzerEnvironment(StaticFuzzerEnvironment&&) = delete;
};

/**
* This class configures mock objects with reasonable defaults. Some mocks will
* enable the fuzzing engine to drive control flow by getting values from a
* `StreamParser`. Instantiate this class once per call to
* `LLVMFuzzerTestOneInput()`.
*/
class AbstractBootstrapMockGroup {
public:
/**
* @param data A chunk of data generated by the fuzzing engine.
* @param verbose Whether to enable verbose logging.
*/
AbstractBootstrapMockGroup(StreamParser stream, bool verbose);

AbstractBootstrapMockGroup() = delete;
AbstractBootstrapMockGroup(AbstractBootstrapMockGroup &) = delete;
AbstractBootstrapMockGroup(AbstractBootstrapMockGroup &&) = delete;

/**
* This method configures the mocks owned by `AbstractBootstrapMockGroup`. If
* you add custom mocks in a derived class and override this method, be sure
* to call `AbstractBootstrapMockGroup::ConfigureMocks()`.
*/
virtual void ConfigureMocks();

protected:
bool verbose_{false};
bool flash_status_override_{false};
size_t max_spi_cmd_count_{0};
size_t spi_cmd_count_{0};
StreamParser stream_;
::rom_test::NiceMockSpiDevice spi_device_;
::rom_test::NiceMockRstmgr rstmgr_;
::rom_test::NiceMockFlashCtrl flash_ctrl_;
::rom_test::NiceMockOtp otp_;
};

} // namespace bootstrap_fuzzer

#endif // OPENTITAN_SW_DEVICE_SILICON_CREATOR_LIB_BOOTSTRAP_FUZZER_UTIL_H_
16 changes: 16 additions & 0 deletions sw/device/silicon_creator/rom/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ load(
)
load("//rules:cross_platform.bzl", "dual_cc_library", "dual_inputs")
load("@rules_pkg//pkg:mappings.bzl", "pkg_files")
load("@rules_fuzzing//fuzzing:cc_defs.bzl", "cc_fuzz_test")

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

Expand Down Expand Up @@ -314,6 +315,21 @@ cc_test(
],
)

# 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",
"//sw/device/silicon_creator/lib:bootstrap_fuzzer_util",
"@com_google_absl//absl/types:span",
],
)

cc_library(
name = "sigverify_keys",
srcs = [
Expand Down
55 changes: 55 additions & 0 deletions sw/device/silicon_creator/rom/bootstrap_fuzz_test.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// 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 "absl/types/span.h"
#include "sw/device/lib/base/mock_abs_mmio.h"
#include "sw/device/silicon_creator/lib/base/chip.h"
#include "sw/device/silicon_creator/lib/bootstrap_fuzzer_util.h"
#include "sw/device/silicon_creator/rom/bootstrap.h"

#include "gpio_regs.h"
#include "hw/ip/otp_ctrl/data/otp_ctrl_regs.h"
#include "hw/top_earlgrey/sw/autogen/top_earlgrey.h"

namespace {
using bootstrap_fuzzer::AbstractBootstrapMockGroup;
using bootstrap_fuzzer::StreamParser;

class RomMockGroup : public AbstractBootstrapMockGroup {
public:
RomMockGroup(StreamParser stream, bool verbose)
: AbstractBootstrapMockGroup(stream, verbose) {}

void ConfigureMocks() override {
AbstractBootstrapMockGroup::ConfigureMocks();

ON_CALL(otp_, read32(OTP_CTRL_PARAM_OWNER_SW_CFG_ROM_BOOTSTRAP_DIS_OFFSET))
.WillByDefault(::testing::Return(stream_.ParseIntOr<hardened_bool_t>(
"bootstrap_enabled", kHardenedBoolFalse)));

ON_CALL(mmio_,
Read32(TOP_EARLGREY_GPIO_BASE_ADDR + GPIO_DATA_IN_REG_OFFSET))
.WillByDefault(
::testing::Return(stream_.ParseIntOr<uint32_t>("strapping", 0)));
}

private:
::rom_test::NiceMockAbsMmio mmio_;
};
} // namespace

extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
static bootstrap_fuzzer::StaticFuzzerEnvironment static_fuzzer_env;

constexpr bool kVerbose = false;
StreamParser stream(absl::MakeConstSpan(data, size), kVerbose);
RomMockGroup mock_group(stream, kVerbose);
mock_group.ConfigureMocks();

(void)bootstrap();

return 0; // Values other than 0 and -1 are reserved for future use.
}
8 changes: 1 addition & 7 deletions sw/device/silicon_creator/rom_ext/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -118,13 +118,7 @@ cc_fuzz_test(
],
deps = [
":bootstrap",
"//hw/top_earlgrey/ip/flash_ctrl/data/autogen:flash_ctrl_regs",
"//hw/top_earlgrey/sw/autogen:top_earlgrey",
"//sw/device/lib/base:hardened",
"//sw/device/silicon_creator/lib:bootstrap_unittest_util",
"//sw/device/silicon_creator/lib/drivers:flash_ctrl",
"//sw/device/silicon_creator/lib/drivers:spi_device",
"@com_google_absl//absl/types:optional",
"//sw/device/silicon_creator/lib:bootstrap_fuzzer_util",
"@com_google_absl//absl/types:span",
],
)
Expand Down

0 comments on commit 006fc28

Please sign in to comment.