-
Notifications
You must be signed in to change notification settings - Fork 721
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[sw] Refactor and add fuzzer for ROM bootstrap
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
Showing
8 changed files
with
373 additions
and
175 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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_ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
// 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 <utility> | ||
|
||
#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(std::move(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(std::move(stream), kVerbose); | ||
mock_group.ConfigureMocks(); | ||
|
||
(void)bootstrap(); | ||
|
||
return 0; // Values other than 0 and -1 are reserved for future use. | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.