From ef8b891480592258a1c30b1f4fc4f221dd44d393 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 30 Jul 2026 02:35:13 +0200 Subject: [PATCH 1/2] fix(M12): clear keypad buffer on idle; distinct overflow beep Co-Authored-By: Claude Opus 4.8 (1M context) --- core1.c | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/core1.c b/core1.c index 0e269ef..8374994 100644 --- a/core1.c +++ b/core1.c @@ -29,8 +29,14 @@ #define INPUT_MAX_LEN 9 #define TOTP_CODE_LEN 6 -static char input_buf[INPUT_MAX_LEN + 1]; -static int input_len = 0; +// Clear an abandoned (non-empty, untouched) buffer after this long so a later +// visitor's keystrokes cannot concatenate onto a previous person's partial +// entry (each retained digit removes a factor of 10 from the search space). +#define INPUT_IDLE_TIMEOUT_US (10ull * 1000 * 1000) // ~10 s + +static char input_buf[INPUT_MAX_LEN + 1]; +static int input_len = 0; +static uint64_t last_input_us = 0; // timestamp of the last accepted keystroke static void input_clear(void) { memset(input_buf, 0, sizeof(input_buf)); @@ -112,6 +118,12 @@ void main1(void) { keypad_init(); while (true) { + // Drop an abandoned partial entry after the idle window so the next + // person cannot complete or extend a previous visitor's buffer. + if (input_len > 0 && time_us_64() - last_input_us > INPUT_IDLE_TIMEOUT_US) { + input_clear(); + } + char key = keypad_get_key(); if (key) { @@ -141,9 +153,15 @@ void main1(void) { case '9': if (input_len < INPUT_MAX_LEN) { input_buf[input_len++] = key; + last_input_us = time_us_64(); buzzer_beep_short(); + } else { + // Buffer full: emit a DISTINCT tone rather than dropping the + // key silently, so "no beep" cannot be used as an oracle to + // learn that the buffer has reached its maximum length. + last_input_us = time_us_64(); + buzzer_beep_medium(); } - // silently ignore if buffer full break; // B, C, D reserved for future use From 9b6dbd2a6ac48d5073d09a7e677396c993474951 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 4 Aug 2026 20:26:42 +0200 Subject: [PATCH 2/2] fix(cmd_status): function-scope keys so the coverage/firmware build compiles Rebasing onto current master pulled in a latent build break: cmd_status declares `keys` inside the `if (commands_is_admin())` block but scrubs it via `secure_wipe(keys, sizeof(keys))` at function scope, so `keys` is undeclared there. asan_commands links serial/commands.c (not commands_system.c) so it never compiled this TU, but `make -C test coverage` compiles the whole first-party surface and fails here (`keys undeclared`) -- as does the real firmware build. Restore `keys` to function scope, matching the scrub's intent of always clearing the key DB from BSS (incl. the non-admin path where it stays zero-initialised). Same fix as PR #15 (M1). Co-Authored-By: Claude Opus 4.8 (1M context) --- serial/commands_system.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/serial/commands_system.c b/serial/commands_system.c index 4b92f9e..bdd3cac 100644 --- a/serial/commands_system.c +++ b/serial/commands_system.c @@ -64,11 +64,13 @@ void cmd_status(int argc, char **argv) { printf("ntp: not synced\r\n"); } - // Keys (admin only: key inventory is target-selection data) + // Keys (admin only: key inventory is target-selection data). Declared at + // function scope so the scrub below always runs, even on the non-admin path + // where the array stays zero-initialised. + static key_record_t keys[BACKUP_MAX_KEYS]; if (commands_is_admin()) { - static key_record_t keys[BACKUP_MAX_KEYS]; - int count = storage_key_list(keys, BACKUP_MAX_KEYS); - int enabled = 0, corrupt = 0; + int count = storage_key_list(keys, BACKUP_MAX_KEYS); + int enabled = 0, corrupt = 0; for (int i = 0; i < count; i++) { if (!keys[i].is_checksum_valid) corrupt++;