Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[sw/silicon_creator] Add fuzzers for ROM bootstrap and ROM_EXT bootstrap #19194

Merged
merged 2 commits into from
Aug 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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
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
105 changes: 105 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,105 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0

#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
157 changes: 157 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,157 @@
// 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) {}

StreamParser(StreamParser &&) = default;

StreamParser(StreamParser &) = delete;

/**
* 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_
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
16 changes: 16 additions & 0 deletions sw/device/silicon_creator/rom/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ load(
load("//rules:cross_platform.bzl", "dual_cc_library", "dual_inputs")
load("@rules_pkg//pkg:mappings.bzl", "pkg_files")
load("@crt//rules:transition.bzl", "platform_target")
load("@rules_fuzzing//fuzzing:cc_defs.bzl", "cc_fuzz_test")

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

Expand Down Expand Up @@ -322,6 +323,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