test: add option for the simulator to write to file.
What changed, and why it matters
This commit adds a testing-only feature to the BitBox02 simulator that lets it save and reload its pretend device memory from files on disk. It is meant to help developers test things like renaming a device without restarting from scratch. The change only affects test/simulator code, not the real hardware wallet firmware, and there is no indication it fixes or introduces a security vulnerability.
No security action required. Treat as a normal test-infrastructure enhancement. If reviewing for code quality, note that file I/O errors terminate the simulator process and that path construction is bounded by a 512-byte buffer with length checks.
Security signals we found
No security relevance claimed by the vendor in commit message or changelog
Change is confined to test/simulator fake memory implementation
Opt-in via environment variable, not enabled by default
No modifications to cryptographic, bootloader, or production firmware code
No CVE, advisory, researcher attribution, or security bug description present
Evidence from the diff
The patch modifies test/hardware-fakes/src/fake_memory.c so that when the environment variable FAKE_MEMORY_FILEPATH is set, the simulator reads and writes three memory regions (shared, app, eeprom) from files instead of in-memory buffers. It adds file initialization on factory reset, file-backed chunk writes, and file-backed reads. The code is guarded by an opt-in environment variable and is part of the hardware-fakes test infrastructure. No production firmware paths are changed.
Changed components
test/hardware-fakes/src/fake_memory.cBitBox02 simulator test environmentInspect captured patch +126 / −0
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 31954aa..b7e00c3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -13,6 +13,7 @@ customers cannot upgrade their bootloader, its changes are recorded separately.
- simulator: enable Test Merchant for payment requests
- simulator: simulate a Nova device
- Add API call to fetch multiple xpubs at once
+- Add the option for the simulator to write its memory to file.
### 9.23.1
- EVM: add HyperEVM (HYPE) and SONIC (S) to known networks
diff --git a/test/hardware-fakes/src/fake_memory.c b/test/hardware-fakes/src/fake_memory.c
index 0ac9ab1..583bd16 100644
--- a/test/hardware-fakes/src/fake_memory.c
+++ b/test/hardware-fakes/src/fake_memory.c
@@ -18,17 +18,60 @@
#include <memory/memory_shared.h>
#include <stdio.h>
#include <string.h>
+#include <sys/stat.h>
+#include <stdlib.h>
+
+
+#define FAKE_MEMORY_ENV_VAR "FAKE_MEMORY_FILEPATH"
+#define SHARED_SUFFIX "shared"
+#define APP_SUFFIX "app"
+#define EEPROM_SUFFIX "eeprom"
static uint8_t _memory_shared_data[FLASH_SHARED_DATA_LEN] = {0};
static uint8_t _memory_app_data[FLASH_APPDATA_LEN] = {0};
static uint8_t _memory_smarteeprom[SMARTEEPROM_RESERVED_FLASH_PAGES * FLASH_PAGE_SIZE] = {0};
+static void _init_file_if_needed(
+ const char* base_path,
+ const char* suffix,
+ const uint8_t* data,
+ size_t data_size)
+{
+ char path[512];
+ if (snprintf(path, sizeof(path), "%s_%s", base_path, suffix) >= sizeof(path)) {
+ fprintf(stderr, "file path %s_%s too long\n", base_path, suffix);
+ exit(EXIT_FAILURE);
+ }
+
+ struct stat st;
+ if (stat(path, &st) == 0) {
+ return; // The file already exists, do nothing.
+ }
+
+ FILE* f = fopen(path, "wb");
+ if (f) {
+ fwrite(data, 1, data_size, f);
+ fclose(f);
+ } else {
+ fprintf(stderr, "error: could not create fake memory file %s\n", path);
+ exit(EXIT_FAILURE);
+ }
+}
+
void fake_memory_factoryreset(void)
{
memset(_memory_shared_data, 0xff, sizeof(_memory_shared_data));
memset(_memory_app_data, 0xff, sizeof(_memory_app_data));
memset(_memory_smarteeprom, 0xff, sizeof(_memory_smarteeprom));
+
+ const char* base_path = getenv(FAKE_MEMORY_ENV_VAR);
+ if (base_path) {
+ _init_file_if_needed(base_path, SHARED_SUFFIX, _memory_shared_data, sizeof(_memory_shared_data));
+ _init_file_if_needed(base_path, APP_SUFFIX, _memory_app_data, sizeof(_memory_app_data));
+ _init_file_if_needed(base_path, EEPROM_SUFFIX, _memory_smarteeprom, sizeof(_memory_smarteeprom));
+ }
}
+
#define ALLOWED_HASH \
"\x1e\x4a\xa8\x36\x4e\x93\x5c\x07\x85\xe4\xf8\x91\x20\x83\x07\xd8\x32\xf7\x88\x17\x2e\x4b\xf6" \
"\x16\x21\xde\x6d\xf9\xec\x3c\x21\x5f"
@@ -63,8 +106,78 @@ static uint8_t* _get_memory(uint32_t base)
}
}
+static void _write_file_chunk(const char* suffix, uint32_t offset, const uint8_t* chunk, size_t len)
+{
+ char path[512];
+ const char* base_path = getenv(FAKE_MEMORY_ENV_VAR);
+ if (!base_path) {
+ return;
+ }
+ if (snprintf(path, sizeof(path), "%s_%s", base_path, suffix) >= sizeof(path)) {
+ fprintf(stderr, "file path %s_%s too long\n", base_path, suffix);
+ exit(EXIT_FAILURE);
+ }
+ FILE* f = fopen(path, "r+b");
+ if (!f) {
+ fprintf(stderr, "error: fake memory file %s not found\n", path);
+ exit(EXIT_FAILURE);
+ }
+ fseek(f, offset, SEEK_SET);
+ fwrite(chunk, 1, len, f);
+ fclose(f);
+}
+
+static void _read_file_chunk(const char* suffix, uint32_t offset, uint8_t* chunk, size_t len)
+{
+ char path[512];
+ const char* base_path = getenv(FAKE_MEMORY_ENV_VAR);
+ if (!base_path) {
+ return;
+ }
+ if (snprintf(path, sizeof(path), "%s_%s", base_path, suffix) >= sizeof(path)) {
+ fprintf(stderr, "file path %s_%s too long\n", base_path, suffix);
+ exit(EXIT_FAILURE);
+ }
+ FILE* f = fopen(path, "rb");
+ if (!f) {
+ fprintf(stderr, "error: fake memory file %s not found\n", path);
+ exit(EXIT_FAILURE);
+ }
+ fseek(f, offset, SEEK_SET);
+ fread(chunk, 1, len, f);
+ fclose(f);
+}
+
bool memory_write_to_address_fake(uint32_t base, uint32_t addr, const uint8_t* chunk)
{
+ const char* base_path = getenv(FAKE_MEMORY_ENV_VAR);
+
+ if (base_path) {
+ size_t len = CHUNK_SIZE;
+ const char* suffix = NULL;
+ switch (base) {
+ case FLASH_SHARED_DATA_START:
+ suffix = SHARED_SUFFIX;
+ break;
+ case FLASH_APPDATA_START:
+ suffix = APP_SUFFIX;
+ break;
+ case FLASH_SMARTEEPROM_START:
+ suffix = EEPROM_SUFFIX;
+ break;
+ default:
+ return false;
+ }
+ if (chunk == NULL) {
+ uint8_t empty[CHUNK_SIZE];
+ memset(empty, 0xff, CHUNK_SIZE);
+ _write_file_chunk(suffix, addr, empty, len);
+ } else {
+ _write_file_chunk(suffix, addr, chunk, len);
+ }
+ return true;
+ }
+
if (chunk == NULL) {
memset(_get_memory(base) + addr, 0xff, (size_t)CHUNK_SIZE);
} else {
@@ -80,11 +193,23 @@ bool memory_write_chunk_fake(uint32_t chunk_num, const uint8_t* chunk)
void memory_read_chunk_fake(uint32_t chunk_num, uint8_t* chunk_out)
{
+ const char* base_path = getenv(FAKE_MEMORY_ENV_VAR);
+ if (base_path) {
+ _read_file_chunk(APP_SUFFIX, chunk_num * (size_t)CHUNK_SIZE, chunk_out, (size_t)CHUNK_SIZE);
+ return;
+ }
+
memcpy(chunk_out, _memory_app_data + chunk_num * (size_t)CHUNK_SIZE, (size_t)CHUNK_SIZE);
}
void memory_read_shared_bootdata_fake(uint8_t* chunk_out)
{
+ const char* base_path = getenv(FAKE_MEMORY_ENV_VAR);
+ if (base_path) {
+ _read_file_chunk(SHARED_SUFFIX, 0, chunk_out, (size_t)FLASH_SHARED_DATA_LEN);
+ return;
+ }
+
memcpy(chunk_out, _memory_shared_data, (size_t)FLASH_SHARED_DATA_LEN);
}
Why this scored 20/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.