-
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] Add a fuzzer for the common bootstrap library
+ 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
Showing
8 changed files
with
214 additions
and
0 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,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. | ||
} |
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
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