blob: b8da105512099eb6e683f48e9bc956542e84036c [file] [log] [blame]
// 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/boot_data.h"
#include <array>
#include <cstring>
#include "gtest/gtest.h"
#include "sw/device/silicon_creator/lib/drivers/mock_flash_ctrl.h"
#include "sw/device/silicon_creator/lib/drivers/mock_hmac.h"
#include "sw/device/silicon_creator/lib/drivers/mock_otp.h"
#include "sw/device/silicon_creator/testing/rom_test.h"
#include "flash_ctrl_regs.h"
#include "hw/top_earlgrey/sw/autogen/top_earlgrey.h"
#include "otp_ctrl_regs.h"
bool operator==(flash_ctrl_perms_t lhs, flash_ctrl_perms_t rhs) {
return std::memcmp(&lhs, &rhs, sizeof(flash_ctrl_perms_t)) == 0;
}
bool operator==(boot_data_t lhs, boot_data_t rhs) {
return std::memcmp(&lhs, &rhs, sizeof(boot_data_t)) == 0;
}
/**
* Example boot data entry with the first `.counter` value.
*/
constexpr boot_data_t kValidEntry0 = {
.digest = {kBootDataIdentifier, kBootDataIdentifier, kBootDataIdentifier,
0x00000000, 0x11111111, 0x22222222, 0x33333333, 0x44444444},
.is_valid = kBootDataValidEntry,
.identifier = kBootDataIdentifier,
.counter = kBootDataDefaultCounterVal,
.min_security_version_rom_ext = 0,
.min_security_version_bl0 = 0,
};
/**
* Example boot data entry with a _higher_ `.counter` value.
*/
constexpr boot_data_t kValidEntry1 = {
.digest = {kBootDataIdentifier, kBootDataIdentifier, kBootDataIdentifier,
0x44444444, 0x33333333, 0x22222222, 0x11111111, 0x00000000},
.is_valid = kBootDataValidEntry,
.identifier = kBootDataIdentifier,
.counter = kBootDataDefaultCounterVal + 1,
.min_security_version_rom_ext = 0,
.min_security_version_bl0 = 0,
};
/**
* Default boot data entry loaded by `boot_data_default_get`.
*/
constexpr boot_data_t kDefaultEntry = {
.digest = {kBootDataIdentifier, kBootDataIdentifier, kBootDataIdentifier,
0xcc761df1, 0xff42f0f2, 0x3f1955ee, 0x9465b3e7, 0x81ce0fdb},
.is_valid = kBootDataValidEntry,
.identifier = kBootDataIdentifier,
.counter = kBootDataDefaultCounterVal,
.min_security_version_rom_ext = 0x01234567,
.min_security_version_bl0 = 0x89abcdef,
};
namespace boot_data_unittest {
namespace {
using ::testing::_;
using ::testing::DoAll;
using ::testing::Return;
using ::testing::SetArgPointee;
class BootDataTest : public rom_test::RomTest {
protected:
rom_test::MockFlashCtrl flash_ctrl_;
rom_test::MockHmac hmac_;
rom_test::MockOtp otp_;
// Data for an entry which is fully erased.
std::array<uint32_t, kBootDataNumWords> erased_entry_ = {};
// Data for a non-erased but non-bootable entry.
std::array<uint32_t, kBootDataNumWords> non_erased_entry_ = {};
// Data for a `boot_data_t` entry with only the first three words erased.
std::array<uint32_t, kBootDataNumWords> part_erased_entry_ = {};
BootDataTest() {
std::fill_n(erased_entry_.begin(), kBootDataNumWords, kFlashCtrlErasedWord);
std::fill_n(non_erased_entry_.begin(), kBootDataNumWords, 0x01234567);
std::fill_n(part_erased_entry_.begin(), kBootDataNumWords, 0x01234567);
std::fill_n(part_erased_entry_.begin(), 3, kFlashCtrlErasedWord);
}
/**
* Sets an expectation that a given boot data entry in the given info page
* is read. The data and return value given by the read can be specified.
*
* @param page The info page containing the boot data entry.
* @param index The index of the boot data entry in the page.
* @param offset Offset into the boot data entry expected to be read from.
* @param data Mock data to be read at this entry. The number of words is
* unchecked and can be less than (or greater) than the boot
* data entry size.
* @param error Value to be returned by the read.
* @param count Optionally the number of values expected to be read from the
* start of the entry. Useful for expecting sniffs.
*/
void ExpectRead(flash_ctrl_info_page_t page, size_t index,
std::array<uint32_t, kBootDataNumWords> data,
rom_error_t error) {
size_t offset = index * sizeof(boot_data_t);
// Mock out flash_ctrl_page_info_read to pass the given `data` and return
// the given `error`.
//
// Using a lambda rather than `.SetArrayArgument(...).Return(error)`
// because we have to cast the `void*` argument to a real pointer type
// before we can write to it.
EXPECT_CALL(flash_ctrl_, InfoRead(page, offset, kBootDataNumWords, _))
.WillOnce([data, error](auto, auto, auto, void *out) {
uint32_t *out_words = static_cast<uint32_t *>(out);
std::copy_n(data.begin(), kBootDataNumWords, out_words);
return error;
});
}
/**
* Sets an expectation that a given boot data entry in an info page was
* sniffed (i.e. with `boot_data_sniff`).
*
* @param page The info page containing the boot entry.
* @param index The index of the boot info entry in the page.
* @param data Data of the boot data entry (only first three words read).
* @param error Value to be returned by the read.
*/
template <size_t N>
void ExpectSniff(flash_ctrl_info_page_t page, size_t index,
std::array<uint32_t, N> data, rom_error_t error) {
static_assert(N > 3, "Data must be at least three words for a sniff");
constexpr uint32_t kIsValidOffset = offsetof(boot_data_t, is_valid);
size_t offset = index * sizeof(boot_data_t) + kIsValidOffset;
// As with `ExpectRead`, provide the given `data` and `error` using a lambda
// to support casting the `void*` parameter before writing.
EXPECT_CALL(flash_ctrl_, InfoRead(page, offset, 3, _))
.WillOnce([data, error](auto, auto, auto, void *out) {
uint32_t *out_words = static_cast<uint32_t *>(out);
std::copy_n(data.begin(), 3, out_words);
return error;
});
}
/**
* Sets an expectation that a digest for the given `boot` data is computed.
*
* @param boot_data The boot data expected to be used in computation.
* @param valid Whether the mocked digest computed should match.
*/
void ExpectDigestCompute(boot_data_t boot_data, bool valid) {
constexpr size_t kDigestRegionOffset = sizeof(boot_data.digest);
constexpr size_t kDigestRegionSize =
sizeof(boot_data_t) - kDigestRegionOffset;
EXPECT_CALL(hmac_, sha256_init());
// Check the post-digest data we're computing with matches what's given.
EXPECT_CALL(hmac_, sha256_update(_, kDigestRegionSize))
.WillOnce(DoAll([boot_data](const void *digest_region, size_t size) {
int digest_region_cmp = std::memcmp(
digest_region,
reinterpret_cast<const char *>(&boot_data) + kDigestRegionOffset,
kDigestRegionSize);
EXPECT_EQ(digest_region_cmp, 0);
return kErrorOk;
}));
// If mocking as invalid, break the digest.
hmac_digest_t digest = boot_data.digest;
if (!valid) {
digest.digest[0] += 1;
}
EXPECT_CALL(hmac_, sha256_final(_))
.WillOnce(DoAll(SetArgPointee<0>(digest), Return(kErrorOk)));
}
/**
* Sets an expectation that the given page's permissions are set to the given
* values for `read`, `write`, and `erase`.
*
* @param page The page whose permissions are expected to be set.
* @param read Expected setting for the `.read` permission.
* @param write Expected setting for the `.write` permission.
* @param erase Expected setting for the `.erase` permission.
*/
void ExpectPermsSet(flash_ctrl_info_page_t page, bool read, bool write,
bool erase) {
flash_ctrl_perms_t perms = {
.read = read ? kMultiBitBool4True : kMultiBitBool4False,
.write = write ? kMultiBitBool4True : kMultiBitBool4False,
.erase = erase ? kMultiBitBool4True : kMultiBitBool4False,
};
EXPECT_CALL(flash_ctrl_, InfoPermsSet(page, perms));
}
/**
* Sets an expectation that the given page is searched for its last valid boot
* data entry.
*
* This acts as a wrapper around the provided function which should contain
* the expectations of sniffs and reads that happen within the given page.
*
* @param page The page expected to be searched for boot data entries.
* @param reads Function given the `page` containing expectations of the reads
* happening there.
*/
void ExpectPageScan(flash_ctrl_info_page_t page,
std::function<void(flash_ctrl_info_page_t)> reads) {
ExpectPermsSet(page, true, false, false);
reads(page);
ExpectPermsSet(page, false, false, false);
}
/**
* Provides a lambda function mocking a page containing various boot
* data entries (all non-bootable or invalid) plus the given `boot_data` which
* is expected to be bootable.
*
* @param boot_data Bootable boot data entry to be inserted into the page.
* @param valid_digest Whether to mock that `boot_data`'s digest is valid.
* @return Lambda function for use with `ExpectPageScan`.
*/
auto EntryPage(boot_data_t boot_data, bool valid_digest = true) {
// Ensures the following memcpy is safe
static_assert(sizeof(uint32_t) * kBootDataNumWords == sizeof(boot_data_t),
"`kBootDataNumWords` must match size of `boot_data_t`");
// Convert the given boot data into an array of words.
std::array<uint32_t, kBootDataNumWords> boot_data_raw = {};
std::memcpy(boot_data_raw.data(), &boot_data, sizeof(boot_data_t));
// Mock the page to have the following layout:
// #0. Non-erased but non-bootable.
// #1. Non-erased and bootable provided boot_data.
// #2. Non-erased and bootable but invalid digest.
// #3. Entry with sniffed area erased but the rest not.
// #4. Fully erased entry.
return [=](flash_ctrl_info_page_t page) {
// Expect to sniff each entry, fully reading if it could be erased.
ExpectSniff(page, 0, non_erased_entry_, kErrorOk);
ExpectSniff(page, 1, boot_data_raw, kErrorOk);
ExpectSniff(page, 2, boot_data_raw, kErrorOk);
ExpectSniff(page, 3, part_erased_entry_, kErrorOk);
ExpectRead(page, 3, part_erased_entry_, kErrorOk);
ExpectSniff(page, 4, erased_entry_, kErrorOk);
ExpectRead(page, 4, erased_entry_, kErrorOk);
// Check the last seen bootable entry's digest (mocked as invalid).
ExpectRead(page, 2, boot_data_raw, kErrorOk);
ExpectDigestCompute(boot_data, false);
// Step back to the previously seen bootable entry (provided `boot_data`).
ExpectRead(page, 1, boot_data_raw, kErrorOk);
ExpectDigestCompute(boot_data, valid_digest);
};
}
/**
* Provides a lambda function mocking a page with only an erased entry.
*
* @return Lambda function for use with `ExpectPageScan`.
*/
auto ErasedPage() {
return [this](auto page) {
ExpectSniff(page, 0, erased_entry_, kErrorOk);
ExpectRead(page, 0, erased_entry_, kErrorOk);
};
}
/**
* Sets an expectation that the device queries for whether the default boot
* data entry should be loaded when in the `prod` lifecycle state.
*
* @param allowed_in_prod Whether loading the default entry should be allowed.
*/
void ExpectAllowedInProdCheck(bool allowed_in_prod) {
EXPECT_CALL(
otp_,
read32(
OTP_CTRL_PARAM_CREATOR_SW_CFG_DEFAULT_BOOT_DATA_IN_PROD_EN_OFFSET))
.WillOnce(
Return(allowed_in_prod ? kHardenedBoolTrue : kHardenedBoolFalse));
}
/**
* Sets an expectation that the default boot data entry is loaded and its
* digest is checked.
*/
void ExpectDefaultEntryRead() {
EXPECT_CALL(
otp_, read32(OTP_CTRL_PARAM_CREATOR_SW_CFG_MIN_SEC_VER_ROM_EXT_OFFSET))
.WillOnce(Return(kDefaultEntry.min_security_version_rom_ext));
EXPECT_CALL(otp_,
read32(OTP_CTRL_PARAM_CREATOR_SW_CFG_MIN_SEC_VER_BL0_OFFSET))
.WillOnce(Return(kDefaultEntry.min_security_version_bl0));
ExpectDigestCompute(kDefaultEntry, true);
}
};
class BootDataReadTest : public BootDataTest {};
TEST_F(BootDataReadTest, ReadBothValidTest1) {
// Expect both pages to be checked, with both giving valid entries.
ExpectPageScan(kFlashCtrlInfoPageBootData0, EntryPage(kValidEntry0));
ExpectPageScan(kFlashCtrlInfoPageBootData1, EntryPage(kValidEntry1));
boot_data_t boot_data = {{0}};
EXPECT_EQ(boot_data_read(kLcStateTest, &boot_data), kErrorOk);
// Expect the entry with the higher `.counter` to have been selected.
EXPECT_EQ(boot_data, kValidEntry1);
}
TEST_F(BootDataReadTest, ReadBothValidTest2) {
// Same as above, but swap which page contains `test_entry_1`.
ExpectPageScan(kFlashCtrlInfoPageBootData0, EntryPage(kValidEntry1));
ExpectPageScan(kFlashCtrlInfoPageBootData1, EntryPage(kValidEntry0));
boot_data_t boot_data = {{0}};
EXPECT_EQ(boot_data_read(kLcStateTest, &boot_data), kErrorOk);
// Expect the entry with the higher `.counter` to have been selected.
EXPECT_EQ(boot_data, kValidEntry1);
}
TEST_F(BootDataReadTest, ReadOneEntryTest) {
// Expect both pages to be searched, but give only a valid entry for one.
ExpectPageScan(kFlashCtrlInfoPageBootData0, EntryPage(kValidEntry0));
ExpectPageScan(kFlashCtrlInfoPageBootData1, ErasedPage());
boot_data_t boot_data = {{0}};
EXPECT_EQ(boot_data_read(kLcStateTest, &boot_data), kErrorOk);
EXPECT_EQ(boot_data, kValidEntry0);
}
TEST_F(BootDataReadTest, ReadOneValidTest) {
// Expect both pages to be searched, but give only a valid entry for one.
ExpectPageScan(kFlashCtrlInfoPageBootData0, EntryPage(kValidEntry0));
ExpectPageScan(kFlashCtrlInfoPageBootData1, EntryPage(kValidEntry1, false));
boot_data_t boot_data = {{0}};
EXPECT_EQ(boot_data_read(kLcStateTest, &boot_data), kErrorOk);
// `...BootData1` had an entry with a higher `.counter`, but it has an
// invalid digest and should not be chosen.
EXPECT_EQ(boot_data, kValidEntry0);
}
TEST_F(BootDataReadTest, ReadErasedDefaultTest) {
// Expect both pages to be searched, but give no entry for either.
ExpectPageScan(kFlashCtrlInfoPageBootData0, ErasedPage());
ExpectPageScan(kFlashCtrlInfoPageBootData1, ErasedPage());
// Expect to fall back to loading the default entry.
ExpectAllowedInProdCheck(false);
ExpectDefaultEntryRead();
boot_data_t boot_data = {{0}};
EXPECT_EQ(boot_data_read(kLcStateTest, &boot_data), kErrorOk);
EXPECT_EQ(boot_data, kDefaultEntry);
}
TEST_F(BootDataReadTest, ReadInvalidDefaultTest) {
// Expect both pages to be searched, but give invalid entries for both.
ExpectPageScan(kFlashCtrlInfoPageBootData0, EntryPage(kValidEntry0, false));
ExpectPageScan(kFlashCtrlInfoPageBootData1, EntryPage(kValidEntry1, false));
// Expect to fall back to loading the default entry.
ExpectAllowedInProdCheck(false);
ExpectDefaultEntryRead();
boot_data_t boot_data = {{0}};
EXPECT_EQ(boot_data_read(kLcStateTest, &boot_data), kErrorOk);
EXPECT_EQ(boot_data, kDefaultEntry);
}
TEST_F(BootDataReadTest, ReadDefaultAllowedInProdTest) {
// Expect both pages to be searched, but give no entry for either.
ExpectPageScan(kFlashCtrlInfoPageBootData0, ErasedPage());
ExpectPageScan(kFlashCtrlInfoPageBootData1, ErasedPage());
// Expect to fall back to loading the default entry (allowed in prod).
ExpectAllowedInProdCheck(true);
ExpectDefaultEntryRead();
boot_data_t boot_data = {{0}};
EXPECT_EQ(boot_data_read(kLcStateProd, &boot_data), kErrorOk);
EXPECT_EQ(boot_data, kDefaultEntry);
}
TEST_F(BootDataReadTest, ReadDefaultNotAllowedInProdTest) {
// Expect both pages to be searched, but give no entry for either.
ExpectPageScan(kFlashCtrlInfoPageBootData0, ErasedPage());
ExpectPageScan(kFlashCtrlInfoPageBootData1, ErasedPage());
// Expect to fall back to loading the default entry (now allowed in prod).
ExpectAllowedInProdCheck(false);
// Do not expect the default entry to be read.
boot_data_t boot_data = {{0}};
EXPECT_EQ(boot_data_read(kLcStateProd, &boot_data), kErrorBootDataNotFound);
}
} // namespace
} // namespace boot_data_unittest