[sw/silicon_creator] Introduce the sec_mmio module The sec_mmio module is responsible for tracking critical register valuesfor an initialized sec_mmio_ctx_t context, and provides a mechanism to check expected values and trigger shutdown escalation on fault detection. Signed-off-by: Miguel Osorio <miguelosorio@google.com>
diff --git a/sw/device/silicon_creator/lib/base/meson.build b/sw/device/silicon_creator/lib/base/meson.build index 6f12e45..782a3c4 100644 --- a/sw/device/silicon_creator/lib/base/meson.build +++ b/sw/device/silicon_creator/lib/base/meson.build
@@ -2,7 +2,6 @@ # Licensed under the Apache License, Version 2.0, see LICENSE for details. # SPDX-License-Identifier: Apache-2.0 -# Mask ROM Secure MMIO module sw_silicon_creator_lib_base_abs_mmio = declare_dependency( link_with: static_library( 'sw_silicon_creator_lib_base_abs_mmio', @@ -28,3 +27,47 @@ cpp_args: ['-DMOCK_ABS_MMIO'], ) ) + +# Mask ROM Secure MMIO module +sw_silicon_creator_lib_base_sec_mmio = declare_dependency( + link_with: static_library( + 'sw_silicon_creator_lib_base_sec_mmio', + sources: [ + 'sec_mmio.c', + ], + dependencies: [ + sw_silicon_creator_lib_base_abs_mmio, + ], + ), +) + +sw_silicon_creator_lib_base_mock_sec_mmio = declare_dependency( + link_with: static_library( + 'mock_sec_mmio', + sources: [ + 'mock_sec_mmio.h', + ], + dependencies: [ + sw_vendor_gtest, + sw_lib_testing_bitfield, + ], + native: true, + ) +) + +test('sw_silicon_creator_lib_base_sec_mmio_unittest', executable( + 'sw_silicon_creator_lib_base_sec_mmio_unittest', + sources: [ + 'sec_mmio_unittest.cc', + 'sec_mmio.c', + ], + dependencies: [ + sw_vendor_gtest, + sw_silicon_creator_lib_base_mock_abs_mmio + ], + native: true, + c_args: ['-DMOCK_ABS_MMIO'], + cpp_args: ['-DMOCK_ABS_MMIO'], + ), + suite: 'mask_rom', +)
diff --git a/sw/device/silicon_creator/lib/base/mock_sec_mmio.h b/sw/device/silicon_creator/lib/base/mock_sec_mmio.h new file mode 100644 index 0000000..1d8794fc --- /dev/null +++ b/sw/device/silicon_creator/lib/base/mock_sec_mmio.h
@@ -0,0 +1,90 @@ +// 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_BASE_MOCK_SEC_MMIO_H_ +#define OPENTITAN_SW_DEVICE_SILICON_CREATOR_LIB_BASE_MOCK_SEC_MMIO_H_ + +#include "sw/device/lib/base/testing/mock_mmio_test_utils.h" +#include "sw/device/lib/testing/mask_rom_test.h" +#include "sw/device/silicon_creator/lib/base/sec_mmio.h" + +namespace mask_rom_test { +namespace internal { +/** + * Mock class for abs_mmio.c. + */ +class MockSecMmio { + public: + MOCK_METHOD(void, Init, (sec_mmio_shutdown_handler callee)); + MOCK_METHOD(uint32_t, Read32, (uint32_t addr)); + MOCK_METHOD(void, Write32, (uint32_t addr, uint32_t value)); + MOCK_METHOD(void, WriteIncrement, (uint32_t value)); + MOCK_METHOD(void, CheckValues, (uint32_t rnd_offset)); + MOCK_METHOD(void, CheckCounters, (uint32_t expected_check_count)); + + virtual ~MockSecMmio() {} +}; +} // namespace internal + +using MockSecMmio = GlobalMock<testing::StrictMock<internal::MockSecMmio>>; + +/** + * Expect a read to the device `dev` at the given offset, returning the given + * 32-bit value. + * + * The value may be given as an integer, a pointer to little-endian data, + * or a `std::initializer_list<BitField>`. + * + * This expectation is sequenced with all other `EXPECT_SEC_READ` and + * `EXPECT_SEC_WRITE` calls. + */ +#define EXPECT_SEC_READ32(mmio, addr, ...) \ + EXPECT_CALL(mmio, Read32(addr)) \ + .WillOnce(testing::Return(mock_mmio::ToInt<uint32_t>(__VA_ARGS__))) + +/** + * Expect a write to the given offset with the given 32-bit value. + * + * The value may be given as an integer, a pointer to little-endian data, + * or a `std::initializer_list<BitField>`. + * + * This function is only available in tests using a fixture that derives + * `MmioTest`. + * + * This expectation is sequenced with all other `EXPECT_SEC_READ` and + * `EXPECT_SEC_WRITE` calls. + */ +#define EXPECT_SEC_WRITE32(mmio, addr, ...) \ + EXPECT_CALL(mmio, Write32(addr, mock_mmio::ToInt<uint32_t>(__VA_ARGS__))); + +extern "C" { + +void sec_mmio_init(sec_mmio_shutdown_handler callee) { + MockSecMmio::Instance().Init(callee); +} + +uint32_t sec_mmio_read32(uint32_t addr) { + MockSecMmio::Instance().Read32(addr); +} + +void sec_mmio_write32(uint32_t addr, uint32_t value) { + MockSecMmio::Instance().Write32(addr, value); +} + +void sec_mmio_write_increment(uint32_t value) { + MockSecMmio::Instance().WriteIncrement(value); +} + +void sec_mmio_check_values(uint32_t rnd_offset) { + MockSecMmio::Instance().CheckValues(rnd_offset); +} + +void sec_mmio_check_counters(uint32_t expected_check_count) { + MockSecMmio::Instance().CheckCounters(expected_check_count); +} + +} // extern "C" +} // namespace mask_rom_test + +#endif // OPENTITAN_SW_DEVICE_SILICON_CREATOR_LIB_BASE_MOCK_SEC_MMIO_H_
diff --git a/sw/device/silicon_creator/lib/base/sec_mmio.c b/sw/device/silicon_creator/lib/base/sec_mmio.c new file mode 100644 index 0000000..0c99d2b --- /dev/null +++ b/sw/device/silicon_creator/lib/base/sec_mmio.c
@@ -0,0 +1,139 @@ +// 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/base/sec_mmio.h" + +#include "sw/device/lib/base/memory.h" +#include "sw/device/silicon_creator/lib/base/abs_mmio.h" + +// FIXME: Linker configuration. +extern sec_mmio_ctx_t sec_mmio_ctx; + +// FIXME: Replace for shutdown module handler. +static sec_mmio_shutdown_handler sec_mmio_shutdown_cb; + +// Value with good hamming weight used to mask the stored expected value. +static const uint32_t kSecMmioMaskVal = 0x21692436u; + +// This must be set to a prime number greater than the number of items in +// `sec_mmio_ctx.addrs`. Used to generate random read order permutations. +static const uint32_t kSecMmioRndStep = 103u; + +/** + * Updates or inserts the register entry pointed to by MMIO `addr` with the + * given `value`. + * + * Increments the `sec_mmio_ctx.last_index`. + */ +static void upsert_register(uint32_t addr, uint32_t value) { + size_t i = 0; + for (; i < sec_mmio_ctx.last_index; ++i) { + if (sec_mmio_ctx.addrs[i] == addr) { + sec_mmio_ctx.values[i] = value; + break; + } + } + if (i == sec_mmio_ctx.last_index && i < kSecMmioRegFileSize) { + sec_mmio_ctx.addrs[i] = addr; + sec_mmio_ctx.values[i] = value; + ++sec_mmio_ctx.last_index; + } + // The following condition check serves as an additional fault detection + // mechanism. + if (i >= kSecMmioRegFileSize) { + sec_mmio_shutdown_cb(); + __builtin_unreachable(); + } +} + +void sec_mmio_init(sec_mmio_shutdown_handler cb) { + sec_mmio_shutdown_cb = cb; + sec_mmio_ctx.last_index = 0; + sec_mmio_ctx.write_count = 0; + sec_mmio_ctx.check_count = 0; + sec_mmio_ctx.expected_write_count = 0; + for (size_t i = 0; i < ARRAYSIZE(sec_mmio_ctx.addrs); ++i) { + sec_mmio_ctx.addrs[i] = UINT32_MAX; + } +} + +uint32_t sec_mmio_read32(uint32_t addr) { + uint32_t value = abs_mmio_read32(addr); + uint32_t masked_value = value ^ kSecMmioMaskVal; + + upsert_register(addr, value); + + if ((abs_mmio_read32(addr) ^ kSecMmioMaskVal) != masked_value) { + sec_mmio_shutdown_cb(); + __builtin_unreachable(); + } + return value; +} + +void sec_mmio_write32(uint32_t addr, uint32_t value) { + abs_mmio_write32(addr, value); + uint32_t masked_value = value ^ kSecMmioMaskVal; + + upsert_register(addr, masked_value); + + if ((abs_mmio_read32(addr) ^ kSecMmioMaskVal) != masked_value) { + sec_mmio_shutdown_cb(); + __builtin_unreachable(); + } + ++sec_mmio_ctx.write_count; +} + +void sec_mmio_write_increment(uint32_t value) { + sec_mmio_ctx.expected_write_count += value; +} + +void sec_mmio_check_values(uint32_t rnd_offset) { + size_t offset = rnd_offset; + size_t i; + for (i = 0; i < sec_mmio_ctx.last_index; ++i) { + // FIXME: Remove dependency on __udivdi3. + offset = (offset + kSecMmioRndStep) % sec_mmio_ctx.last_index; + uint32_t read_value = abs_mmio_read32(sec_mmio_ctx.addrs[offset]); + if ((read_value ^ kSecMmioMaskVal) != sec_mmio_ctx.values[offset]) { + sec_mmio_shutdown_cb(); + __builtin_unreachable(); + } + } + // Check for loop completion. + if (i != sec_mmio_ctx.last_index) { + sec_mmio_shutdown_cb(); + __builtin_unreachable(); + } + ++sec_mmio_ctx.check_count; +} + +void sec_mmio_check_counters(uint32_t expected_check_count) { + // Generous use of volatile in critical variables to avoid compiler + // optimizations, and map "zero" to a value with good hamming weight + // and with a good hamming distance to "all-ones". + // TODO(#6610): Update based on implementation guidance. + static volatile const uint32_t kValZero = 0x3ca5965a; + static volatile const uint32_t kValOnes = 0xc35a69a5; + + uint32_t result = kValZero ^ sec_mmio_ctx.write_count; + result ^= sec_mmio_ctx.expected_write_count; + + // Check the expected write count. This is equivalent to + // sec_mmio_ctx.write_count == sec_mmio_ctx.expected_write_count + if (result != kValZero) { + sec_mmio_shutdown_cb(); + __builtin_unreachable(); + } + + // Check the expected check counts. This is equivalent to + // sec_mmio_ctx.check_count == expected_check_count. This check is expected to + // fail if the previous check failed. + result ^= sec_mmio_ctx.check_count; + result ^= expected_check_count; + if (~result != kValOnes) { + sec_mmio_shutdown_cb(); + __builtin_unreachable(); + } + ++sec_mmio_ctx.check_count; +}
diff --git a/sw/device/silicon_creator/lib/base/sec_mmio.h b/sw/device/silicon_creator/lib/base/sec_mmio.h new file mode 100644 index 0000000..6a9aa51 --- /dev/null +++ b/sw/device/silicon_creator/lib/base/sec_mmio.h
@@ -0,0 +1,197 @@ +// 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_BASE_SEC_MMIO_H_ +#define OPENTITAN_SW_DEVICE_SILICON_CREATOR_LIB_BASE_SEC_MMIO_H_ + +#include <stddef.h> +#include <stdint.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @file + * @brief Secure Memory-mapped IO functions, for volatile access. + * + * This module is responsible for tracking critical register values for an + * initialized context `sec_mmio_ctx_t`, and provides a mechanism to evaluate + * expectations and trigger shutdown escalation on fault detection. + * + * Initialization + * + * - `sec_mmio_init()`. + * + * Register writes + * + * - Perform a number (N) of calls to `sec_mmio_write32()`. + * - Increment the expected number of writes by N by calling + * `sec_mmio_write_increment()`. This is done using a separate function call + * to be able to detect skip instruciton faults on `sec_mmio_write32()` + * calls. + * + * Register reads + * + * Use the `sec_mmio_read32()`. + * + * Expectation checks + * + * See the following: + * + * - `sec_mmio_check_values()` + * - `sec_mmio_check_counters()` + * + * Opens: + * + * - sec_mmio_ctx is currently defined as an extern to simplify testing. For the + * actual target, we need to define a memory region to share the data with the + * ROM_EXT. + * - Currently fault detection escalations are performed by calling a handler + * that is registered at `sec_mmio_init()` call time. Need to determine if we + * want to move to a mock_shutdown implementation, or if we want to refactor + * the code to return error codes. + */ + +enum { + /** + * Number of registers stored in the sec_mmio context. + * + * This value must be less than the `kSecMmioRndStep` in sec_mmio.c. + */ + // TODO(#6609): Update size of expectations table. + kSecMmioRegFileSize = 100, +}; + +/** + * Working context. + * + * Contains list of expected register addresses and associated values, as well + * as expected counters. + */ +typedef struct sec_mmio_ctx { + /** + * List of expected register values. + */ + uint32_t values[kSecMmioRegFileSize]; + + /** + * List of expected register addresses. + */ + uint32_t addrs[kSecMmioRegFileSize]; + + /** + * Represents the expected number of register values. + */ + uint32_t last_index; + /** + * Represents the number of register write operations. Incremented by the + * `sec_mmio_write32()` function. + */ + uint32_t write_count; + /** + * Represents the expected number of register write operations. Incremented by + * the `sec_mmio_write_increment()` function. + */ + uint32_t expected_write_count; + /** + * Represents the number of times the check functions have been called. + * Incremented by the `sec_mmio_check_values()` and the + * `sec_mmio_check_counters()` functions. + */ + uint32_t check_count; +} sec_mmio_ctx_t; + +/** + * Shutdown module callback handler. + */ +typedef void (*sec_mmio_shutdown_handler)(void); + +/** + * Initializes the module. + * + * Registers the `cb` callback handler and initializes the internal + * `sec_mmio_ctx_t` context. + * + * @param cb Shutdown module callback handler. + */ +void sec_mmio_init(sec_mmio_shutdown_handler cb); + +/** + * Reads an aligned uint32_t from the MMIO region `addr`. + * + * This function implements a read-read-comparison operation. The first read + * is stored in the list of expected register values for later comparison + * via `sec_mmio_check_values()`. + * + * A shutdown sequence is initiated if the comparison operation fails. + * + * @param addr The address to read from. + * @return the read value. + */ +uint32_t sec_mmio_read32(uint32_t addr); + +/** + * Writes an aligned uint32_t to the MMIO region `base` at the give byte + * `offset`. + * + * This function implements a write-read-comparison operation. The first write + * value is stored in the list of expected register values for later comparison + * via `sec_mmio_check_values()`. + * + * On successful calls, this function will increment the internal count of + * writes. The caller is responsible to setting the expected write count by + * calling `sec_mmio_write_increment()`. + * + * A shutdown sequence is initiated if the comparison operation fails. + * + * @param addr The address to write to. + * @param value The value to write. + */ +void sec_mmio_write32(uint32_t addr, uint32_t value); + +/** + * Increment the expected count of register writes by `value`. + * + * @param value The expected write count increment. + */ +void sec_mmio_write_increment(uint32_t value); + +/** + * Checks the expected list of register values. + * + * All expected register values are verified against expectations. A shutdown + * sequence is initiated if any of the comparison fails. + * + * The `rnd_offset` parameter can be set to a random value to randomize the + * order of reads. + * + * Calling this function will increment the check function counter on a + * successful call. + * + * The `rnd_offset` parameter can be generated by calling the entropy source or + * the CSRNG driver. + * + * @param rnd_offset A random value used to generate a random read sequence. + */ +void sec_mmio_check_values(uint32_t rnd_offset); + +/** + * Checks the expected counter state. + * + * Checks the expected number of register writes and check counts. A shutdown + * sequence is initiated if the counters fail to match expectations. + * + * Calling this function will increment the check function counter on a + * successful + * + * @param expected_check_count The expected check counter. + */ +void sec_mmio_check_counters(uint32_t expected_check_count); + +#ifdef __cplusplus +} +#endif + +#endif // OPENTITAN_SW_DEVICE_SILICON_CREATOR_LIB_BASE_SEC_MMIO_H_
diff --git a/sw/device/silicon_creator/lib/base/sec_mmio_unittest.cc b/sw/device/silicon_creator/lib/base/sec_mmio_unittest.cc new file mode 100644 index 0000000..10ede39 --- /dev/null +++ b/sw/device/silicon_creator/lib/base/sec_mmio_unittest.cc
@@ -0,0 +1,192 @@ +// 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/base/sec_mmio.h" + +#include <array> +#include <cstdlib> +#include <vector> + +#include "gtest/gtest.h" +#include "sw/device/silicon_creator/lib/base/mock_abs_mmio.h" + +extern "C" { +// This is an extern in the sec_mmio module. +sec_mmio_ctx_t sec_mmio_ctx; +} + +namespace sec_mmio_unittest { +namespace { +using ::testing::Each; +using ::testing::Eq; +using ::testing::Test; + +class SecMmioTest : public mask_rom_test::MaskRomTest { + protected: + void SetUp() override { + sec_mmio_init(+[] { std::abort(); }); + } + sec_mmio_ctx_t *ctx_ = &::sec_mmio_ctx; + mask_rom_test::MockAbsMmio mmio_; +}; + +TEST_F(SecMmioTest, Initialize) { + // Write non-zero values to critical fields before calling `sec_mmio_init()`. + ctx_->check_count = 1; + ctx_->expected_write_count = 1; + ctx_->last_index = 1; + ctx_->write_count = 1; + ctx_->addrs[0] = 0; + sec_mmio_init(+[] { std::abort(); }); + + EXPECT_EQ(ctx_->check_count, 0); + EXPECT_EQ(ctx_->expected_write_count, 0); + EXPECT_EQ(ctx_->last_index, 0); + EXPECT_EQ(ctx_->write_count, 0); + EXPECT_THAT(ctx_->addrs, Each(Eq(UINT32_MAX))); +} + +TEST_F(SecMmioTest, Read32OrDie) { + EXPECT_ABS_READ32(mmio_, 0, 0x12345678); + EXPECT_ABS_READ32(mmio_, 0, 0x12345678); + EXPECT_EQ(sec_mmio_read32(0), 0x12345678); + + EXPECT_ABS_READ32(mmio_, 4, 0x87654321); + EXPECT_ABS_READ32(mmio_, 4, 0x87654321); + EXPECT_EQ(sec_mmio_read32(4), 0x87654321); + + EXPECT_ABS_READ32(mmio_, 0, 0x87654321); + EXPECT_ABS_READ32(mmio_, 0, 0x87654321); + EXPECT_EQ(sec_mmio_read32(0), 0x87654321); + + // Two of the operations were targeting the same offset, so we only expect two + // operations and zero shutdown attempts. + EXPECT_EQ(ctx_->write_count, 0); + EXPECT_EQ(ctx_->last_index, 2); +} + +TEST_F(SecMmioTest, Write32) { + EXPECT_ABS_WRITE32(mmio_, 0, 0x12345678); + EXPECT_ABS_READ32(mmio_, 0, 0x12345678); + sec_mmio_write32(0, 0x12345678); + EXPECT_EQ(ctx_->write_count, 1); + + EXPECT_ABS_WRITE32(mmio_, 4, 0x87654321); + EXPECT_ABS_READ32(mmio_, 4, 0x87654321); + sec_mmio_write32(4, 0x87654321); + EXPECT_EQ(ctx_->write_count, 2); + + EXPECT_ABS_WRITE32(mmio_, 0, 0x87654321); + EXPECT_ABS_READ32(mmio_, 0, 0x87654321); + sec_mmio_write32(0, 0x87654321); + EXPECT_EQ(ctx_->write_count, 3); + + // Two of the operations were targeting the same offset, so we only expect two + // operations. + EXPECT_EQ(ctx_->last_index, 2); +} + +TEST_F(SecMmioTest, CounterInc) { + sec_mmio_write_increment(5); + EXPECT_EQ(ctx_->expected_write_count, 5); + + sec_mmio_write_increment(10); + EXPECT_EQ(ctx_->expected_write_count, 15); +} + +TEST_F(SecMmioTest, CheckValues) { + EXPECT_ABS_WRITE32(mmio_, 0, 0x12345678); + EXPECT_ABS_READ32(mmio_, 0, 0x12345678); + sec_mmio_write32(0, 0x12345678); + + EXPECT_ABS_WRITE32(mmio_, 4, 0x87654321); + EXPECT_ABS_READ32(mmio_, 4, 0x87654321); + sec_mmio_write32(4, 0x87654321); + + EXPECT_ABS_WRITE32(mmio_, 8, 0); + EXPECT_ABS_READ32(mmio_, 8, 0); + sec_mmio_write32(8, 0); + + // The expected permutation order for rnd_offset=0 is {1, 2, 0}. + EXPECT_ABS_READ32(mmio_, 4, 0x87654321); + EXPECT_ABS_READ32(mmio_, 8, 0); + EXPECT_ABS_READ32(mmio_, 0, 0x12345678); + sec_mmio_check_values(/*rnd_offset=*/0); + EXPECT_EQ(ctx_->check_count, 1); + + // The expected permutation order for rnd_offset=1 is {2, 0, 1}. + EXPECT_ABS_READ32(mmio_, 8, 0); + EXPECT_ABS_READ32(mmio_, 0, 0x12345678); + EXPECT_ABS_READ32(mmio_, 4, 0x87654321); + sec_mmio_check_values(/*rnd_offset=*/1); + EXPECT_EQ(ctx_->check_count, 2); + + // The expected permutation order for rnd_offset=32 is {0, 1, 2}. + EXPECT_ABS_READ32(mmio_, 0, 0x12345678); + EXPECT_ABS_READ32(mmio_, 4, 0x87654321); + EXPECT_ABS_READ32(mmio_, 8, 0); + sec_mmio_check_values(/*rnd_offset=*/32); + EXPECT_EQ(ctx_->check_count, 3); +} + +TEST_F(SecMmioTest, CheckCount) { + EXPECT_ABS_WRITE32(mmio_, 0, 0x12345678); + EXPECT_ABS_READ32(mmio_, 0, 0x12345678); + sec_mmio_write32(0, 0x12345678); + sec_mmio_write_increment(1); + + sec_mmio_check_counters(/*expected_check_count=*/0); + sec_mmio_check_counters(/*expected_check_count=*/1); + EXPECT_EQ(ctx_->check_count, 2); +} + +// Negative test cases trigger assertions, which are caugth by `ASSERT_DEATH` +// calls. All test cases use lambda functions to wrap expectations and work +// around issue google/googletest#1004. +class SecMmioDeathTest : public SecMmioTest {}; + +TEST_F(SecMmioDeathTest, Read32OrDieSimulatedFault) { + auto deadly_ops = [this] { + EXPECT_ABS_READ32(mmio_, 0, 0x12345678); + EXPECT_ABS_READ32(mmio_, 0, 0); + sec_mmio_read32(0); + }; + ASSERT_DEATH(deadly_ops(), ""); +} + +TEST_F(SecMmioDeathTest, Write32SimulatedFault) { + auto deadly_ops = [this] { + EXPECT_ABS_WRITE32(mmio_, 0, 0x12345678); + EXPECT_ABS_READ32(mmio_, 0, 0); + sec_mmio_write32(0, 0x12345678); + }; + ASSERT_DEATH(deadly_ops(), ""); +} + +TEST_F(SecMmioDeathTest, CheckValuesSimulatedFault) { + auto deadly_ops = [this] { + EXPECT_ABS_WRITE32(mmio_, 0, 0x12345678); + EXPECT_ABS_READ32(mmio_, 0, 0x12345678); + sec_mmio_write32(0, 0x12345678); + + EXPECT_ABS_READ32(mmio_, 0, 0); + sec_mmio_check_values(/*rnd_offset=*/0); + }; + ASSERT_DEATH(deadly_ops(), ""); +} + +TEST_F(SecMmioDeathTest, CheckCountWriteMismatch) { + auto deadly_ops = [this] { + EXPECT_ABS_WRITE32(mmio_, 0, 0x12345678); + EXPECT_ABS_READ32(mmio_, 0, 0x12345678); + sec_mmio_write32(0, 0x12345678); + sec_mmio_check_counters(/*expected_check_count=*/0); + }; + // The developer forgot to increment the write counter, or an attacker + // glitched the sec write operation. + ASSERT_DEATH(deadly_ops(), ""); +} + +} // namespace +} // namespace sec_mmio_unittest