Add compile-time checks for silent reboot, disabled core dumps, and disabled debugger-aware panic handling. Include regression coverage, hardening guidance, and update Phase 8/9 project status.
72 lines
8.6 KiB
Markdown
72 lines
8.6 KiB
Markdown
# Security hardening — Phase 9
|
|
|
|
**Status: in progress.** Phase 8 is complete at the accepted 8D.22 scope. Phase 9 starts with **9A crash/debug build policy**; host matrix and firmware build validation passed on 2026-09-15, and target-hardware validation has not run. This document records policy and procedures, not unrun passes or production certification.
|
|
|
|
## Scope and threat model
|
|
|
|
Reduce network abuse, accidental diagnostic disclosure and unnecessary secret retention while preserving one UART1 broker writer, isolated observers and binary transparency. UART0 remains trusted physical administration/recovery; native USB remains network-independent UART1 access, not an admin console. Whole-device reboot interrupts every transport.
|
|
|
|
Secure boot and encrypted NVS are explicitly excluded by user preference. Physical flash/RAM extraction and firmware replacement remain outside the threat model even after Phase 9. There is no commitment to flash/PSRAM encryption, eFuse provisioning or physical JTAG restrictions. Software debugger-aware configuration checks do not disable physical debug access by fuse.
|
|
|
|
9A does not change partitions, at-rest encryption, dependencies or generated assets, and requires no upload or erase as part of host/build validation. The unused `nvs_key` and `coredump` partitions remain for layout compatibility. Disabling new dumps does not clear old coredump contents. Logical NVS replacement, reset and credential rotation are not secure erasure; historical plaintext copies can remain.
|
|
|
|
## 9A supported build baseline
|
|
|
|
`src/security_build_policy.c` enforces the following at compile time, with explicit settings in `sdkconfig.defaults`:
|
|
|
|
- Require `CONFIG_ESP_COREDUMP_ENABLE_TO_NONE=y` and `CONFIG_ESP_SYSTEM_PANIC_SILENT_REBOOT=y`.
|
|
- Reject enabled core-dump support, flash or UART dump destinations.
|
|
- Reject panic print/reboot, print/halt and GDBstub modes, plus runtime GDBstub.
|
|
- Reject `CONFIG_ESP_DEBUG_OCDAWARE` and `CONFIG_FREERTOS_DEBUG_OCDAWARE` when enabled.
|
|
|
|
The host matrix in `tests/security_build_policy/run.py` compiles the actual guard against synthetic configurations; it is not merely a text check of defaults. Existing generated SDK configuration must also satisfy the guard: defaults alone are not evidence of the effective build configuration.
|
|
|
|
Silent panic reboot deliberately sacrifices panic text, register dumps and backtraces for reduced crash disclosure. Reset-reason/boot information and ordinary status/logging can remain; neither silence across the full boot sequence nor general log redaction is guaranteed. A monitor exception decoder cannot reconstruct a backtrace that was never emitted.
|
|
|
|
## Operational profiles
|
|
|
|
These are handling and validation profiles of the **same supported build baseline**, not separate PlatformIO environments or selectable security overrides.
|
|
|
|
| Profile | Operational rules |
|
|
|---|---|
|
|
| Development | Keep the guard enabled; use synthetic credentials and controlled serial payloads for fault investigation. Keep UART0 recovery available. Review captures before sharing. |
|
|
| Test | Use an isolated, expendable target and synthetic secrets; record exact source/configuration, host/build results and device observations. Exercise crashes and recovery without exporting raw memory. |
|
|
| Production | Use the same guard, restrict physical/network access, verify device identity through trusted UART0, and apply reviewed provisioning/rotation/recovery procedures. Readiness remains pending Phase 9 review and target evidence. |
|
|
|
|
If richer crash debugging is essential, use an isolated synthetic-secret build outside this supported baseline. It requires explicit reviewed changes to the source policy and applicable configuration; no bypass flag is provided. Do not use real credentials or deploy that build as production firmware. Restore and revalidate the supported policy before release.
|
|
|
|
Raw flash, RAM and dumps can contain Wi-Fi passwords, private keys, password verifiers, session material and serial payloads. Treat them as secret-bearing and **do not export them as routine diagnostics**. Prefer bounded status/counter observations and reviewed synthetic-secret reproductions. Restrict any exceptional artifacts and define retention/deletion before collecting them; deletion is not a secure-erase guarantee.
|
|
|
|
## Validation gates
|
|
|
|
### Host and build — passed 2026-09-15
|
|
|
|
From the repository root:
|
|
|
|
```sh
|
|
python3 tests/security_build_policy/run.py
|
|
pio run
|
|
python3 tests/security_build_policy/run.py --sdkconfig-header .pio/build/esp32-s3-devkitc-1-n16r8/config/sdkconfig.h
|
|
```
|
|
|
|
Validation: 17 synthetic/default compile cases passed; the generated SDK-header check passed as the eighteenth case. `pio run` passed with 94,196 B linked RAM and 1,828,565 B flash. The initial host invocation was blocked by the compiler wrapper's read-only cache location; rerunning with `CCACHE_DISABLE=1` passed. Independent policy review and `git diff --check` passed. No upload, erase, eFuse operation or target test was performed.
|
|
|
|
Record the revision, compiler/build outcome and effective configuration. Confirm that the matrix accepts the supported configuration, rejects each prohibited option independently, and rejects absent/disabled required settings. Confirm the normal firmware build compiles the guard. A rejected unsafe configuration is an expected negative-test result, not a firmware build pass. Neither these commands nor a successful build proves target panic behavior.
|
|
|
|
### Target hardware — not run; required for 9A acceptance
|
|
|
|
1. On an isolated synthetic-secret target, record the tested image/configuration and capture UART0 at 115200 baud. Verify normal boot, UART0 administration, native USB UART1 access, HTTPS and SSH before fault testing.
|
|
2. Through separately reviewed test-only fault injection, trigger a controlled panic with the supported build policy intact. Verify reboot rather than halt/debugger wait, no panic register/backtrace output and no UART core dump. Record any remaining boot/reset information; do not promise complete UART silence.
|
|
3. Verify no new flash core dump is written using a reviewed target-side pass/fail check that does not export partition contents. Distinguish old partition contents from a new write; do not erase the partition merely to claim this test passed.
|
|
4. After reboot, verify UART0 recovery and USB serial access, then authenticated HTTPS/SSH and broker writer/observer behavior. With network services unavailable, verify UART0 and native USB still work. Review routine status/log output using synthetic secrets; this is bounded evidence, not universal redaction proof.
|
|
5. Record outcomes and limitations before marking 9A complete. Device flashing/fault injection requires a separately authorized hardware session; no eFuse changes, partition migration or erase is required by this policy.
|
|
|
|
## Staged next work
|
|
|
|
- **Next: bounded SSH cross-connection throttling.** `src/ssh_transport.c` currently closes after three counted failed authentication attempts in a session; slot reset/reconnect resets the budget. Not every protocol message is counted (for example, an unsigned public-key probe is not a failed signed authentication). `src/web_cookie_auth.c` already limits credential verifications to five per 60-second fixed global window, shared across clients; this is not a sliding window or persistent account lockout, and auth lifecycle restart resets its state. Define reconnect-resistant SSH admission/backoff, bounded state and secret-free counters without blocking established sessions, UART0 or USB.
|
|
- **Later: zeroization and protocol policy.** Audit secret lifetimes and failure cleanup across application, mbedTLS, wolfSSL and wolfSSH; review crypto algorithms, certificate trust, browser security headers and password policy. Existing wipes are not proof that every library/stack/PSRAM copy is cleared.
|
|
- **Later: maintenance and lifecycle.** Review ESP-IDF/wolfSSL/wolfSSH advisories and dependency licenses, then plan any upgrades separately. Complete provisioning, key rotation, backup, factory reset, recovery and decommissioning runbooks without claiming physical-extraction resistance or secure erasure.
|
|
- **Phase 10: OTA trust.** Define independent image-signature verification, trust-anchor provisioning, rotation/revocation, rollback/downgrade and recovery policy without secure boot. Authenticated transport alone is not image-signing policy, and OTA signature checks cannot prevent physical firmware replacement.
|
|
|
|
See the [roadmap](roadmap.md#phase-9--security-and-production-hardening), [electrical procedures](electrical_tests.md) and [administration regressions](user_administration_tests.md) for wider gates. Production readiness remains pending; Phase 8 acceptance is not reopened by these follow-ups.
|