Migrate to IDF 5.5.3 candidate

Pin PlatformIO packages and toolchains, rebase protected SDK
overrides, and add WebSocket receive regression coverage. Document
isolated candidate validation, archive provenance, and remaining gates.
This commit is contained in:
2026-09-18 14:23:13 +02:00
parent cdc4d4a8df
commit 797d2681ac
32 changed files with 1394 additions and 123 deletions
+3 -3
View File
@@ -14,9 +14,9 @@ ESP32-S3 firmware for a secure, multi-transport RS-232 adapter. It operates one
## Development status ## Development status
Hardware characterization, serial/USB/Wi-Fi/HTTPS/SSH and local display/control are implemented and hardware-validated. **Phase 8 role-based users and administration is complete:** 8A8C were target-hardware validated and the user explicitly signed off tested firmware at **8D.22 (2026-09-13)**. See the [roadmap](docs/roadmap.md#phase-8--role-based-users-and-administrative-access--complete) and [acceptance evidence](docs/web_administration_acceptance.md). Very low internal/DMA lifetime minima remain a nonblocking headroom follow-up, not an approved reserve. **Phase 9 security hardening is in progress**: 9A crash/debug policy, 9B SSH admission/credential handling and 9C library cleanup/protocol policy have passed host/build checks. **9D maintenance and lifecycle is in progress:** an operator runbook, finite SSH parser/caller and IDF applicability reviews, and offline notice assembly tooling are available. Scoped corrections have parent host/build evidence; the [finite dependency search](docs/dependency_advisory_coverage.md) is complete (all 22 IDF advisory entries across three pages plus named managed/browser channels), but fresh findings, explicit coverage gaps, release delivery/source/legal questions and whole-phase target gates remain open. **The PMF-capable SoftAP security blocker is not fixed**: the [update plan](docs/wifi_security_update_plan.md) requires a coherent vendor framework/source/header/binary ABI bundle and compatible tooling, not PMF/WPA3 weakening; **Phase 9 is not complete or production-ready**. The user will validate Phase 9 as a whole; no 9D device validation or license clearance is claimed. See [security hardening](docs/security_hardening.md) for scope, operational profiles, and validation gates. Hardware characterization, serial/USB/Wi-Fi/HTTPS/SSH and local display/control are implemented and hardware-validated. **Phase 8 role-based users and administration is complete:** 8A8C were target-hardware validated and the user explicitly signed off tested firmware at **8D.22 (2026-09-13)**. See the [roadmap](docs/roadmap.md#phase-8--role-based-users-and-administrative-access--complete) and [acceptance evidence](docs/web_administration_acceptance.md). Very low internal/DMA lifetime minima remain a nonblocking headroom follow-up, not an approved reserve. **Phase 9 security hardening is in progress**: 9A crash/debug policy, 9B SSH admission/credential handling and 9C library cleanup/protocol policy have passed host/build checks. **9D maintenance and lifecycle is in progress:** an operator runbook, finite SSH parser/caller and IDF applicability reviews, and offline notice assembly tooling are available. Scoped corrections have parent host/build evidence; the [finite dependency search](docs/dependency_advisory_coverage.md) is complete (all 22 IDF advisory entries across three pages plus named managed/browser channels), but fresh findings, explicit coverage gaps, release delivery/source/legal questions and whole-phase target gates remain open. **The fix-bearing vendor Wi-Fi bundle is integrated in the validated IDF 5.5.3 candidate, not radio-hardware closure**: the [update plan](docs/wifi_security_update_plan.md) retains target gates and unchanged PMF/WPA3; **Phase 9 is not complete or production-ready**. The user will validate Phase 9 as a whole; no 9D device validation or license clearance is claimed. See [security hardening](docs/security_hardening.md) for scope, operational profiles, and validation gates.
Latest supplied parent integration: `pio run` **PASS**, **94,340 B linked RAM / 1,768,901 B flash** (+200 B flash); **seven focused suites PASS**. See [exact counts and evidence limits](docs/security_hardening.md#ordering-hostbuild-evidence--2026-09-16). Linked size and host passes are not target headroom or release acceptance. Latest supplied parent integration: **fresh isolated IDF 5.5.3 application build PASS**, **95,552 B linked RAM / 1,749,493 B flash** (**+1,212 B RAM / 19,408 B flash** versus historical 94,340 / 1,768,901 B). Explicit-candidate Phase 9 validation with interop and web performance **PASS 24/24**. Default root `pio run` timed out at **200 seconds during installation, before compilation**—not a normal root build PASS. See [candidate integration](docs/idf_candidate_integration.md) and [exact command/snapshot evidence](docs/phase9_validation.md#current-candidate-execution--2026-09-18). Linked size and host passes are not target headroom or release acceptance.
### Browser administration ### Browser administration
@@ -82,7 +82,7 @@ pio device monitor -b 115200
pio run pio run
``` ```
The build requires the reviewed ESP-IDF 5.5.0 and pinned component sources. It generates audited security corrections under `.pio/build/` without changing the installed SDK/managed components; changed source hashes fail configuration rather than silently dropping a fix. See the [library review and upgrade contract](docs/security_library_review.md). Do not edit generated corrections or update hashes without reviewing the new source. The root configuration pins PlatformIO espressif32 **6.13.0**, ESP-IDF **5.5.3** (`framework-espidf@3.50503.0`), and Xtensa/RISC-V toolchains **14.2.0+20251107**, with pinned component sources. Exact version pins do not establish a complete immutable build closure. Nine C overrides plus one forced header retain the reviewed corrections, including five signed WS receive-size checks; see the [semantic rebase review](docs/idf_553_rebase_review.md). It generates audited security corrections under `.pio/build/` without changing the installed SDK/managed components; changed source hashes fail configuration rather than silently dropping a fix. See the [library review and upgrade contract](docs/security_library_review.md). Do not edit generated corrections or update hashes without reviewing the new source.
## Upload and monitor ## Upload and monitor
+1 -1
View File
@@ -58,7 +58,7 @@ dependencies:
idf: idf:
source: source:
type: idf type: idf
version: 5.5.0 version: 5.5.3
wolfssl/wolfssh: wolfssl/wolfssh:
component_hash: 24d623360c07374a90b1ade8d1218b24bb7b661fd51ace9135dccf510a338927 component_hash: 24d623360c07374a90b1ade8d1218b24bb7b661fd51ace9135dccf510a338927
dependencies: [] dependencies: []
+2 -2
View File
@@ -78,7 +78,7 @@ UART RX -> serial-service RX stream -> broker task
The broker drains serial input even with no clients. A full client output stream drops only that client's copy and updates drop counters; it does not block UART reception or other clients. The broker drains serial input even with no clients. A full client output stream drops only that client's copy and updates drop counters; it does not block UART reception or other clients.
Binary serial WebSocket output uses the IDF-5.5.0-pinned `web_httpd_ws_send_binary` adapter: one owner-only session-override send of a bounded header+payload copy (516 bytes of local scratch, 512-byte payload). Existing generation validation and one outstanding work item per slot remain required. Non-full sends immediately replace that session's send override with a reject-only guard, mark it closing and shut down the socket before deferred cleanup; this prevents automatic SDK control replies from reentering TLS after incomplete output. HTTPD retains TLS destruction ownership. Text/control and admin output retain the SDK sender. One send call does not imply one TLS record, packet or peer receipt; the user signed off drop-free230400-baud full-client-mix operation at160MHz. Runtime stack-margin validation remains a separate follow-up; evidence and acceptance scope are in `current-state.md`. Binary serial WebSocket output uses the IDF-5.5.3-pinned `web_httpd_ws_send_binary` adapter: one owner-only session-override send of a bounded header+payload copy (516 bytes of local scratch, 512-byte payload). Existing generation validation and one outstanding work item per slot remain required. Non-full sends immediately replace that session's send override with a reject-only guard, mark it closing and shut down the socket before deferred cleanup; this prevents automatic SDK control replies from reentering TLS after incomplete output. HTTPD retains TLS destruction ownership. Text/control and admin output retain the SDK sender. One send call does not imply one TLS record, packet or peer receipt; the user signed off drop-free230400-baud full-client-mix operation at160MHz. Runtime stack-margin validation remains a separate follow-up; evidence and acceptance scope are in `current-state.md`.
Active-client counter snapshots expose ID/type/pending/HWM/UART/queued/read/dropped. HWM is maintained independently of web tracing and counter clear seeds current occupancy. Read means transport handoff, not peer receipt. Disconnect removes the client row while global totals retain traffic and unread-output discards. Independent default-disabled web performance capture uses two fixed slot records and nonwrapping epoch/generation fences; toggles fence in-flight samples, disable freezes aggregates, clear preserves enable state. Binary-only timestamps bound reservation-entry to callback-entry before locking, synchronous send calls and completion to broker-read return; idle and intervening work prevent scheduler-only/backlog-at-completion claims. No new instrumentation allocations or scheduling/buffer changes. Exact fields, overhead and capture contracts: [throughput diagnostics](../web_throughput_diagnostics.md). Active-client counter snapshots expose ID/type/pending/HWM/UART/queued/read/dropped. HWM is maintained independently of web tracing and counter clear seeds current occupancy. Read means transport handoff, not peer receipt. Disconnect removes the client row while global totals retain traffic and unread-output discards. Independent default-disabled web performance capture uses two fixed slot records and nonwrapping epoch/generation fences; toggles fence in-flight samples, disable freezes aggregates, clear preserves enable state. Binary-only timestamps bound reservation-entry to callback-entry before locking, synchronous send calls and completion to broker-read return; idle and intervening work prevent scheduler-only/backlog-at-completion claims. No new instrumentation allocations or scheduling/buffer changes. Exact fields, overhead and capture contracts: [throughput diagnostics](../web_throughput_diagnostics.md).
@@ -108,7 +108,7 @@ TinyUSB callbacks enqueue/copy data and state; the transport task owns broker li
Cookie login/logout replaces Basic/cache. Digest-only records carry copied principals, CSRF state, absolute expiry and nonreused originating-session IDs. Strict same-origin/CSRF mutations and session/principal checks gate admission; logout invalidates its session before transport cleanup, account mutations invalidate only the affected account, and ongoing currentness is authoritative. Authentication initialization failure gates HTTPS; failed start/accepted stop wipes records. A non-consuming quota/epoch check rejects exhausted login requests before body receive; verification reservation remains post-parse and only that reservation charges the existing fixed window. RNG/SHA/database calls run outside short spinlocks with post-call epoch/identity revalidation. [Authentication contract](../web_administration.md#authentication-and-admission). Cookie login/logout replaces Basic/cache. Digest-only records carry copied principals, CSRF state, absolute expiry and nonreused originating-session IDs. Strict same-origin/CSRF mutations and session/principal checks gate admission; logout invalidates its session before transport cleanup, account mutations invalidate only the affected account, and ongoing currentness is authoritative. Authentication initialization failure gates HTTPS; failed start/accepted stop wipes records. A non-consuming quota/epoch check rejects exhausted login requests before body receive; verification reservation remains post-parse and only that reservation charges the existing fixed window. RNG/SHA/database calls run outside short spinlocks with post-call epoch/identity revalidation. [Authentication contract](../web_administration.md#authentication-and-admission).
`web_httpd_adapter` is the sole private IDF 5.5.0 boundary for duplicate headers, admission-before-101, consumed-scratch wiping, staged optional URI registration, combined binary sends and owner-only idle sweeps. Re-audit its version guard on SDK upgrades. HTTPD debug logging must not expose headers/tickets. `web_diagnostics` independently observes public post-TLS callbacks using six metadata records and a default-disabled 32-event ring; it cannot see preaccept/in-progress/failed TLS. [Admission diagnostics](../web_admission_diagnostics.md). `web_httpd_adapter` is the sole private IDF 5.5.3 boundary for duplicate headers, admission-before-101, consumed-scratch wiping, staged optional URI registration, combined binary sends and owner-only idle sweeps. Re-audit its version guard on SDK upgrades. The 5.5.3 rebase retains private-layout/ownership contracts and adds a pinned WS receive correction so negative fixed-header reads cannot pass unsigned length comparisons; see [rebase evidence](../idf_553_rebase_review.md). HTTPD debug logging must not expose headers/tickets. `web_diagnostics` independently observes public post-TLS callbacks using six metadata records and a default-disabled 32-event ring; it cannot see preaccept/in-progress/failed TLS. [Admission diagnostics](../web_admission_diagnostics.md).
`web_httpd_idle` uses one one-second timer, six rows and at most one queued owner probe. Current-owner shutdown follows 15 seconds of observed ordinary idle, exempting actual WebSockets/async/pending input. Owner delays prevent hard timeout guarantees; accepted-but-lost work stays reserved until successful destruction, failed stop retains ownership. [Idle lifecycle contract](../https_idle_cleanup.md). `web_httpd_idle` uses one one-second timer, six rows and at most one queued owner probe. Current-owner shutdown follows 15 seconds of observed ordinary idle, exempting actual WebSockets/async/pending input. Owner delays prevent hard timeout guarantees; accepted-but-lost work stays reserved until successful destruction, failed stop retains ownership. [Idle lifecycle contract](../https_idle_cleanup.md).
+5 -5
View File
@@ -20,19 +20,19 @@ This is a semantic map, not a complete file inventory. Start here, then read the
## Source-pinned dependency corrections (Phases 9C9D) ## Source-pinned dependency corrections (Phases 9C9D)
- Files: root `CMakeLists.txt` (after `project()`), `cmake/security_overrides.cmake`, `tools/security_overrides.py`; tests: `tests/sdk_security_overrides/run.py --build-dir .pio/build/esp32-s3-devkitc-1-n16r8`. - Files: root `CMakeLists.txt` (after `project()`), `cmake/security_overrides.cmake`, `tools/security_overrides.py`; tests: `tests/sdk_security_overrides/run.py --build-dir .pio/build/esp32-s3-devkitc-1-n16r8`.
- Build input is the exact-hash original **plus checked-in edits**, not installed source alone. Generated copies replace eight target C sources plus one PUBLIC forced header without modifying SDK/managed components: HTTPS cleanup/private-key release, HTTPD scratch lifetime/null first read, ESP-TLS server-only protocol list, wolfSSH password bounds/payload wiping plus bounded IGNORE/service/string parsing, channel-window overflow rejection and ECC/Ed25519 label/exact-signature framing, DHCP option bounds (CVE-2026-45160), TLS 1.2 EMS error propagation (CVE-2026-50581), and X.509 OID allocation failure (CVE-2026-34874). The mbedTLS entries explicitly select validated nested `mbedtls`/`mbedx509` targets, not the component wrapper. Original notices and compile properties retained; all outputs carry the 2026-09-15 baseline modification notice and wolfSSH outputs add the 2026-09-16 ordering/provenance notice; source/hash/target ambiguity fails configuration. Never hand-edit generated copies or silently repin. - Build input is the exact-hash original **plus checked-in edits**, not installed source alone. Generated copies replace nine target C sources plus one PUBLIC forced header without modifying SDK/managed components: HTTPS cleanup/private-key release, HTTPD scratch lifetime/null first read, ESP-TLS server-only protocol list, wolfSSH password bounds/payload wiping plus bounded IGNORE/service/string parsing, channel-window overflow rejection and ECC/Ed25519 label/exact-signature framing, DHCP option bounds (CVE-2026-45160), TLS 1.2 EMS error propagation (CVE-2026-50581), X.509 OID allocation failure (CVE-2026-34874), and five signed WS fixed-header size comparisons on IDF 5.5.3 (982 cases / 10 mutation checks against generated code). The mbedTLS entries explicitly select validated nested `mbedtls`/`mbedx509` targets, not the component wrapper. Original notices and compile properties retained; all outputs carry the 2026-09-15 baseline modification notice and wolfSSH outputs add the 2026-09-16 ordering/provenance notice; source/hash/target ambiguity fails configuration. Never hand-edit generated copies or silently repin.
- Xtensa crypto policy: root `CMakeLists.txt` sets `CURVE25519_SMALL`/`ED25519_SMALL` before component parsing; `cmake/wolf_crypto_policy.cmake` PUBLIC-propagates `cmake/wolf_crypto_policy.h` to wolfSSL consumers. PUBLIC `WOLFSSL_VALIDATE_ECC_IMPORT` and `WOLFSSL_ECDHX_SHARED_NOT_ZERO` enable existing P-256 import and X25519 all-zero-result checks. The resolved-settings guard requires these checks and both small implementations, rejects reviewed ECC validator-disabling/hardware-stub configurations, X25519 blinding and unreviewed Curve448/Ed448 enablement. Tests: `tests/wolf_crypto_policy/run.py` (strict actual production flags, real vendor crypto/ASN vectors and independently specified exact source deltas; candidate injection is not production evidence). [Key-validation review](../ssh_key_validation_review.md) records effective flags, caller/API limits and unmeasured validation cost. [Wolf review](../wolf_security_review.md) distinguishes implemented restricted ordering/crypto/parser mitigations from pending target gates and explicit dormant-profile exclusions; [IDF review](../idf_security_review.md) records three backports, finite six-finding applicability completion and first-page feature exclusions, not exhaustive advisory safety. - Xtensa crypto policy: root `CMakeLists.txt` sets `CURVE25519_SMALL`/`ED25519_SMALL` before component parsing; `cmake/wolf_crypto_policy.cmake` PUBLIC-propagates `cmake/wolf_crypto_policy.h` to wolfSSL consumers. PUBLIC `WOLFSSL_VALIDATE_ECC_IMPORT` and `WOLFSSL_ECDHX_SHARED_NOT_ZERO` enable existing P-256 import and X25519 all-zero-result checks. The resolved-settings guard requires these checks and both small implementations, rejects reviewed ECC validator-disabling/hardware-stub configurations, X25519 blinding and unreviewed Curve448/Ed448 enablement. Tests: `tests/wolf_crypto_policy/run.py` (strict actual production flags, real vendor crypto/ASN vectors and independently specified exact source deltas; candidate injection is not production evidence). [Key-validation review](../ssh_key_validation_review.md) records effective flags, caller/API limits and unmeasured validation cost. [Wolf review](../wolf_security_review.md) distinguishes implemented restricted ordering/crypto/parser mitigations from pending target gates and explicit dormant-profile exclusions; [IDF review](../idf_security_review.md) records three backports, finite six-finding applicability completion and first-page feature exclusions, not exhaustive advisory safety.
- Parser tests: `tests/wolfssh_parser_contract/run.py`, [scope and exclusions](../../tests/wolfssh_parser_contract/README.md): 3,258 base cases per each of two stack modes plus five channel profiles in both modes (2,737/profile/mode for TERM, TERM+SHELL, TERM+SHELL+AGENT; 2,735 for neither/SHELL-only); 11 base + 18 name/length + 2 application-gate mutations rejected. Guard pages/UBSan and crypto doubles test parsing/gating, not arithmetic. [Finite review](../ssh_parser_remaining_review.md) closes exact CHANNEL_FAILURE recipient, ssh-userauth service and nine channel-name dispatch checks; PR899 client skips remain unchanged behind role/ordering gates, forwarding disabled, generic caller trace dispositioned but APIs unpatched. Unknown/trailing behavior is retained; not exhaustive safety. Ordering has its separate suite below. - Parser tests: `tests/wolfssh_parser_contract/run.py`, [scope and exclusions](../../tests/wolfssh_parser_contract/README.md): 3,258 base cases per each of two stack modes plus five channel profiles in both modes (2,737/profile/mode for TERM, TERM+SHELL, TERM+SHELL+AGENT; 2,735 for neither/SHELL-only); 11 base + 18 name/length + 2 application-gate mutations rejected. Guard pages/UBSan and crypto doubles test parsing/gating, not arithmetic. [Finite review](../ssh_parser_remaining_review.md) closes exact CHANNEL_FAILURE recipient, ssh-userauth service and nine channel-name dispatch checks; PR899 client skips remain unchanged behind role/ordering gates, forwarding disabled, generic caller trace dispositioned but APIs unpatched. Unknown/trailing behavior is retained; not exhaustive safety. Ordering has its separate suite below.
- Latest supplied parent `pio run` PASS: **94,340 B linked RAM / 1,768,901 B flash (+200 B versus 1,768,701 B)**. Final parent **all seven suites PASS**: ordering `--interop` (8,028 checks / seven rejected mutations / 12 exact-256-KiB sessions, clean channel close and transport EOF), SDK overrides `--build-dir .pio/build/esp32-s3-devkitc-1-n16r8`, auth (135), protocol, strict crypto, notices (30), and parser (3,258 × two modes plus channel profiles; 11 + 18 + 2 rejected mutations). Interop used unsandboxed approval only for local AF_UNIX sockets; no remote network/device operation. Review's misplaced EOF guard is corrected in `SendChannelEof` before lookup/serialization/state mutation; follow-up verification found no scoped blocker. Not target/runtime-reserve evidence. - Historical IDF 5.5.0 supplied parent `pio run` PASS: **94,340 B linked RAM / 1,768,901 B flash (+200 B versus 1,768,701 B)**. Final parent **all seven suites PASS**: ordering `--interop` (8,028 checks / seven rejected mutations / 12 exact-256-KiB sessions, clean channel close and transport EOF), SDK overrides `--build-dir .pio/build/esp32-s3-devkitc-1-n16r8`, auth (135), protocol, strict crypto, notices (30), and parser (3,258 × two modes plus channel profiles; 11 + 18 + 2 rejected mutations). Interop used unsandboxed approval only for local AF_UNIX sockets; no remote network/device operation. Review's misplaced EOF guard is corrected in `SendChannelEof` before lookup/serialization/state mutation; follow-up verification found no scoped blocker. Not target/runtime-reserve evidence.
- Restricted **CVE-2025-14942** ordering correction implemented: `tools/wolfssh_order/delta.json` supplies exact executable edits to `internal.c`, `ssh.c` and `internal.h`; [README](../../tools/wolfssh_order/README.md) records PR793/819/840/855/921 prerequisite disposition, and `provenance.json` pins archived patch hashes/commit IDs. Existing X25519/P-256 KEX only; independent SELF/PEER bits, expected-message/auth gates and exactly-once queued NEWKEYS under WANT_WRITE. **EXT_INFO intentionally disabled, no `server-sig-algs`; `extInfoSent` stays zero.** No full upstream backport or dependency upgrade. CMake propagates the generated ABI header BEFORE PUBLIC plus PUBLIC forced include with stale-header rejection; joined `-include/path` flags preserve ordering/crypto guards through PlatformIO sorting/deduplication. - Restricted **CVE-2025-14942** ordering correction implemented: `tools/wolfssh_order/delta.json` supplies exact executable edits to `internal.c`, `ssh.c` and `internal.h`; [README](../../tools/wolfssh_order/README.md) records PR793/819/840/855/921 prerequisite disposition, and `provenance.json` pins archived patch hashes/commit IDs. Existing X25519/P-256 KEX only; independent SELF/PEER bits, expected-message/auth gates and exactly-once queued NEWKEYS under WANT_WRITE. **EXT_INFO intentionally disabled, no `server-sig-algs`; `extInfoSent` stays zero.** No full upstream backport or dependency upgrade. CMake propagates the generated ABI header BEFORE PUBLIC plus PUBLIC forced include with stale-header rejection; joined `-include/path` flags preserve ordering/crypto guards through PlatformIO sorting/deduplication.
- Ordering tests: [tests/wolfssh_order_contract](../../tests/wolfssh_order_contract/README.md), full generated C/real wolfCrypt, **8,028 checks / seven rejected mutations**; `pio_adapter.py` validates installed PlatformIO/SCons flags with a real Xtensa consumer. Initial interop failed a harness close race despite early PASS output; fixed harness awaits peer close and transport EOF and independently reaps the server via fd-passing proxy. Final agent `--interop --interop-repeat 3`: **36/36 sessions**, exact **256 KiB** each, **two or ten** key exchanges and clean client/server exits. [Remaining gates](../wolf_security_review.md#restricted-ordering-correction-and-remaining-gates): target cleanup during rekey, no-EXT_INFO client compatibility, resource/timing and whole-phase validation; no device operation or phase sign-off. - Ordering tests: [tests/wolfssh_order_contract](../../tests/wolfssh_order_contract/README.md), full generated C/real wolfCrypt, **8,028 checks / seven rejected mutations**; `pio_adapter.py` validates installed PlatformIO/SCons flags with a real Xtensa consumer. Initial interop failed a harness close race despite early PASS output; fixed harness awaits peer close and transport EOF and independently reaps the server via fd-passing proxy. Final agent `--interop --interop-repeat 3`: **36/36 sessions**, exact **256 KiB** each, **two or ten** key exchanges and clean client/server exits. [Remaining gates](../wolf_security_review.md#restricted-ordering-correction-and-remaining-gates): target cleanup during rekey, no-EXT_INFO client compatibility, resource/timing and whole-phase validation; no device operation or phase sign-off.
- [Finite dependency advisory snapshot](../dependency_advisory_coverage.md): all 22 IDF index entries across three pages plus named managed/browser channels accounted for; declared search complete, not exhaustive clearance. **PMF-capable SoftAP security blocker remains unfixed**; [Wi-Fi update plan](../wifi_security_update_plan.md) requires coherent vendor framework/toolchain/source/header/binary ABI bundles and override revalidation, not PMF/WPA3 weakening. Residual applicability questions and target gates remain. - [Finite dependency advisory snapshot](../dependency_advisory_coverage.md): all 22 IDF index entries across three pages plus named managed/browser channels accounted for; declared search complete, not exhaustive clearance. **Fix-bearing Wi-Fi vendor bundle integrated in the validated IDF 5.5.3 candidate; radio-hardware closure pending**; [Wi-Fi update plan](../wifi_security_update_plan.md) requires coherent vendor framework/toolchain/source/header/binary ABI bundles and override revalidation, not PMF/WPA3 weakening. Residual applicability questions and target gates remain.
- [Bounded Phase 9 runner](../../tools/validate_phase9.py), [usage/evidence](../phase9_validation.md), fixtures `tests/phase9_validation/run.py`: 23 default commands; build and OpenSSH interop are independent opt-ins. After the owning agent fixed the stale console-boundary anchor, final supplied parent `CCACHE_DISABLE=1 python3 -B tools/validate_phase9.py` **PASS 23/23** and orchestrator fixtures **PASS 10 tests**. No build/interop this turn; historical build/interop evidence above is separate, not phase acceptance. - [Bounded Phase 9 runner](../../tools/validate_phase9.py), [usage/evidence](../phase9_validation.md), fixtures `tests/phase9_validation/run.py`: 23 default commands; build and OpenSSH interop are independent opt-ins. After the owning agent fixed the stale console-boundary anchor, final supplied parent `CCACHE_DISABLE=1 python3 -B tools/validate_phase9.py` **PASS 23/23** and orchestrator fixtures **PASS 10 tests**. That default run is historical. Current supplied parent explicit-candidate validation with `--interop --web-performance` **PASS 24/24**; [exact command and pre/post source equality](../phase9_validation.md#current-candidate-execution--2026-09-18). Fresh isolated `app-validated` build PASS: **95,552 B RAM / 1,749,493 B flash (+1,212 / 19,408 B)**. Default root build timed out at 200 seconds during installation before compilation, not a root build PASS. Root now pins platform 6.13.0 / IDF 5.5.3 / both toolchains 14.2.0+20251107; [integration](../idf_candidate_integration.md), [semantic rebase and resolved historical WS blockers](../idf_553_rebase_review.md). Version pins are not complete immutable dependency closure or phase acceptance.
- Policy/evidence/limits: [library review](../security_library_review.md), [Phase 9C](../security_hardening.md#9c-library-cleanup-and-protocol-policy). Source-contract tests must locate and verify actual generated compilation inputs, not assume original vendor paths. - Policy/evidence/limits: [library review](../security_library_review.md), [Phase 9C](../security_hardening.md#9c-library-cleanup-and-protocol-policy). Source-contract tests must locate and verify actual generated compilation inputs, not assume original vendor paths.
## Offline release notice assembly ## Offline release notice assembly
- [Procedure and release gates](../release_packaging.md), [tool](../../tools/release_notices.py), catalog `third_party/release-notices/inputs.json`, scope/TLSF provenance under `third_party/release-notices/`; tests: `tests/release_notices/run.py` (supplied agent 36 fixture tests PASS). 75 explicit hash/size-pinned inputs; no recursive collection, fetch, build, asset regeneration or device access; output must be fresh and outside input roots. - [Procedure and release gates](../release_packaging.md), [tool](../../tools/release_notices.py), catalog `third_party/release-notices/inputs.json`, scope/TLSF provenance under `third_party/release-notices/`; tests: `tests/release_notices/run.py` (supplied agent 36 fixture tests PASS). 75 explicit hash/size-pinned inputs (nine IDF/toolchain notice entries semantically rebased for the candidate; the other 66 unchanged); no recursive collection, fetch, build, asset regeneration or device access; output must be fresh and outside input roots.
- Supplied agent evidence: two actual 75-input bundles verified deterministic, each **77 files / 4,433,930 bytes**; no reassembly here. [Exact retained SVG provenance](../icon_provenance.md) resolves official 7.4.47 sources at `9e04201d4557e729822fb57f62a316c3dea1d4a8` (Google USB, Simran Wi-Fi); manual bitmap derivation and distinct mockup Wi-Fi path remain unresolved. The catalog pins `docs/icon_provenance.md`, not this code map or the edited overview docs; coordinate changes to pinned evidence, never silently repin. Recipient delivery, corresponding source, legal/radio-blob basis and whole-phase target gates remain open. A notice manifest is not a source archive, complete SBOM or release clearance. - Supplied agent evidence: two actual 75-input bundles verified deterministic, each **77 files / 4,433,930 bytes**; no reassembly here. [Exact retained SVG provenance](../icon_provenance.md) resolves official 7.4.47 sources at `9e04201d4557e729822fb57f62a316c3dea1d4a8` (Google USB, Simran Wi-Fi); manual bitmap derivation and distinct mockup Wi-Fi path remain unresolved. The catalog pins `docs/icon_provenance.md`, not this code map or the edited overview docs; coordinate changes to pinned evidence, never silently repin. Recipient delivery, corresponding source, legal/radio-blob basis and whole-phase target gates remain open. A notice manifest is not a source archive, complete SBOM or release clearance.
## Secure randomness ## Secure randomness
+9
View File
@@ -2,6 +2,15 @@
Working memory, not an implementation timeline. Source is authoritative; begin with [code map](code-map.md), then [architecture](architecture.md) and [decisions](design-decisions.md). Working memory, not an implementation timeline. Source is authoritative; begin with [code map](code-map.md), then [architecture](architecture.md) and [decisions](design-decisions.md).
## Phase 9D continuation — IDF 5.5.3 integration — 2026-09-18
- Initial working tree already contained platform6.13/framework3.50503/toolchains14.2.0+20251107 migration, exact version guards/hash rebases and new httpd_ws override; preserved/audited rather than overwrote. Complete official packages archived/verified under .pio/idf-candidate-5.5.3; all WiFi/PHY/coex/supplicant trees match pinned package. PMF/WPA3 unchanged. Vendor radio correction integrated in candidate, not hardware exploit/closure proof.
- Existing eightC+header corrections all retained after semantic audit; new ninth C override corrects five WS signed-receive/unsigned-size comparisons introduced upstream. Dedicated982guard/canary cases plus fivecompile+fivebehavior mutations PASS. HTTPD private layout/owner and heap extent contracts re-audited5.5.3; no weakening guards. Root dependencies.lock updated only IDF5.5.0→5.5.3 to match actual resolver result; six managed dependencies unchanged.
- Default root pio run timed out200s during sharedpackage installation before compilation; no normalrootbuildPASS claim/retry. Separate fresh app-validated stage uses complete verified archives and isolatedcore. Agent buildPASS50.51s **95,552 B linked RAM / 1,749,493 B flash** (+1,212RAM/19,408flash vs old). Staging fresh-only explicitdestination avoids prior app overwrite; manifest includes transformedconfig hashes and records sole resolver lock change. All ninegeneratedC+PUBLICheader actualregistration verified; WS vendororiginal absent.
- Final parent tools/validate_phase9.py with --build-dir .pio/idf-candidate-5.5.3/app-validated/.pio/build/esp32-s3-devkitc-1-n16r8 --idf-path .pio/idf-candidate-5.5.3/core/packages/framework-espidf --platformio-core-dir .pio/idf-candidate-5.5.3/core --interop --web-performance **24/24PASS**. Pre/post3237sourceinputs equal hash3a1af78c15cfdd02da1055a8957b4086f9018862b7aa1c7c52fd2401a1a0a031. Earlier5webcookie failures were stale5.5.0 fixture updated5.5.3; allpassnow. Parent helper5/runner16fixturesPASS. Independent recheck noactionableblocker, reran WS/performance/runner, verified archive/linkpaths/sourceequality/rootlock. No device validation.
- Notice catalog semanticallyrebased9entries, other66unchanged;75inputs36testsPASS twoactualbundles77files4433930bytes. Evidence/docs updated with candidatenotrootbuild distinction and historical baselines. No full environmentclosure or legalclearance claim.
- **Next:** normal package installation/build completion remains unverified after timeout; ask user before retrying longer. Isolated source/currentconfig technically validated; wholePhase9 radio/resource/recovery/noEXTclient/abuse/operations targetgates stillpending, release source/notice/legal obligations open. Candidate5.5.3 is newest available official PlatformIO5.5 package verified, not latest upstream maintenance or alladvisoriesclosed. No upload/erase/assets/secureboot/encryptedNVS changes or commits.
## Phase 9D continuation — broader snapshot / reproducible validation — 2026-09-16 ## Phase 9D continuation — broader snapshot / reproducible validation — 2026-09-16
- Initial Git clean. `docs/dependency_advisory_coverage.md` completes finite dated search of all22official IDF advisories/3pages plus specified managed/browser channels. **New current-profile blocker:** PMF-capable mixed WPA2/WPA3 SoftAP matches vendor management-frame memory-corruption path; installed7S3WiFi archives match original5.5. No exploit demonstrated. `docs/wifi_security_update_plan.md` verifies fix/release bundle differences and both callback uint8_t*→size_t* ABI changes. Verified reference IDF5.5.3 commit2c211b236707889e8400c4dc5644dd5c4ee071e0 contains fix; not deployment approval or automatic recommendation over newer releases. Do not C-only patch/swap one archive/disable PMF as guessed mitigation. Next coherent framework/toolchain/source/header/radio update evaluation with audited override rebase; PHY/coexistence/privateABI compatibility unresolved. Direct SDK AES-DMA/PSRAM applicability remains explicit review gap. - Initial Git clean. `docs/dependency_advisory_coverage.md` completes finite dated search of all22official IDF advisories/3pages plus specified managed/browser channels. **New current-profile blocker:** PMF-capable mixed WPA2/WPA3 SoftAP matches vendor management-frame memory-corruption path; installed7S3WiFi archives match original5.5. No exploit demonstrated. `docs/wifi_security_update_plan.md` verifies fix/release bundle differences and both callback uint8_t*→size_t* ABI changes. Verified reference IDF5.5.3 commit2c211b236707889e8400c4dc5644dd5c4ee071e0 contains fix; not deployment approval or automatic recommendation over newer releases. Do not C-only patch/swap one archive/disable PMF as guessed mitigation. Next coherent framework/toolchain/source/header/radio update evaluation with audited override rebase; PHY/coexistence/privateABI compatibility unresolved. Direct SDK AES-DMA/PSRAM applicability remains explicit review gap.
+8 -7
View File
@@ -1,10 +1,10 @@
# Dependency license inventory — Phase 9D # Dependency license inventory — Phase 9D
## Implementation addendum — 2026-09-16 ## Implementation addendum — updated 2026-09-18
**Missing generated modification/date notices: resolved; current count is eight C sources plus one header.** `tools/security_overrides.py` prepends the baseline project modification notice dated **2026-09-15**, identifying the editable generator and warning against editing derived copies. The wolfSSH `internal.c`, added `ssh.c` and generated `wolfssh/internal.h` also receive a prominent **2026-09-16** ordering-profile/provenance notice. Exact original hashes and exact-once edits remain required; upstream notices are retained. The header is a PUBLIC forced build input, not a ninth C source. Both new mbedTLS copies retain their original Mbed TLS Contributors copyright and **`Apache-2.0 OR GPL-2.0-or-later`** SPDX header; the Apache-2.0 selection in this inventory is unchanged. This resolves the narrow notice-generation finding, **not release packaging or license clearance**. **Missing generated modification/date notices: resolved; current count is nine C sources plus one header.** `tools/security_overrides.py` prepends the baseline project modification notice dated **2026-09-15**, identifying the editable generator and warning against editing derived copies. The wolfSSH `internal.c`, added `ssh.c` and generated `wolfssh/internal.h` also receive a prominent **2026-09-16** ordering-profile/provenance notice. Exact original hashes and exact-once edits remain required; upstream notices are retained. The header is a PUBLIC forced build input, not a C source. Both new mbedTLS copies retain their original Mbed TLS Contributors copyright and **`Apache-2.0 OR GPL-2.0-or-later`** SPDX header; the Apache-2.0 selection in this inventory is unchanged. This resolves the narrow notice-generation finding, **not release packaging or license clearance**.
Offline notice assembly tooling is implemented; actual firmware/device/browser delivery, radio-blob corresponding-source/exception questions, preferred-source packaging, wolfSSH license-package discrepancy, manual bitmap/mockup provenance limits and release-specific runtime/bootloader review remain open; [exact retained SVG provenance](icon_provenance.md) is resolved. [Offline notice assembly](release_packaging.md) is implemented by [tools/release_notices.py](../tools/release_notices.py): 75 mandatory hash/size-pinned inputs, deterministic bounded outputs, fail-closed preflight and no overwrite/fetch/build/device access. Supplied agent evidence: **36 notice fixture tests PASS; two actual bundles verified deterministic, each 77 files / 4,433,930 bytes**. This includes retained SVGs and [exact icon provenance](icon_provenance.md), not corresponding-source delivery or legal clearance. These are agent measurements, not a parent rebuild or reassembly in this documentation turn. Include `cmake/wolf_crypto_policy.{cmake,h}` and the ordering inputs described below with the reproducible build/source package. No upgrades, asset regeneration or device operations were performed for this update. Offline notice assembly tooling is implemented; actual firmware/device/browser delivery, radio-blob corresponding-source/exception questions, preferred-source packaging, wolfSSH license-package discrepancy, manual bitmap/mockup provenance limits and release-specific runtime/bootloader review remain open; [exact retained SVG provenance](icon_provenance.md) is resolved. [Offline notice assembly](release_packaging.md) is implemented by [tools/release_notices.py](../tools/release_notices.py): 75 mandatory hash/size-pinned inputs (nine IDF/toolchain notice entries semantically rebased for the candidate; the other 66 unchanged), deterministic bounded outputs, fail-closed preflight and no overwrite/fetch/build/device access. Supplied agent evidence: **36 notice fixture tests PASS; two actual bundles verified deterministic, each 77 files / 4,433,930 bytes**. This includes retained SVGs and [exact icon provenance](icon_provenance.md), not corresponding-source delivery or legal clearance. These are agent measurements, not a parent rebuild or reassembly in this documentation turn. Include `cmake/wolf_crypto_policy.{cmake,h}` and the ordering inputs described below with the reproducible build/source package. The IDF 5.5.3 migration is now integrated in the isolated validated candidate; this documentation update performs no upgrade, asset regeneration or device operation. Root pins select platform 6.13.0 / framework 3.50503.0 / both toolchains 14.2.0+20251107; see [integration and build/closure limits](idf_candidate_integration.md). The detailed 5.5.0 SDK/runtime inventory below remains historical, not a newly exhaustive 5.5.3 license audit.
## Historical inventory baseline — 2026-09-15 ## Historical inventory baseline — 2026-09-15
@@ -99,15 +99,16 @@ For conveyance, review GPLv3 §§1, 46, 7 and 10 in [LICENSE](../LICENSE):
1. Supply required license/copyright/warranty notices and use a §6-compliant corresponding-source delivery method for binaries. A dependency name, registry URL, generic upstream source link, or this inventory alone is not corresponding source or a compliant written offer. 1. Supply required license/copyright/warranty notices and use a §6-compliant corresponding-source delivery method for binaries. A dependency name, registry URL, generic upstream source link, or this inventory alone is not corresponding source or a compliant written offer.
2. Preserve exact application source, lock/manifests, relevant SDK and managed source, configuration/build/link inputs, asset preferred sources and generation scripts. Include the scripts controlling compilation/installation. Generic unmodified build tools may fall outside corresponding source, but record precise versions needed for reproduction; if distributing those tools themselves, satisfy their separate licenses. 2. Preserve exact application source, lock/manifests, relevant SDK and managed source, configuration/build/link inputs, asset preferred sources and generation scripts. Include the scripts controlling compilation/installation. Generic unmodified build tools may fall outside corresponding source, but record precise versions needed for reproduction; if distributing those tools themselves, satisfy their separate licenses.
3. The Phase 9C9D firmware compiles **pinned originals plus project edits**. Include `tools/security_overrides.py`, `cmake/security_overrides.cmake`, root CMake integration, `cmake/wolf_crypto_policy.{cmake,h}`, the complete `tools/wolfssh_order/` inputs, original hash-matching C/header sources, and the means to reproduce all eight C copies and the PUBLIC forced header. A stock IDF/wolfSSH tarball alone omits the compiled modifications. Generated copies alone are not a substitute for the preferred editable patch/generator inputs. 3. The Phase 9C9D firmware compiles **pinned originals plus project edits**. Include `tools/security_overrides.py`, `cmake/security_overrides.cmake`, root CMake integration, `cmake/wolf_crypto_policy.{cmake,h}`, the complete `tools/wolfssh_order/` inputs, original hash-matching C/header sources, and the means to reproduce all nine C copies and the PUBLIC forced header. A stock IDF/wolfSSH tarball alone omits the compiled modifications. Generated copies alone are not a substitute for the preferred editable patch/generator inputs.
4. **Resolved; updated 2026-09-16:** all eight generated C files plus one header below retain upstream notices and carry explicit project modification/date notices from the generator (baseline 2026-09-15, plus 2026-09-16 ordering notices on wolfSSH outputs). The historical four-file audit found the notice missing; this narrow defect is repaired. GPLv3 §5(a) calls for a relevant date, and Apache-2.0 §4(b) requires prominent change notices. Continue including notices and reproducible generator inputs in distribution; do not hand-edit generated copies or vendor sources. 4. **Resolved; updated 2026-09-18:** all nine generated C files plus one header below retain upstream notices and carry explicit project modification/date notices from the generator (baseline 2026-09-15, plus 2026-09-16 ordering notices on wolfSSH outputs). The historical four-file audit found the notice missing; this narrow defect is repaired. GPLv3 §5(a) calls for a relevant date, and Apache-2.0 §4(b) requires prominent change notices. Continue including notices and reproducible generator inputs in distribution; do not hand-edit generated copies or vendor sources.
5. If distributing a GPLv3 “User Product,” assess and provide required Installation Information under §6. Do not publish device passwords, private keys, Wi-Fi material, account verifiers, tickets, NVS images, or real credential backups as build/source artifacts. Installation compliance should use appropriate documented procedures, not routine secret disclosure. Secure boot was not enabled or altered here. 5. If distributing a GPLv3 “User Product,” assess and provide required Installation Information under §6. Do not publish device passwords, private keys, Wi-Fi material, account verifiers, tickets, NVS images, or real credential backups as build/source artifacts. Installation compliance should use appropriate documented procedures, not routine secret disclosure. Secure boot was not enabled or altered here.
6. **Prebuilt radio libraries are the principal unresolved compatibility/source risk.** Apache-2.0 permits binary redistribution, but does not itself satisfy GPL corresponding-source obligations for a combined firmware. Establish whether required preferred source is obtainable, or whether a defensible GPL System Library/other exception applies to the actual linked artifacts. Static linkage is not automatically “mere aggregation.” Do not declare distribution cleared or definitely prohibited from this bounded audit. Escalate this before a public binary/device release. 6. **Prebuilt radio libraries are the principal unresolved compatibility/source risk.** Apache-2.0 permits binary redistribution, but does not itself satisfy GPL corresponding-source obligations for a combined firmware. Establish whether required preferred source is obtainable, or whether a defensible GPL System Library/other exception applies to the actual linked artifacts. Static linkage is not automatically “mere aggregation.” Do not declare distribution cleared or definitely prohibited from this bounded audit. Escalate this before a public binary/device release.
Current generated paths and retained licenses (eight C sources plus one header): Current generated paths and retained licenses (nine C sources plus one header). Here `BUILD/` denotes the validated candidate build, `.pio/idf-candidate-5.5.3/app-validated/.pio/build/esp32-s3-devkitc-1-n16r8/`, rather than the historical audit root:
- `BUILD/security_overrides/https_server/https_server.c` — Espressif Apache-2.0. - `BUILD/security_overrides/https_server/https_server.c` — Espressif Apache-2.0.
- `BUILD/security_overrides/httpd_parse/httpd_parse.c` — Espressif Apache-2.0. - `BUILD/security_overrides/httpd_parse/httpd_parse.c` — Espressif Apache-2.0.
- `BUILD/security_overrides/httpd_ws/httpd_ws.c` — Espressif Apache-2.0; five signed fixed-header receive-size comparisons, with generated modification/rebase notices.
- `BUILD/security_overrides/esp_tls_mbedtls/esp_tls_mbedtls.c` — Espressif Apache-2.0. - `BUILD/security_overrides/esp_tls_mbedtls/esp_tls_mbedtls.c` — Espressif Apache-2.0.
- `BUILD/security_overrides/wolfssh_internal/internal.c` — wolfSSH GPL-3.0-or-later header. - `BUILD/security_overrides/wolfssh_internal/internal.c` — wolfSSH GPL-3.0-or-later header.
- `BUILD/security_overrides/wolfssh_ssh/ssh.c` — wolfSSH GPL-3.0-or-later header. - `BUILD/security_overrides/wolfssh_ssh/ssh.c` — wolfSSH GPL-3.0-or-later header.
@@ -141,7 +142,7 @@ Notice assembly is implemented; the remaining release actions below have not bee
| Release blocker to resolve | Document a defensible corresponding-source/exception basis for the **selected radio blobs** and other source-unavailable linked content. This is an unresolved question, not a legal verdict. | | Release blocker to resolve | Document a defensible corresponding-source/exception basis for the **selected radio blobs** and other source-unavailable linked content. This is an unresolved question, not a legal verdict. |
| Assembly tooling implemented; delivery pending | [Offline tool and procedure](release_packaging.md) assemble 75 pinned inputs, retaining GPL/Apache/MIT/BSD texts, mixed SDK/toolchain Newlib and argtable3 notices, GCC exception and a scoped full TLSF rendering. Supplied agent fixture suite: 36 PASS; two actual bundles independently verified deterministic, each 77 files / 4,433,930 bytes. Validate final firmware/device/browser recipient delivery; assembly is not corresponding-source delivery or legal clearance. | | Assembly tooling implemented; delivery pending | [Offline tool and procedure](release_packaging.md) assemble 75 pinned inputs, retaining GPL/Apache/MIT/BSD texts, mixed SDK/toolchain Newlib and argtable3 notices, GCC exception and a scoped full TLSF rendering. Supplied agent fixture suite: 36 PASS; two actual bundles independently verified deterministic, each 77 files / 4,433,930 bytes. Validate final firmware/device/browser recipient delivery; assembly is not corresponding-source delivery or legal clearance. |
| Before distributing icon derivatives | [Exact retained SVG provenance](icon_provenance.md) is resolved at official 7.4.47; full Apache-2.0 text and attribution are retained, with no NOTICE found in the pinned distribution. Preserve manual bitmap preferred sources and resolve the distinct mockup Wi-Fi path/derivation limits. Recipient delivery remains open; do not invent a NOTICE or rasterizer. | | Before distributing icon derivatives | [Exact retained SVG provenance](icon_provenance.md) is resolved at official 7.4.47; full Apache-2.0 text and attribution are retained, with no NOTICE found in the pinned distribution. Preserve manual bitmap preferred sources and resolve the distinct mockup Wi-Fi path/derivation limits. Recipient delivery remains open; do not invent a NOTICE or rasterizer. |
| Resolved in generator; retain in distribution | Prominent modification notices cover **eight C sources plus one header** (baseline **2026-09-15**, wolfSSH ordering **2026-09-16**), with original notices retained. Release packaging must still include them, archived patches/provenance, consolidated delta and reproducible build inputs. | | Resolved in generator; retain in distribution | Prominent modification notices cover **nine C sources plus one header** (baseline **2026-09-15**, wolfSSH ordering **2026-09-16**), with original notices retained. Release packaging must still include them, archived patches/provenance, consolidated delta and reproducible build inputs. |
| Upstream clarification | Record/resolve wolfSSH 1.4.20 package LICENSE/README vs source-header mismatch without editing the managed package in place. GPLv3 is supported by the inspected headers and is also within the package prose's “or later” wording. | | Upstream clarification | Record/resolve wolfSSH 1.4.20 package LICENSE/README vs source-header mismatch without editing the managed package in place. GPLv3 is supported by the inspected headers and is also within the package prose's “or later” wording. |
| Release packaging verification | Archive exact corresponding source/build inputs and required asset preferred sources; validate source delivery/offer and any User Product installation information. Recheck bootloader, runtime objects and aggregation boundaries against the final release map. | | Release packaging verification | Archive exact corresponding source/build inputs and required asset preferred sources; validate source delivery/offer and any User Product installation information. Recheck bootloader, runtime objects and aggregation boundaries against the final release map. |
+190
View File
@@ -0,0 +1,190 @@
# IDF 5.5.3 uncommitted rebase review
Review date: **2026-09-18**. Baseline Git HEAD: `cdc4d4a8df02c2c44180b9b6d17bf6104c9fbcd2`.
**Current disposition: existing protected corrections are semantically retained; the initial stale compiled WS input and missing receive-error regression blockers are resolved by the supplied final fresh-build/24-suite evidence below.** No evidence found that a protected correction should be removed as superseded. This is a bounded rebase review, not advisory recertification, device acceptance or release clearance.
Initial reviewer-owned change: **this document only**. Existing working changes were preserved. No SDK installation, firmware build, upload, erase, asset regeneration or production edit was performed. A concurrent change to `third_party/release-notices/inputs.json` appeared during review; it was left untouched and was not reviewed in that initial pass. The later supplied notice review semantically rebased nine entries, retaining the other 66 unchanged; this does not turn the initial review into a catalog audit.
## Final follow-up — 2026-09-18
Supplied final parent command, run from the repository root:
```sh
CCACHE_DISABLE=1 python3 -B tools/validate_phase9.py --build-dir .pio/idf-candidate-5.5.3/app-validated/.pio/build/esp32-s3-devkitc-1-n16r8 --idf-path .pio/idf-candidate-5.5.3/core/packages/framework-espidf --platformio-core-dir .pio/idf-candidate-5.5.3/core --interop --web-performance
```
**PASS 24/24.** This selects the actual fresh isolated build, SDK and toolchain core; it is not a default-root-build execution. The separate fresh `.pio/idf-candidate-5.5.3/app-validated` build **PASS** reports **95,552 B linked RAM / 1,749,493 B flash**, versus historical **94,340 / 1,768,901 B** (**+1,212 B RAM / 19,408 B flash**). The default parent `pio run` timed out after **200 seconds during installation, before compilation**; no normal root build PASS is claimed.
Pre/post source equality: **3,237 files**, SHA-256 **`3a1af78c15cfdd02da1055a8957b4086f9018862b7aa1c7c52fd2401a1a0a031`**. Actual generated-input registration covers **nine C sources plus one forced header**. WS receive tests exercise generated code: **982 cases / 10 mutation checks**, including the five signed `sizeof` corrections. The stale web-cookie fixture asserting IDF 5.5.0 was corrected, not bypassed. Historical stale-WS compilation/coverage failures in the [rebase review](idf_553_rebase_review.md) are resolved by this final snapshot, not hidden or retroactively called passes.
The [fix-bearing Wi-Fi bundle](wifi_security_update_plan.md) is integrated in the candidate, with unchanged PMF/WPA3; radio-hardware vulnerability closure and full target/resource/recovery gates remain pending. Nine notice catalog entries were semantically rebased, the other 66 unchanged (75 total); supplied notice evidence is **36 fixtures PASS**, two actual deterministic bundles each **77 files / 4,433,930 bytes**. Archive pins and source equality do not prove complete immutable root/ancillary/Python dependency closure, legal clearance or Phase 9 acceptance. This documentation update records supplied parent evidence; it did not rerun these builds, suites, bundles or hardware tests.
## 1. Historical review inputs, not assumed SDK paths
Paths below are relative to the repository except the explicitly absolute shared SDK:
| Role | Observed path / identity |
| --- | --- |
| Old SDK | `/home/mscholz/.platformio/packages/framework-espidf`, package **3.50500.0** / IDF 5.5.0 |
| Candidate archive | `.pio/idf-candidate-5.5.3/archives/framework-espidf-3.50503.0.tar.gz` |
| Candidate installed SDK | `.pio/idf-candidate-5.5.3/core/packages/framework-espidf`, package **3.50503.0** / IDF 5.5.3 |
| Isolated application | `.pio/idf-candidate-5.5.3/app` |
| Existing candidate build | `.pio/idf-candidate-5.5.3/app/.pio/build/esp32-s3-devkitc-1-n16r8` |
| Candidate compiler in compile database | `.pio/idf-candidate-5.5.3/core/packages/toolchain-xtensa-esp-elf/bin/xtensa-esp32s3-elf-gcc` |
There is no literal `.pio/idf-candidate` directory. The shared SDK is still **old**, not an installed production 5.5.3 SDK. There was no root `.pio/build/esp32-s3-devkitc-1-n16r8` build to validate. Tests defaulting to the shared SDK must explicitly select the candidate.
Recomputed **size and SHA-256 of all four complete archives** against `tools/idf_candidate/artifacts.json`: all matched (platform 6.13.0, framework 3.50503.0, Xtensa and RISC-V 14.2.0+20251107). Framework archive SHA-256: `8353f6fd5030dd7e662500891428fad15d46efd7e4b718cab2fe6bfb9e7f13fc`. Current `platformio.ini` selects that version pairing. Registry version pins in the root configuration are not themselves enforcement of these archive hashes; the isolated application uses local archive URLs.
Loaded the protected-entry registry from **Git HEAD**, verified every old original against its HEAD hash, then compared it with the working registry and actual candidate sources. Every working original hash and exact edit application passed. Also compared candidate protected IDF originals and `esp_httpd_priv.h` directly with members of the hash-verified framework archive: byte-identical. This separates old baseline, downloaded package, installed candidate and generated compilation inputs.
## 2. Historical blockers — resolved in final follow-up
### B1 — Historical candidate firmware does not contain the new WS correction
The staged `app/tools/security_overrides.py` lacks `Entry("httpd_ws", ...)`; the working generator includes it. The staged test runner also differs from the working web-performance runner. The candidate compile database compiles:
```
.../core/packages/framework-espidf/components/esp_http_server/src/httpd_ws.c
```
not `security_overrides/httpd_ws/httpd_ws.c`. The map includes `libesp_http_server.a(httpd_ws.c.o)`. The working SDK override test with this actual build fails:
```
AssertionError: ('httpd_ws', [])
```
All eight previously protected generated C files and the wolfSSH forced header independently match the current generator's bytes. Their compile paths are present; representative wolfSSH and application commands retain the joined forced-header and crypto-policy includes. This is a **specific stale ninth-C-source build**, not evidence that all replacements are missing.
An ELF/bin/map exists, but this reviewer did not build it. Snapshot identities:
- `firmware.elf`: 15,558,488 bytes; SHA-256 `39d12988d80c44787f1d9587d7ba685c990bfac940ba10180b86e60313b60fad`.
- `firmware.bin`: 1,749,888 bytes; SHA-256 `0477958591e0e21269b21fdd62ba855d507a6dd460107647f4f38739f9264e96`.
- `esp32_serial_swiss_army_knife.map`: SHA-256 `346abf8ce619742edd38274eef6267d6d8e0750fea6b1e2c8ffdcc34e5bc0085`.
**Required:** stage the final reviewed inputs in an ownership-safe isolated workspace, rebuild, and verify all **nine C overrides plus one forced header**, exact owners, original-source absence and generated bytes against that build. Do not attribute the current WS correction to the existing binary.
### B2 — Historical vendor-code tests and missing signed-error behavioral matrix
`tests/web_serial_performance/run.py` reads installed `httpd_ws.c`, not generated `httpd_ws.c`. Its enum-aware receive double is a necessary adaptation, but the suite fails compiling `httpd_ws_get_frame_type` against candidate vendor source:
```
error: comparison of integer expressions of different signedness
[-Werror=sign-compare]
```
The new five-edit override is included in generic generator/CMake fixtures. However, `tests/sdk_security_overrides/run.py` has no dedicated WS receive behavior or mutation suite. The web-performance receive double always supplies a successful first byte and the control-frame receiver is a double; it cannot establish the newly changed real frame-receive error paths.
**Required:** consume verified generated WS code without suppressing the warning or editing vendor files. Exercise actual `httpd_ws_get_frame_type`, `httpd_ws_recv_frame` and, where needed, the real `httpd_recv_with_opt` against bounded IO doubles: negative fail/timeout, EOF, short lengths and success at all five fixed-header reads; split/pending bytes; two- and eight-byte lengths; four-byte mask; automatic control-frame failure/close and no subsequent payload/send on failed framing. Require each removed signed cast to be detected by a negative regression/mutation. Passing generic generation alone is insufficient.
### B3 — Historical documentation gap: final snapshot required
At the initial review, `docs/idf_candidate_integration.md` described preparation only, production untouched and smoke not run, while the worktree already had production configuration/guard changes and an isolated application ELF. The generated modification notice pointed to that preparation document as rebase evidence without per-entry dispositions. The integration record now links the semantic dispositions and supplied final build/test evidence. This review's initial artifact inspection is not retroactively a fresh build pass.
Broader wolf ordering/parser/crypto/authentication, browser admission and full phase validation were **not rerun by this reviewer**. Existing historical 5.5.0 results and a stale candidate build cannot stand in for final-snapshot integration validation. Hardware/radio/resource/recovery and distribution/source obligations remain separate established gates, not reasons to repeat completed broad advisory research.
## 3. Per-entry semantic disposition
All old entries preserve exactly their HEAD edit sequences; changed IDF file hashes reflect the vendor differences below. Exact successful replacement is necessary but was not the only check: old/new source differences and relevant ownership/error paths were inspected, and existing behavioral fixtures were executed against candidate-generated sources.
| Protected entry | Disposition and reason |
| --- | --- |
| `dhcpserver` | **Retain unchanged original and six edits.** Vendor source is byte-identical across these SDKs; bounded option header/body, PAD/END handling and minimum lengths are not supplied by this upgrade. Extracted parser passes 196,623 guard-page/equivalence cases. |
| `mbedtls_ssl_tls` | **Rebase original hash; retain EMS error return.** Vendor changes include handshake-state helpers, dynamic-buffer reset handling, PRF label-length plumbing and exporter support. The `calc_verify` error branch still logs without returning; the local return remains necessary before PRF use. SHA256/SHA384 failures, success, PRF failure, non-EMS and resumption fixtures pass. Dynamic TLS buffers remain rejected. This is not a review of every new exporter/TLS1.3 path. |
| `mbedtls_x509_create` | **Rebase original hash; retain OID allocation check.** Vendor now rejects a prepopulated output list instead of clearing it; it still dereferences an unchecked newly allocated `oid.p`. Local OOM return remains needed. Subject/issuer OOM, cleanup and retry fixtures pass. Project certificate generation sets subject and issuer separately once, consistent with the changed empty-list precondition; do not generalize this to repeated setter calls elsewhere. |
| `https_server` | **Rebase original hash; retain all four edits.** Vendor delta adds hardware ECDSA curve/block configuration, not post-handshake allocation-failure cleanup, copied-key wiping or failed-start secure-context destruction. Local TLS delete, wipe-before-free and restored/null-cleared ownership still apply. Existing allocation/handshake/start/stop matrix passes. Failed stop continues to retain live ownership. |
| `httpd_parse` | **Rebase original hash; retain all six edits.** Vendor delta fixes cookie length reporting on truncation, not scratch ownership. Null initial pointer arithmetic, failed `realloc` ownership and unwiped retired scratch still require local fixes. Allocate/copy/wipe/free, preserve-old-on-failure, nullable parser pointer and final wipe remain; null/grow/shrink/failure/pending-unread fixtures pass. |
| `esp_tls_mbedtls` | **Rebase original hash; retain guards and server-only policy.** Vendor adds dynamic-buffer strategy, hardware ECDSA configuration, RSA-ALT cleanup and error/certificate reporting changes. None supplies the project server TLS1.2 ECDHE-ECDSA AES-GCM allowlist or no-renegotiation policy. Injection remains in server configuration; clients retain their defaults. Server/client isolation, feature and dynamic-buffer guard fixtures pass. |
| `wolfssh_internal` | **Retain original hash and all 53 edits.** Managed source matches HEAD. Ordering delta, restricted no-EXT_INFO profile, password bounds/wiping, parser/name/service/signature/window corrections are unchanged; this IDF migration does not supersede them. The initial semantic review did not rerun full wolf behavior; the final supplied 24/24 run includes the established wolf suites. |
| `wolfssh_ssh` | **Retain original hash and ordering edits.** Generated candidate bytes match working output; exact generated compilation path present. |
| `wolfssh_internal_header` | **Retain original hash and ABI/order overlay.** Generated header bytes match; candidate library and representative application commands retain the forced include and overlay include path. PUBLIC direct/transitive consumer fixture passes. |
| `httpd_ws` (new) | **New candidate-specific correction, not a repin.** IDF 5.5.3 changes fixed header reads from nonblocking `<= 0` checks to blocking `< sizeof(...)`. `int` negative receive results convert to unsigned `size_t`, making the checks false. The five `(int)sizeof(...)` changes correctly restore signed rejection for sizes 1, 2, 8, 4 and 1; they retain vendor blocking reads and do not alter payload framing policy. Behavioral regression and current-build evidence were initially blocked by B1/B2; the final follow-up resolves both with 982 cases / 10 mutation checks and actual generated-source registration. |
No protected correction was demonstrated to be **superseded**. Native pre-handshake support is not a reason to delete the application's admission adapter: it is disabled in the candidate configuration and the application deliberately registers the two upgrade routes as ordinary GET handlers.
## 4. Heap extent and HTTPD private boundary
### Heap: guarded 5.5.3 extent contract is supported
`components/heap/heap_caps.c`, `multi_heap.c`, `multi_heap_platform.h` and `tlsf/tlsf.c` are byte-identical old-to-candidate. The audited chain remains:
1. `heap_caps_get_allocated_size` removes any owner offset, locates the containing heap and subtracts owner metadata from the reported size.
2. Unpoisoned `multi_heap_get_allocated_size` aliases the implementation returning `tlsf_block_size`.
3. TLSF obtains the allocated payload block's rounded size, not a containing-block extent for an arbitrary interior pointer.
Candidate resolved config enables `CONFIG_HEAP_POISONING_DISABLED`, disables task tracking and leaves dynamic mbedTLS buffers off. `ssh_memory` still rejects poisoning and all other IDF versions. Actual candidate-source contract checks and host allocation/free/realloc wipe tests pass, including rounded capacity, retained shrink, growth and allocation failure. No new allocator layout/header mechanism was introduced. Target wipe latency and old-plus-new allocation peaks remain unmeasured.
### HTTPD: relevant layouts retained; receive API changed
The private-header diff adds shared control-message declarations, `HTTPD_RECV_OPT_*`, the enum receive parameter and a direct-LRU-close declaration. It does **not** change the adapter-used `httpd_data`, `httpd_req_aux` or `sock_db` field layouts. The application compiles against candidate headers rather than embedding an old SDK struct definition.
Relevant behavior review:
- Session completion still performs request creation/handling, request deletion/purge, then updates the LRU completion marker. New sessions initialize the counter from the server; the adapter already tracks session identity. All 18 idle lifecycle groups pass against candidate completion/purge sources.
- `httpd_main` still processes control work before connection data. New direct LRU close is conditional; application `lru_purge_enable` remains false.
- Header/scratch parser delta is the cookie reporting fix, not a layout/termination change. Right-aligned pending bytes and application wipe preservation remain applicable.
- Optional URI registration still publishes before `strdup` failure cleanup; the application's allocate-both-before-publication correction remains necessary.
- Vendor URI dispatch adds optional pre-handshake callback support. Candidate config disables it; application ordinary-GET upgrade handlers continue to authorize/admit before manually sending 101.
- SDK WS sending remains separate header/payload sends through session `send_fn`; the bounded combined binary adapter still uses that TLS override and replaces it with reject-only behavior after incomplete output. No raw socket bypass was introduced.
- `httpd_recv_with_opt` changes from Boolean to enum and loops for blocking fixed-header reads; `httpd_recv` uses `NONE`. Async completion now wakes the owner with a control message. Application owner/async exclusion rules remain relevant. The receive double was correctly changed to the enum, and the initial WS receive-error/test-input blockers B1/B2 are now resolved as recorded in the final follow-up.
This supports the narrow guard update; it does not certify arbitrary configurations, async use or every HTTPD API.
## 5. Whole radio bundle identity and actual link inputs
Compared **every regular archive member** under these component trees with the corresponding installed candidate file; all were byte-identical:
| Component | Files compared |
| --- | ---: |
| `esp_wifi` | 163 |
| `esp_phy` | 114 |
| `esp_coex` | 41 |
| `wpa_supplicant` | 301 |
This includes packaged source, headers, CMake inputs and all target/optional archives, not only the seven currently discussed S3 Wi-Fi blobs. It establishes coherent delivery from the one locked framework archive, **not** independent upstream PHY/coexistence Git submodule provenance, vendor signature validation or opaque binary correctness.
S3 Git blob identities recomputed from installed bytes:
| Archive | Git blob SHA-1 |
| --- | --- |
| Wi-Fi `libcore.a` | `9f7b14a8bf6eec64973da8adc65d35b5ba9bee49` |
| Wi-Fi `libespnow.a` | `132b4f67e339ca2081d2add91c14eefa39476ff9` |
| Wi-Fi `libmesh.a` | `2e9dc1c8c7afbf033337b4175032e9b1161e3262` |
| Wi-Fi `libnet80211.a` | `2800d447ec385d33869373696ba8191292647694` |
| Wi-Fi `libpp.a` | `8944bcad7371621045f376cf74c62fde6f368cbb` |
| Wi-Fi `libsmartconfig.a` | `4dc759e25617aa00b9e12887fd092a1d5780a170` |
| Wi-Fi `libwapi.a` | `65655b6feab0572a6e8a1200946d53a21f3f4722` |
| PHY `libphy.a` | `75d3c8756c8e0310066fa9a1e5c24df50e6c04e3` |
| PHY `libbtbb.a` | `e94e3c2b476d8abb2757bcb53519ebcfba92b213` |
| PHY `libbttestmode.a` | `06389c391ea55278df6205b6195b6d8182c7b8be` |
| PHY `librfate.a` | `05dc4b69e02dac8e6c75ab8f9dc49c1d33d66c32` |
| PHY `librftest.a` | `45c6dc240287332c8c8790e84a80475dbfe3a842` |
| Coexistence `libcoexist.a` | `21fb35634fbdd421519bc985072fdc0ab54dd1df` |
The seven Wi-Fi identities agree with the already recorded 5.5.3 **release-point** column in `docs/wifi_security_update_plan.md`; they are not a mixed fix-point/release-point bundle. Candidate `esp_wifi_driver.h` declares `wpa_ap_get_wpa_ie(size_t *len)` and `esp_wpa_main.c` defines `wpa_ap_get_wpa_ie(size_t *ie_len)` and assigns that callback. The compile database uses candidate supplicant sources.
Actual candidate map `LOAD` entries select **all seven Wi-Fi blobs plus `libphy.a` and `libbtbb.a` from the candidate SDK path**, and the candidate-built component wrappers/supplicant archive. No old shared-SDK radio path was observed in those inputs. `libcoexist.a` is delivered and verified but is **not a LOAD input** in this configuration: `esp_coex/CMakeLists.txt` adds it only for software/external coexistence, not merely `CONFIG_ESP_COEX_ENABLED`. Optional RF/test PHY archives likewise are packaged identities, not claimed linked inputs. A map `LOAD` line does not prove all members survive link garbage collection.
The whole-package approach avoids a 5.5.0 C/header/blob transplant. This provides stronger delivery/path evidence than the earlier seven-Wi-Fi-only check, but does not demonstrate runtime PMF/WPA3 behavior, radio stability, target reserves or deployment vulnerability closure.
## 6. Historical validation executed and handoff
Commands run from the repository root:
```sh
CCACHE_DISABLE=1 python3 -B tests/ssh_memory/run.py --idf-path .pio/idf-candidate-5.5.3/core/packages/framework-espidf
IDF_PATH=/home/mscholz/Repos/ESP32_serial_swiss_army_knife/.pio/idf-candidate-5.5.3/core/packages/framework-espidf CCACHE_DISABLE=1 python3 -B tests/web_httpd_idle/run.py
IDF_PATH=/home/mscholz/Repos/ESP32_serial_swiss_army_knife/.pio/idf-candidate-5.5.3/core/packages/framework-espidf CCACHE_DISABLE=1 python3 -B tests/web_serial_performance/run.py
CCACHE_DISABLE=1 python3 -B tests/sdk_security_overrides/run.py --idf-path .pio/idf-candidate-5.5.3/core/packages/framework-espidf --build-dir .pio/idf-candidate-5.5.3/app/.pio/build/esp32-s3-devkitc-1-n16r8
```
Results:
- **PASS:** SSH allocator behavior, six invalid poisoning profiles, explicit disabled profile, seven rejected IDF versions, real candidate extent-source checks and compile with candidate version header.
- **PASS:** all 18 idle lifecycle groups and candidate completion/owner-order guards.
- **FAIL:** web serial performance compile against original WS source, B2.
- **PARTIAL PASS / overall FAIL:** SDK generator, HTTPS/scratch/TLS/DHCP/EMS/X509 behavior and CMake replacement/forced-header/extension fixtures pass; actual build registration rejects missing generated WS input, B1. Do not report this command as passing.
- **PASS:** read-only HEAD/current original-hash and edit comparisons; all four archive identities; protected candidate/archive comparisons; all four radio component trees; candidate compile/map path inspection.
The initial next actions were generated-WS regression coverage, a fresh staged snapshot/build and final-input integration validation. Those actions are now complete in the supplied final follow-up; the historical failures above remain failures of the old snapshot, not current blockers or retroactive passes. No production defect was found in the retained old overrides or reviewed heap/private-layout assumptions. Normal root build completion, immutable full dependency closure, target/radio/resource/recovery and release gates remain open. Preserve PMF/WPA3, broker isolation, native USB independence and UART0 recovery.
+100
View File
@@ -0,0 +1,100 @@
# Official PlatformIO IDF 5.5.3 candidate integration
Initial package preparation: **2026-09-16**, Linux x86_64; integration update: **2026-09-18**. Preparation-only statements below describe the earlier stage. Root `platformio.ini` and reviewed guards/overrides are now migrated; shared-SDK installation is not claimed complete and no device/deployment acceptance is claimed.
## Final integration evidence — 2026-09-18
Supplied final parent command, run from the repository root:
```sh
CCACHE_DISABLE=1 python3 -B tools/validate_phase9.py --build-dir .pio/idf-candidate-5.5.3/app-validated/.pio/build/esp32-s3-devkitc-1-n16r8 --idf-path .pio/idf-candidate-5.5.3/core/packages/framework-espidf --platformio-core-dir .pio/idf-candidate-5.5.3/core --interop --web-performance
```
**PASS 24/24.** This selects the actual fresh isolated build, SDK and toolchain core; it is not a default-root-build execution. The separate fresh `.pio/idf-candidate-5.5.3/app-validated` build **PASS** reports **95,552 B linked RAM / 1,749,493 B flash**, versus historical **94,340 / 1,768,901 B** (**+1,212 B RAM / 19,408 B flash**). The default parent `pio run` timed out after **200 seconds during installation, before compilation**; no normal root build PASS is claimed.
Pre/post source equality: **3,237 files**, SHA-256 **`3a1af78c15cfdd02da1055a8957b4086f9018862b7aa1c7c52fd2401a1a0a031`**. Actual generated-input registration covers **nine C sources plus one forced header**. WS receive tests exercise generated code: **982 cases / 10 mutation checks**, including the five signed `sizeof` corrections. The stale web-cookie fixture asserting IDF 5.5.0 was corrected, not bypassed. Historical stale-WS compilation/coverage failures in the [rebase review](idf_553_rebase_review.md) are resolved by this final snapshot, not hidden or retroactively called passes.
The [fix-bearing Wi-Fi bundle](wifi_security_update_plan.md) is integrated in the candidate, with unchanged PMF/WPA3; radio-hardware vulnerability closure and full target/resource/recovery gates remain pending. Nine notice catalog entries were semantically rebased, the other 66 unchanged (75 total); supplied notice evidence is **36 fixtures PASS**, two actual deterministic bundles each **77 files / 4,433,930 bytes**. Archive pins and source equality do not prove complete immutable root/ancillary/Python dependency closure, legal clearance or Phase 9 acceptance. This documentation update records supplied parent evidence; it did not rerun these builds, suites, bundles or hardware tests.
## Concrete result
**Select official `platformio/espressif32@6.13.0` + `platformio/framework-espidf@3.50503.0` (IDF 5.5.3) + `platformio/toolchain-xtensa-esp-elf@14.2.0+20251107`.** The official adapter also selects `platformio/toolchain-riscv32-esp@14.2.0+20251107` for ESP32-S3 ULP; include it even when evaluating the Xtensa application.
This is an explicitly released supported pairing, not a speculative package override. Exact downloaded archive hashes below make the selected inputs content-pinned; tags/version labels alone are not treated as immutable.
Official channels checked:
- [Platform registry](https://api.registry.platformio.org/v3/packages/platformio/platform/espressif32): latest stable **7.1.3**, published September 11, 2026. [Latest release API](https://api.github.com/repos/platformio/platform-espressif32/releases/latest) agrees (`prerelease=false`). Its [manifest](https://raw.githubusercontent.com/platformio/platform-espressif32/v7.1.3/platform.json) selects `~4.60100.0`, **IDF 6.1**, not IDF 5.5.
- [Framework registry](https://api.registry.platformio.org/v3/packages/platformio/tool/framework-espidf): newest published 5.5 package is **3.50503.0**, published February 18, 2026. Returned 5.5 versions are 3.50503.0, 3.50502.0 and 3.50500.0. No 5.5.4/5.5.5 package appears in that response. Thus 5.5.3 is the **latest available official PlatformIO 5.5 candidate**, not the latest upstream Espressif 5.5 maintenance release. Registry framework metadata labels its tier `community` but its owner is `platformio`; the platform itself is tier `official`.
- [6.13.0 release](https://github.com/platformio/platform-espressif32/releases/tag/v6.13.0), [release API](https://api.github.com/repos/platformio/platform-espressif32/releases/tags/v6.13.0): explicitly adds IDF **5.5.3** and updates IDF toolchains to **14.2.0+20251107**. Stable release, February 26, 2026.
- [6.13.0 manifest](https://raw.githubusercontent.com/platformio/platform-espressif32/v6.13.0/platform.json): framework `~3.50503.0`, Xtensa `14.2.0+20251107`. [Adapter](https://raw.githubusercontent.com/platformio/platform-espressif32/v6.13.0/platform.py) removes the legacy chip-specific Xtensa toolchains for standalone IDF, enables unified Xtensa for S3, and selects the same-date PlatformIO RISC-V package for S3 ULP.
- [Xtensa registry](https://api.registry.platformio.org/v3/packages/platformio/tool/toolchain-xtensa-esp-elf) and [RISC-V registry](https://api.registry.platformio.org/v3/packages/platformio/tool/toolchain-riscv32-esp) both publish the selected Linux x86_64 artifacts.
If the requirement is specifically upstream **5.5.5**, rather than the newest officially delivered 5.5 maintenance release, that requirement remains **blocked on official packaging/support**. Do not substitute 7.1.3 plus an arbitrary 5.5 override or a raw GitHub source archive.
## Actual downloaded identities
All four complete archives were downloaded into `.pio/idf-candidate-5.5.3/archives/` and their **local bytes** passed both registry size and SHA-256 checks. These are measured download checks, not just registry advertisements. No archive was unpacked into the shared PlatformIO SDK. Total compressed size: **992,746,039 bytes**.
| Artifact | Bytes | SHA-256 |
| --- | ---: | --- |
| `espressif32-6.13.0.tar.gz` | 1,009,115 | `5d1032b43828773ba87cf2e509432202c0bfe64f7304b58c9d669f13b116c6e0` |
| `framework-espidf-3.50503.0.tar.gz` | 76,402,966 | `8353f6fd5030dd7e662500891428fad15d46efd7e4b718cab2fe6bfb9e7f13fc` |
| `toolchain-xtensa-esp-elf-linux_x86_64-14.2.0+20251107.tar.gz` | 322,439,270 | `a5de49ce3299b0d9253ab6a423648bc23113db96b34a7cc8e57702cae1bb190e` |
| `toolchain-riscv32-esp-linux_x86_64-14.2.0+20251107.tar.gz` | 592,894,688 | `1af8e233931500b8712079808e4974413d95d3601d03275dff79665c436e9d33` |
Machine-readable registry URLs, artifact URLs, versions, system selectors, sizes and hashes: [`artifacts.json`](../tools/idf_candidate/artifacts.json). No automatic repinning occurs.
The verifier reads members directly from the hash-verified archives without extracting or executing vendor files. Actual checks passed:
- Platform manifest version, framework range and exact Xtensa requirement.
- SDK package version and SDK `tools/tools.json` recommendation `esp-14.2.0_20251107`.
- Fixed `wpa_ap_get_wpa_ie(size_t *len)` callback declaration.
- All seven packaged ESP32-S3 Wi-Fi libraries match the **5.5.3 release-point Git blob identities**, not the fix-point-only bundle, recorded in [the existing Wi-Fi plan](wifi_security_update_plan.md#bundle-identity-baseline-fix-point-release-point-are-different). The plan already establishes release commit `2c211b236707889e8400c4dc5644dd5c4ee071e0` and Wi-Fi submodule `e0befaa593277b4e80726079fbd521b4681754c2`; this task does not repeat fix research.
Whole-archive SHA-256 pins include the delivered PHY/coexistence/source/header contents, preventing changes to those bytes going unnoticed by this verifier. The subsequent [semantic rebase review](idf_553_rebase_review.md#5-whole-radio-bundle-identity-and-actual-link-inputs) compared every regular packaged file in `esp_wifi` (163), `esp_phy` (114), `esp_coex` (41), and `wpa_supplicant` (301) against the installed candidate: all matched. This is complete comparison of those delivered component trees, not merely seven Wi-Fi archives. This is **not** an independent recursive source-to-package audit, vendor signature verification, proof of opaque implementation correctness, or execution of the compiler binaries.
## Reproduce preparation and verification
From the repository root, with Python 3.9+ on Linux x86_64:
```sh
python3 -B tools/idf_candidate/test_prepare.py
python3 -B tools/idf_candidate/prepare.py
```
The second command is offline, verifies all four already-downloaded archives, and makes no installation. Missing or altered inputs fail. Four offline helper tests cover corrupted hash/size, URL restrictions, contract failure, and absent/ambiguous archive members.
On a fresh checkout, download and create the isolated project:
```sh
python3 -B tools/idf_candidate/prepare.py --fetch --prepare
```
Network is restricted by the tool to HTTPS `dl.registry.platformio.org` and `dl.registry.nm1.platformio.org`, including redirects. Approximately 993 MB download space is needed plus substantial unpacked/build space for the later test. `--sdk-only --fetch` obtains/verifies only the platform and SDK. Existing mismatched archives fail rather than being overwritten. A killed download may leave a `.partial` file; inspect/remove that candidate-only partial before retrying. `--prepare` deliberately refuses an existing smoke directory rather than overwriting it. **Preparation already succeeded here**; use offline verification, not a second `--prepare`.
Generated smoke project: `.pio/idf-candidate-5.5.3/smoke/`. Its configuration uses the verified local official platform archive and exact local framework/toolchain archives, with `core_dir` under `.pio/idf-candidate-5.5.3/core/`. Its sources are a separate empty `app_main`; it neither inherits production config nor imports application overrides. Generic official ESP32-S3 board is intentional: this tests package integration, **not** the production N16R8 board or feature configuration.
### Command to test the isolated candidate
After successful verification, from the repository root:
```sh
env -u PLATFORMIO_PACKAGES_DIR -u PLATFORMIO_PLATFORMS_DIR -u PLATFORMIO_BUILD_DIR -u PLATFORMIO_CACHE_DIR -u IDF_PATH -u IDF_TOOLS_PATH -u IDF_PYTHON_ENV_PATH PLATFORMIO_CORE_DIR=/home/mscholz/Repos/ESP32_serial_swiss_army_knife/.pio/idf-candidate-5.5.3/core CCACHE_DISABLE=1 pio run --project-dir .pio/idf-candidate-5.5.3/smoke
```
For another checkout location, replace the absolute `PLATFORMIO_CORE_DIR` accordingly. Run in a normal clean PlatformIO shell, not an activated unrelated IDF environment. The initial preparation task did **not run this smoke invocation**; the later fresh application build PASS is recorded above and does not retroactively claim execution of this exact smoke command. It may download platform ancillary packages and IDF Python dependencies (registry/mirror, PyPI/files.pythonhosted.org and Espressif download endpoints as requested by the adapter); grant those hosts separately as needed. Those ancillary/Python dependencies are not yet a complete frozen build closure. The prepared four-input lock is not advertised as a fully reproducible toolchain environment/SBOM. No upload, monitor or erase command is part of this evaluation.
## Rebase disposition and remaining integration gates
The official package-availability/toolchain mismatch question and bounded application build/host compatibility checks are resolved for the validated candidate. The [per-entry semantic review](idf_553_rebase_review.md#3-per-entry-semantic-disposition) retains every old protected correction (rebasing changed IDF original hashes), retains wolfSSH C/ABI overlays, and adds the ninth C override for five signed WS receive-size comparisons. Nothing was removed as superseded or bypassed by a permissive version guard. Reviewed heap extent/private HTTPD guards now target 5.5.3; generated-input ownership/include order and behavioral suites pass on the final build.
Remaining gates:
1. A successful normal root build: the default attempt stopped during installation, before compilation. Exact root version pins are not enforcement of the downloaded archive hashes.
2. Complete immutable ancillary/Python/tool/managed-component dependency closure; four verified primary archives and source equality are not a full reproducible environment or SBOM.
3. Target/radio/resource/recovery validation in the [Wi-Fi plan](wifi_security_update_plan.md), including the exact trigger and unchanged PMF/WPA3. Candidate integration is not hardware vulnerability closure.
4. Recipient notices, corresponding source, radio-blob legal basis and release-specific runtime/bootloader attribution. Nine notice entries have been semantically rebased and 66 retained unchanged, but assembly is not delivery or legal clearance.
5. Explicit whole-Phase-9 target acceptance. No upload, erase, credential migration, PMF weakening or generated-asset regeneration is part of this documentation update.
+20 -7
View File
@@ -5,6 +5,20 @@ runners, not a new security test implementation or a Phase 9 acceptance gate.
See [security hardening](security_hardening.md) for scope, historical evidence, See [security hardening](security_hardening.md) for scope, historical evidence,
and the deferred whole-phase target checklist. and the deferred whole-phase target checklist.
## Current candidate execution — 2026-09-18
Supplied final parent command, run from the repository root:
```sh
CCACHE_DISABLE=1 python3 -B tools/validate_phase9.py --build-dir .pio/idf-candidate-5.5.3/app-validated/.pio/build/esp32-s3-devkitc-1-n16r8 --idf-path .pio/idf-candidate-5.5.3/core/packages/framework-espidf --platformio-core-dir .pio/idf-candidate-5.5.3/core --interop --web-performance
```
**PASS 24/24.** This selects the actual fresh isolated build, SDK and toolchain core; it is not a default-root-build execution. The separate fresh `.pio/idf-candidate-5.5.3/app-validated` build **PASS** reports **95,552 B linked RAM / 1,749,493 B flash**, versus historical **94,340 / 1,768,901 B** (**+1,212 B RAM / 19,408 B flash**). The default parent `pio run` timed out after **200 seconds during installation, before compilation**; no normal root build PASS is claimed.
Pre/post source equality: **3,237 files**, SHA-256 **`3a1af78c15cfdd02da1055a8957b4086f9018862b7aa1c7c52fd2401a1a0a031`**. Actual generated-input registration covers **nine C sources plus one forced header**. WS receive tests exercise generated code: **982 cases / 10 mutation checks**, including the five signed `sizeof` corrections. The stale web-cookie fixture asserting IDF 5.5.0 was corrected, not bypassed. Historical stale-WS compilation/coverage failures in the [rebase review](idf_553_rebase_review.md) are resolved by this final snapshot, not hidden or retroactively called passes.
The [fix-bearing Wi-Fi bundle](wifi_security_update_plan.md) is integrated in the candidate, with unchanged PMF/WPA3; radio-hardware vulnerability closure and full target/resource/recovery gates remain pending. Nine notice catalog entries were semantically rebased, the other 66 unchanged (75 total); supplied notice evidence is **36 fixtures PASS**, two actual deterministic bundles each **77 files / 4,433,930 bytes**. Archive pins and source equality do not prove complete immutable root/ancillary/Python dependency closure, legal clearance or Phase 9 acceptance. This documentation update records supplied parent evidence; it did not rerun these builds, suites, bundles or hardware tests.
## Usage ## Usage
From the repository root (the script itself also resolves the root independently From the repository root (the script itself also resolves the root independently
@@ -37,7 +51,7 @@ avoid testing stale artifacts as if that build succeeded.
default single matrix). That runner uses OpenSSH and local AF_UNIX sockets, not default single matrix). That runner uses OpenSSH and local AF_UNIX sockets, not
an IP host or device. Socket permissions and installed OpenSSH tooling must an IP host or device. Socket permissions and installed OpenSSH tooling must
already be available; denial is a failure, not a reason to grant permissions. already be available; denial is a failure, not a reason to grant permissions.
Neither option is enabled by default. No target address, credentials, arbitrary Neither option is enabled by default. `--web-performance` independently adds the generated-WS performance runner (24 commands total). `--build-dir`, `--idf-path` and `--platformio-core-dir` select existing candidate artifacts/SDK/core; `--build` rejects a non-default build directory, which must be built separately. No target address, credentials, arbitrary
command, or device-operation argument is accepted. command, or device-operation argument is accepted.
## Coverage and prerequisites ## Coverage and prerequisites
@@ -70,9 +84,9 @@ not proof that the current checkout has been freshly built.
The current cookie-auth runner has no `--admission` branch: the default exercises The current cookie-auth runner has no `--admission` branch: the default exercises
early admission/wiping, and each selected domain mode repeats that baseline. early admission/wiping, and each selected domain mode repeats that baseline.
Unrelated display/network/UI/performance suites are intentionally excluded. Unrelated display/network/UI suites are intentionally excluded; web performance is an explicit opt-in.
`ssh_memory` retains its documented optional installed-IDF extent-source check `ssh_memory` retains its documented optional installed-IDF extent-source check
as skipped (no `--idf-path` supplied); its normal allocator contracts still run. as skipped when no `--idf-path` is supplied; explicit candidate selection enables it. Its normal allocator contracts still run.
The ordering runner's candidate `--target-contracts`, separate `--pio-adapter` The ordering runner's candidate `--target-contracts`, separate `--pio-adapter`
regression, and repeated interop matrices are not added implicitly. Run those regression, and repeated interop matrices are not added implicitly. Run those
separately when their particular evidence is needed. This is a focused full separately when their particular evidence is needed. This is a focused full
@@ -89,7 +103,7 @@ Phase 9 host regression set, not every test in the repository.
group descendants. This covers normal compiler/test children, not deliberately group descendants. This covers normal compiler/test children, not deliberately
detached processes. Group reaping has a five-second wait bound. detached processes. Group reaping has a five-second wait bound.
- Commands use argv lists, no shell evaluation or interpolation, with stdin - Commands use argv lists, no shell evaluation or interpolation, with stdin
closed. The child environment is copied intact except `CCACHE_DISABLE=1`; closed. The child environment is copied with `CCACHE_DISABLE=1`; explicit `--idf-path` and `--platformio-core-dir` also set resolved `IDF_PATH` and `PLATFORMIO_CORE_DIR` for children;
arbitrary compiler, SDK, proxy and other caller environment settings are not arbitrary compiler, SDK, proxy and other caller environment settings are not
overwritten. The parent's environment is not mutated. overwritten. The parent's environment is not mutated.
- stdout/stderr are inherited live. The orchestrator creates no persistent log, - stdout/stderr are inherited live. The orchestrator creates no persistent log,
@@ -116,7 +130,7 @@ Phase 9 host regression set, not every test in the repository.
python3 -B tests/phase9_validation/run.py python3 -B tests/phase9_validation/run.py
``` ```
Ten offline unittest groups check the exact plan/paths/strict arguments, independent Offline unittest groups check the exact plan/paths/strict arguments, independent
opt-ins, timeout configuration and invalid values, dry-run nonexecution, default opt-ins, timeout configuration and invalid values, dry-run nonexecution, default
build/interop omission, failure collection, fail-fast and failed-build skips, build/interop omission, failure collection, fail-fast and failed-build skips,
missing file/executable handling, literal argv/environment preservation, missing file/executable handling, literal argv/environment preservation,
@@ -168,5 +182,4 @@ new build or bundle measurement by this documentation integration.
The [finite advisory search](dependency_advisory_coverage.md) is complete for its The [finite advisory search](dependency_advisory_coverage.md) is complete for its
declared snapshot, but the [PMF-capable SoftAP blocker](wifi_security_update_plan.md) declared snapshot, but the [PMF-capable SoftAP blocker](wifi_security_update_plan.md)
is **not fixed**. Host success does not repair that vendor bundle, validate radio was **not fixed at that historical snapshot**. The current fix-bearing bundle is now integrated in the validated candidate; host success does not validate radio behavior, clear distribution obligations or complete Phase 9.
behavior, clear distribution obligations or complete Phase 9.
+85 -3
View File
@@ -17,12 +17,12 @@ installs, builds, runs PlatformIO, accesses a device, or regenerates assets.
Run from the repository root. Supply **an explicit, not-yet-existing output Run from the repository root. Supply **an explicit, not-yet-existing output
directory outside all three input roots**, with an existing parent. Example directory outside all three input roots**, with an existing parent. Example
for the inventory's local installation (choose a fresh final directory name): for the reviewed installed 5.5.3 candidate (choose a fresh final directory name):
```sh ```sh
python3 tools/release_notices.py \ python3 tools/release_notices.py \
--sdk-root /home/mscholz/.platformio/packages/framework-espidf \ --sdk-root /home/mscholz/Repos/ESP32_serial_swiss_army_knife/.pio/idf-candidate-5.5.3/core/packages/framework-espidf \
--toolchain-root /home/mscholz/.platformio/packages/toolchain-xtensa-esp-elf \ --toolchain-root /home/mscholz/Repos/ESP32_serial_swiss_army_knife/.pio/idf-candidate-5.5.3/core/packages/toolchain-xtensa-esp-elf \
--output /tmp/esp32-release-notices-review-01 --output /tmp/esp32-release-notices-review-01
``` ```
@@ -135,6 +135,88 @@ turn; firmware source is unchanged. Assembly is not legal clearance or Phase 9
acceptance. The [unfixed Wi-Fi blocker](wifi_security_update_plan.md) also remains acceptance. The [unfixed Wi-Fi blocker](wifi_security_update_plan.md) also remains
a separate security gate; a future coherent vendor update requires catalog review. a separate security gate; a future coherent vendor update requires catalog review.
## Installed candidate notice rebase — 2026-09-18
This addendum audits the existing 75-input catalog against **already installed**
PlatformIO 6.13.0 migration candidates, not merely downloaded archives. No
installation, build, production/config edit, or security-override rebase was
performed by this notice audit. The preceding 2026-09-16 evidence is historical.
Actual candidate package root:
`/home/mscholz/Repos/ESP32_serial_swiss_army_knife/.pio/idf-candidate-5.5.3/core/packages/`.
Its `framework-espidf/package.json` reports **3.50503.0**, and `version.txt`
reports **5.5.3**. Both `toolchain-xtensa-esp-elf/package.json` and
`toolchain-riscv32-esp/package.json` report **14.2.0+20251107**. RISC-V identity
was checked for context; it is not a new input root or a claim of RISC-V notice
coverage. The shared `/home/mscholz/.platformio/packages/` still contains
framework **3.50500.0 / IDF 5.5.0** and both toolchains **14.2.0+20241119**;
do not use those shared paths with this rebased catalog.
All 75 old catalog full-file hashes/sizes matched the old shared SDK/toolchain
and current project inputs before editing. Comparison against the installed
candidate found exactly **nine changed entries** (eight SDK, one Xtensa),
listed below. The other **66 entries remain unchanged**, including all **39
project/managed inputs**, the dependency-lock pin, icon evidence and managed
version map. No catalog paths, purposes unrelated to version identity, or
excerpt ranges were added/removed/relaxed. Snapshot identity/date and the three
identity-purpose strings were updated deliberately, not by automatic repinning.
Paths below are relative to the indicated catalog root. Hashes are full-source
SHA-256; old hashes remain in the preceding Git catalog revision.
| Root / path | Old → candidate bytes | Candidate SHA-256 |
| --- | ---: | --- |
| sdk / `components/esp-tls/esp_tls_mbedtls.c` | 50176 → 54323 | `edc39052244526cb91c93a16bc765194031060e5560fd432e75542f2f6c8db53` |
| sdk / `components/esp_http_server/src/httpd_parse.c` | 42937 → 42937 | `db7fbbb322bccb4a21bc1607208a6bb7d1f4c0d7ecc3299de10c9e60edaa943b` |
| sdk / `components/esp_https_server/src/https_server.c` | 15257 → 15459 | `a2a5ca0549fbe8d1ddd7f9647a48a31fdd6329b997e1550ab69d6a10efecff0d` |
| sdk / `components/freertos/FreeRTOS-Kernel/include/freertos/task.h` | 133072 → 133108 | `8b908b964d2c87377838762478253eec04312b985e6bb680bacd3f03ab253897` |
| sdk / `components/mbedtls/mbedtls/library/ssl_tls.c` | 329921 → 336963 | `0154e70f20b465213d3f97a9c2b75e686d79ee83592f90e1d7e7d995593ca7e1` |
| sdk / `components/mbedtls/mbedtls/library/x509_create.c` | 19803 → 19934 | `55edce5b8fcb039a404b84ea830f06d018128b155dfbf0d74d26b17247b546c6` |
| sdk / `package.json` | 536 → 536 | `230c52993499adf9f64d1d6477495ea46c3f14d63022bd52081858373098da68` |
| sdk / `version.txt` | 5 → 5 | `b473c8e2ed91b38d7e96e54d4ce094474ca6015c0641b7cdfc0ab94b1f8b4668` |
| toolchain / `package.json` | 472 → 472 | `bdd3b852d051d6513912e71d29851bd04b800e2d33d78aec48067af5d83da5c9` |
Semantic notice review: five changed implementation files retain byte-identical
complete opening notices at their existing ranges (ESP-TLS `[0,122)`, HTTPD
`[0,124)`, HTTPS `[0,123)`, Mbed TLS shared TLS `[0,223)` and X.509 `[0,169)`).
Their output hashes stay unchanged; full-source hashes must change even when
only implementation bytes change. This does **not** approve the implementation
changes or establish compatibility of the project's security overlays.
FreeRTOS `task.h` retains its complete MIT notice at `[0,1455)`; only
`SPDX-FileContributor: 2023-2024` becomes `2023-2025` within that excerpt.
Its output hash changes from
`8e3d9f79bbbdf6dfd6fbf132982a7ec3a45ff6dac92277034316efae4cccad84` to
`e53c9bcefd4327c713696e870a3568a27cedf8833467b9cd9536cb2e09ab7ad8`.
The remaining `task.h` changes correct stack-watermark documentation to bytes.
The two package JSON diffs change only versions; SDK `version.txt` changes only
`5.5.0` to `5.5.3`.
All 24 remaining SDK inputs and three Xtensa license inputs match the old pins,
including SDK/radio Apache texts, both Newlib documents, mixed argtable3,
wpa_supplicant and GCC exception/GPL texts. An auxiliary installed-tree filename
comparison found the same 45 SDK files whose basenames start with LICENSE,
COPYING or NOTICE (case-insensitive), with no additions/removals. All 85 files
under Xtensa `share/licenses/` have identical relative paths and hashes. This
finite discovery check is not an exhaustive attribution or linked-object audit.
Validation after the catalog edit: **36/36 notice fixture tests PASS** via
`python3 -B tests/release_notices/run.py`; two fresh temporary actual bundles
using the candidate roots above and the current project passed assembly,
per-payload manifest SHA-256/size checks, exact file-list checks, and identical
relative names/bytes across outputs. Each has **77 files / 4,433,930 bytes**
(the unchanged total size does not imply unchanged contents). Both manifest
SHA-256 values are
`1fd0a69b82dc2632bab4ab6dda33675e7229c8d341c7fafb7158c1e8f2456a68`.
Temporary bundles were removed after comparison. Project entries and managed
snapshot pins were also asserted equal to the pre-migration Git catalog.
These results are **notice assembly evidence only**, not legal clearance,
corresponding-source delivery, radio-blob exception approval, vulnerability
closure, firmware build validation or target acceptance. Existing pinned project
provenance retains historical baseline wording; this addendum distinguishes the
new SDK notice snapshot without silently rewriting/re-pinning that evidence.
All separate distribution gates below remain open.
## Separate work before distribution ## Separate work before distribution
1. Resolve or obtain qualified review of radio-blob corresponding-source or 1. Resolve or obtain qualified review of radio-blob corresponding-source or
+7 -5
View File
@@ -216,15 +216,17 @@ Staged work:
1. **9A — Crash/debug build policy and operational profiles — In progress; hardware pending.** `src/security_build_policy.c` requires `CONFIG_ESP_COREDUMP_ENABLE_TO_NONE=y` and `CONFIG_ESP_SYSTEM_PANIC_SILENT_REBOOT=y`; rejects core-dump enable/flash/UART, panic print/halt/GDBstub, runtime GDBstub and ESP/FreeRTOS debugger-aware options. `sdkconfig.defaults` makes the baseline explicit. Development/test/production use the same build baseline, not separate PlatformIO environments. Host matrix (`python3 tests/security_build_policy/run.py`) compiles the actual guard: 17 cases plus the generated-header check passed on 2026-09-15. `pio run` passed (94,196 B linked RAM / 1,828,565 B flash); target panic/recovery tests have not run. Production readiness remains pending. 1. **9A — Crash/debug build policy and operational profiles — In progress; hardware pending.** `src/security_build_policy.c` requires `CONFIG_ESP_COREDUMP_ENABLE_TO_NONE=y` and `CONFIG_ESP_SYSTEM_PANIC_SILENT_REBOOT=y`; rejects core-dump enable/flash/UART, panic print/halt/GDBstub, runtime GDBstub and ESP/FreeRTOS debugger-aware options. `sdkconfig.defaults` makes the baseline explicit. Development/test/production use the same build baseline, not separate PlatformIO environments. Host matrix (`python3 tests/security_build_policy/run.py`) compiles the actual guard: 17 cases plus the generated-header check passed on 2026-09-15. `pio run` passed (94,196 B linked RAM / 1,828,565 B flash); target panic/recovery tests have not run. Production readiness remains pending.
2. **9B — SSH admission and credential handling — Implemented; combined target validation deferred.** Boot-lifetime, owner-only token buckets independently bound handshakes, password/signed-key requests and unsigned probes; reconnect/restart/counter clearing do not replenish them. Existing per-slot attempt limits/currentness remain. Explicit keyboard-interactive rejection, pending-signature result fencing, secret-free admission counters, consumed admin-buffer wipes and fail-closed hidden-prompt overflow/unsupported-byte handling are implemented. Four focused suites (including 35 pinned-vendor control-flow cases), 11 related regressions and `pio run` passed on 2026-09-15: 94,340 B linked RAM / 1,829,925 B flash. Global-budget starvation remains a documented tradeoff, not a solved availability problem. 2. **9B — SSH admission and credential handling — Implemented; combined target validation deferred.** Boot-lifetime, owner-only token buckets independently bound handshakes, password/signed-key requests and unsigned probes; reconnect/restart/counter clearing do not replenish them. Existing per-slot attempt limits/currentness remain. Explicit keyboard-interactive rejection, pending-signature result fencing, secret-free admission counters, consumed admin-buffer wipes and fail-closed hidden-prompt overflow/unsupported-byte handling are implemented. Four focused suites (including 35 pinned-vendor control-flow cases), 11 related regressions and `pio run` passed on 2026-09-15: 94,340 B linked RAM / 1,829,925 B flash. Global-budget starvation remains a documented tradeoff, not a solved availability problem.
3. **9C — Library cleanup and protocol policy — Implemented; combined target validation deferred.** Exact-hash build-tree overrides correct HTTPS cleanup/leaks, HTTPD scratch failure/wiping/first-read handling, bounded SSH password parsing/wiping and server-local TLS policy without modifying installed dependencies. Secure wolfSSL allocation hooks and explicit SSH policy fail closed; early web quota probing avoids receiving already-throttled bodies. TLS1.2 ECDHE-ECDSA AES-GCM and SSH GCM/modern-KEX allowlists intentionally exclude legacy-only clients; no identity migration. Bounded password/certificate/header/destructor review is documented, not exhaustive zeroization. Final build PASS 94,340 B linked RAM / 1,831,309 B flash; focused and related host/source-contract tests passed. [Review and maintenance contract](security_library_review.md). 3. **9C — Library cleanup and protocol policy — Implemented; combined target validation deferred.** Exact-hash build-tree overrides correct HTTPS cleanup/leaks, HTTPD scratch failure/wiping/first-read handling, bounded SSH password parsing/wiping and server-local TLS policy without modifying installed dependencies. Secure wolfSSL allocation hooks and explicit SSH policy fail closed; early web quota probing avoids receiving already-throttled bodies. TLS1.2 ECDHE-ECDSA AES-GCM and SSH GCM/modern-KEX allowlists intentionally exclude legacy-only clients; no identity migration. Bounded password/certificate/header/destructor review is documented, not exhaustive zeroization. Final build PASS 94,340 B linked RAM / 1,831,309 B flash; focused and related host/source-contract tests passed. [Review and maintenance contract](security_library_review.md).
4. **9D — Maintenance and lifecycle — In progress; unresolved security and distribution questions.** [Security operations](security_operations.md) documents provisioning, explicit saves, rotation, recovery, reconstruction (no implemented backup/export) and approved destructive reset/decommissioning. The [wolfSSL/wolfSSH implementation addendum](wolf_security_review.md) records the implemented Xtensa small X25519/Ed25519 mitigation (CVE-2025-12888), with consistent library/consumer flags and a resolved-settings guard; small X25519 is not combined with blinding. Bounded IGNORE/service/string parsing, channel-window overflow, ECC/Ed25519 labels and exact signature framing are now corrected in the existing generated override. [P-256 import and X25519 all-zero-result checks](ssh_key_validation_review.md) are enabled with effective PUBLIC flags confirmed; the [finite remaining SSH parser/caller review](ssh_parser_remaining_review.md) is complete, not exhaustive safety. Exact CHANNEL_FAILURE recipient, `ssh-userauth` service and all nine channel-name checks are implemented; unknown/trailing-payload behavior is retained. PR899 client skips remain unchanged behind server ordering, forwarding is disabled, and the inspected generic caller trace has no short-digest/OID trigger; generic APIs are not globally fixed. **Restricted existing-profile ordering correction (CVE-2025-14942) is implemented**, using audited PR793/819/840/855/921 subsets plus local gates, not a full upstream backport. X25519/P-256 only, independent SELF/PEER bits, expected-message/authentication gates and queued NEWKEYS continuation handle nonblocking writes. **EXT_INFO is intentionally disabled; no `server-sig-algs` is advertised.** Eight C overrides plus one PUBLIC forced ABI header retain hash pins/notices; joined forced-header arguments fix PlatformIO sorting/deduplication. The review's misplaced EOF guard is corrected and verified with no scoped blocker. The [IDF implementation addendum](idf_security_review.md) records pinned DHCP (CVE-2026-45160), TLS 1.2 EMS (CVE-2026-50581) and X.509 allocation-failure (CVE-2026-34874) backports; historical research remains labeled and retained. The [finite IDF applicability completion](idf_security_review.md#finite-applicability-completion--2026-09-16) dispositions all six named findings: ECDH small-output, zero-length ECC PK parse, basicConstraints, server NewSessionTicket and stale ASN.1 length are not current paths/configurations for their documented reasons; optimized ECC reduction is active but its privileged-local/physical side-channel attacker model is excluded. No new current-path correction was established. That earlier review screened only the first advisory-index page. The subsequent [finite dependency coverage](dependency_advisory_coverage.md) accounts for all **22 IDF entries across three pages**, plus named managed-component and browser publication channels. The declared dated search is complete, not exhaustive advisory clearance; unpatched primitives, inaccessible full-release databases and fresh findings remain. 4. **9D — Maintenance and lifecycle — In progress; unresolved security and distribution questions.** [Security operations](security_operations.md) documents provisioning, explicit saves, rotation, recovery, reconstruction (no implemented backup/export) and approved destructive reset/decommissioning. The [wolfSSL/wolfSSH implementation addendum](wolf_security_review.md) records the implemented Xtensa small X25519/Ed25519 mitigation (CVE-2025-12888), with consistent library/consumer flags and a resolved-settings guard; small X25519 is not combined with blinding. Bounded IGNORE/service/string parsing, channel-window overflow, ECC/Ed25519 labels and exact signature framing are now corrected in the existing generated override. [P-256 import and X25519 all-zero-result checks](ssh_key_validation_review.md) are enabled with effective PUBLIC flags confirmed; the [finite remaining SSH parser/caller review](ssh_parser_remaining_review.md) is complete, not exhaustive safety. Exact CHANNEL_FAILURE recipient, `ssh-userauth` service and all nine channel-name checks are implemented; unknown/trailing-payload behavior is retained. PR899 client skips remain unchanged behind server ordering, forwarding is disabled, and the inspected generic caller trace has no short-digest/OID trigger; generic APIs are not globally fixed. **Restricted existing-profile ordering correction (CVE-2025-14942) is implemented**, using audited PR793/819/840/855/921 subsets plus local gates, not a full upstream backport. X25519/P-256 only, independent SELF/PEER bits, expected-message/authentication gates and queued NEWKEYS continuation handle nonblocking writes. **EXT_INFO is intentionally disabled; no `server-sig-algs` is advertised.** Nine C overrides (including the new IDF 5.5.3 WS signed-size correction) plus one PUBLIC forced ABI header retain hash pins/notices; joined forced-header arguments fix PlatformIO sorting/deduplication. The review's misplaced EOF guard is corrected and verified with no scoped blocker. The [IDF implementation addendum](idf_security_review.md) records pinned DHCP (CVE-2026-45160), TLS 1.2 EMS (CVE-2026-50581) and X.509 allocation-failure (CVE-2026-34874) backports; historical research remains labeled and retained. The [finite IDF applicability completion](idf_security_review.md#finite-applicability-completion--2026-09-16) dispositions all six named findings: ECDH small-output, zero-length ECC PK parse, basicConstraints, server NewSessionTicket and stale ASN.1 length are not current paths/configurations for their documented reasons; optimized ECC reduction is active but its privileged-local/physical side-channel attacker model is excluded. No new current-path correction was established. That earlier review screened only the first advisory-index page. The subsequent [finite dependency coverage](dependency_advisory_coverage.md) accounts for all **22 IDF entries across three pages**, plus named managed-component and browser publication channels. The declared dated search is complete, not exhaustive advisory clearance; unpatched primitives, inaccessible full-release databases and fresh findings remain.
**Latest integration evidence:** supplied parent build PASS: **94,340 B linked RAM / 1,768,901 B flash**, unchanged RAM / **+200 B flash** versus 1,768,701 B. Final parent **all seven suites PASS**: ordering `--interop` (8,028 checks, seven rejected mutations, 12 exact-256-KiB sessions with clean channel close/transport EOF), SDK overrides with actual `--build-dir` registration, auth (135), protocol, strict crypto, notices (30), and parser (3,258 × two modes plus channel profiles; 11 + 18 + 2 rejected mutations). Interop required unsandboxed approval only for local AF_UNIX sockets; no remote network/device operation. Agent ordering evidence: **8,028 checks / seven rejected mutations**. Initial interop failed a harness close race despite early PASS output; the corrected harness waits for peer close/transport EOF and independently reaps the server through an fd-passing proxy. Final agent repeated OpenSSH matrix: **36/36 sessions**, each exact **256 KiB**, **two or ten** completed key exchanges and clean client/server exits; see [evidence and limits](security_hardening.md#ordering-hostbuild-evidence--2026-09-16). **Historical IDF 5.5.0 integration evidence:** supplied parent build PASS: **94,340 B linked RAM / 1,768,901 B flash**, unchanged RAM / **+200 B flash** versus 1,768,701 B. Final parent **all seven suites PASS**: ordering `--interop` (8,028 checks, seven rejected mutations, 12 exact-256-KiB sessions with clean channel close/transport EOF), SDK overrides with actual `--build-dir` registration, auth (135), protocol, strict crypto, notices (30), and parser (3,258 × two modes plus channel profiles; 11 + 18 + 2 rejected mutations). Interop required unsandboxed approval only for local AF_UNIX sockets; no remote network/device operation. Agent ordering evidence: **8,028 checks / seven rejected mutations**. Initial interop failed a harness close race despite early PASS output; the corrected harness waits for peer close/transport EOF and independently reaps the server through an fd-passing proxy. Final agent repeated OpenSSH matrix: **36/36 sessions**, each exact **256 KiB**, **two or ten** completed key exchanges and clean client/server exits; see [evidence and limits](security_hardening.md#ordering-hostbuild-evidence--2026-09-16).
**Release status:** the [bounded license inventory](dependency_licenses.md) marks modification/date notices resolved for all eight generated C files plus one header (baseline 2026-09-15 and wolfSSH ordering 2026-09-16; upstream licenses retained), with archived patch hashes/commits and patch license context recorded, but radio-blob corresponding-source/exception, source/notice delivery and provenance questions remain open. [Offline notice assembly](release_packaging.md) is implemented by [tools/release_notices.py](../tools/release_notices.py): 75 mandatory hash/size-pinned inputs, deterministic bounded outputs, fail-closed preflight and no overwrite/fetch/build/device access. Supplied agent evidence: **36 notice fixture tests PASS; two actual bundles verified deterministic, each 77 files / 4,433,930 bytes**. This includes retained SVGs and [exact icon provenance](icon_provenance.md), not corresponding-source delivery or legal clearance. These are agent measurements, not a parent rebuild or reassembly in this documentation turn. **Release status:** the [bounded license inventory](dependency_licenses.md) marks modification/date notices resolved for all nine generated C files plus one header (baseline 2026-09-15 and wolfSSH ordering 2026-09-16; upstream licenses retained), with archived patch hashes/commits and patch license context recorded, but radio-blob corresponding-source/exception, source/notice delivery and provenance questions remain open. [Offline notice assembly](release_packaging.md) is implemented by [tools/release_notices.py](../tools/release_notices.py): 75 mandatory hash/size-pinned inputs (nine IDF/toolchain notice entries semantically rebased for the candidate; the other 66 unchanged), deterministic bounded outputs, fail-closed preflight and no overwrite/fetch/build/device access. Supplied agent evidence: **36 notice fixture tests PASS; two actual bundles verified deterministic, each 77 files / 4,433,930 bytes**. This includes retained SVGs and [exact icon provenance](icon_provenance.md), not corresponding-source delivery or legal clearance. These are agent measurements, not a parent rebuild or reassembly in this documentation turn.
**Remaining maintenance/target gates:** official registry wolfSSH 1.5.0 / wolfSSL 5.9.2 queries returned 404 on 2026-09-15; upstream tags exist but managed compatibility is not established. The [remaining ordering gates and immutable provenance](wolf_security_review.md#restricted-ordering-correction-and-remaining-gates) retain target cleanup during rekey, intended-client compatibility without EXT_INFO and whole-phase resource/timing validation. Future upstream adoption still requires isolated packaging/compatibility evaluation. Upgrades/backports require coherent source/header review, override rebasing and contract tests, not blind repinning. The [declared finite dependency search](dependency_advisory_coverage.md) is complete; fresh findings and explicit coverage gaps remain. **Current PMF-capable SoftAP security blocker: not fixed.** Follow the [Wi-Fi security update plan](wifi_security_update_plan.md): select a coherent vendor framework/toolchain and source/header/binary ABI bundle, rebase exact security overrides and validate on target; do not transplant one archive, edit only the callback signature or weaken PMF/WPA3. No dependency upgrade was performed. **Phase 9 is not complete or production-ready; scoped mitigations do not establish full advisory closure, device validation or license/distribution clearance.** OTA signing trust remains separate Phase 10 work. **Remaining maintenance/target gates:** official registry wolfSSH 1.5.0 / wolfSSL 5.9.2 queries returned 404 on 2026-09-15; upstream tags exist but managed compatibility is not established. The [remaining ordering gates and immutable provenance](wolf_security_review.md#restricted-ordering-correction-and-remaining-gates) retain target cleanup during rekey, intended-client compatibility without EXT_INFO and whole-phase resource/timing validation. Future upstream adoption still requires isolated packaging/compatibility evaluation. Upgrades/backports require coherent source/header review, override rebasing and contract tests, not blind repinning. The [declared finite dependency search](dependency_advisory_coverage.md) is complete; fresh findings and explicit coverage gaps remain. **The fix-bearing Wi-Fi vendor bundle is integrated in the validated IDF 5.5.3 candidate; radio-hardware closure remains pending.** Follow the [Wi-Fi security update plan](wifi_security_update_plan.md) for target acceptance; do not transplant one archive, edit only the callback signature or weaken PMF/WPA3. Root migration pins are edited; complete immutable dependency closure and a successful normal root build are not established. **Phase 9 is not complete or production-ready; scoped mitigations do not establish full advisory closure, device validation or license/distribution clearance.** OTA signing trust remains separate Phase 10 work.
**Bounded host validation:** [tools/validate_phase9.py](../tools/validate_phase9.py) orchestrates **23 commands** by default, with no build or OpenSSH interop unless separately opted in. The stale console-boundary anchor was fixed by its owning agent; supplied follow-up agent result is **23/23 PASS**. Final supplied parent execution: `CCACHE_DISABLE=1 python3 -B tools/validate_phase9.py` **PASS 23/23**; `tests/phase9_validation/run.py` **PASS 10 tests**. No build or interop ran this turn. See [usage and execution boundaries](phase9_validation.md). No parent rebuild this turn; firmware source is unchanged. Historical build/interop results above are not fresh target or release evidence. **Current IDF 5.5.3 integration:** fresh isolated `app-validated` build PASS, **95,552 B linked RAM / 1,749,493 B flash** (**+1,212 / 19,408 B** versus historical 94,340 / 1,768,901 B). Root selects PlatformIO 6.13.0 / framework 3.50503.0 / both toolchains 14.2.0+20251107. Default root `pio run` timed out after 200 seconds during installation, before compilation. All nine C overrides plus the header have actual-build registration evidence; generated WS tests pass **982 cases / 10 mutation checks**. See [integration and remaining gates](idf_candidate_integration.md).
**Bounded host validation:** 23 commands by default; build, OpenSSH interop and web performance are explicit options. Historical default 23/23 and 10 orchestrator fixtures passed on the earlier snapshot. Current supplied parent validation selects the isolated IDF 5.5.3 build/core/SDK, with `--interop --web-performance`: **PASS 24/24**. See [exact command, source equality and evidence limits](phase9_validation.md#current-candidate-execution--2026-09-18). This does not claim a successful default root build or whole-phase acceptance.
At the user's request, hardware validation is deferred to **Phase 9 as a whole**, not required between implementation slices. [Security hardening](security_hardening.md) collects profiles, host evidence and the combined target checklist. Silent panic reboot removes useful crash diagnostics, not ordinary reset/boot/status information or every possible log disclosure. Raw flash/RAM/dumps remain secret-bearing, not routine diagnostic exports. Existing coredump bytes are not retroactively cleared; no secure erase is claimed. Isolated synthetic-secret debug builds require explicit reviewed source-policy changes, not a provided bypass flag. At the user's request, hardware validation is deferred to **Phase 9 as a whole**, not required between implementation slices. [Security hardening](security_hardening.md) collects profiles, host evidence and the combined target checklist. Silent panic reboot removes useful crash diagnostics, not ordinary reset/boot/status information or every possible log disclosure. Raw flash/RAM/dumps remain secret-bearing, not routine diagnostic exports. Existing coredump bytes are not retroactively cleared; no secure erase is claimed. Isolated synthetic-secret debug builds require explicit reviewed source-policy changes, not a provided bypass flag.
+16 -6
View File
@@ -2,6 +2,16 @@
**Status: in progress.** Phase 8 is complete at the accepted 8D.22 scope. **9A crash/debug policy, 9B SSH admission/credential handling and 9C library cleanup/protocol policy** are implemented with host/build validation. At the user's request, hardware validation is deferred to **Phase 9 as a whole**, not an approval gate between implementation slices. **9D maintenance/lifecycle is in progress, with unresolved advisory and distribution/source/notice questions. Phase 9 is not complete or production-ready.** This document records policy and procedures, not unrun passes or production certification. **Status: in progress.** Phase 8 is complete at the accepted 8D.22 scope. **9A crash/debug policy, 9B SSH admission/credential handling and 9C library cleanup/protocol policy** are implemented with host/build validation. At the user's request, hardware validation is deferred to **Phase 9 as a whole**, not an approval gate between implementation slices. **9D maintenance/lifecycle is in progress, with unresolved advisory and distribution/source/notice questions. Phase 9 is not complete or production-ready.** This document records policy and procedures, not unrun passes or production certification.
## Current migration evidence — 2026-09-18
Root `platformio.ini` now pins **PlatformIO espressif32 6.13.0 / framework-espidf 3.50503.0 (IDF 5.5.3)** and both Xtensa and RISC-V toolchains at **14.2.0+20251107**. These are exact version pins, not enforcement of archive hashes or a complete immutable build closure. The [candidate integration record](idf_candidate_integration.md) distinguishes the verified archive identities from the root configuration.
Supplied parent evidence: a **fresh isolated application build PASS** in `.pio/idf-candidate-5.5.3/app-validated`, with **95,552 B linked RAM / 1,749,493 B flash**, versus historical **94,340 / 1,768,901 B**: **+1,212 B RAM / 19,408 B flash**. The default root `pio run` timed out after **200 seconds during installation, before compilation**; it is **not a normal root build PASS**. Final explicit-candidate Phase 9 validation with interop and web performance **PASS 24/24**; see [command and snapshot evidence](phase9_validation.md#current-candidate-execution--2026-09-18). Source equality covered **3,237 files**, pre/post SHA-256 **`3a1af78c15cfdd02da1055a8957b4086f9018862b7aa1c7c52fd2401a1a0a031`**.
All **nine generated C sources plus one forced header** are validated as actual compilation inputs. The new WS correction makes five fixed-header `sizeof` comparisons signed; **982 behavioral cases / 10 mutation checks** cover generated WS receive behavior. The stale web-cookie IDF 5.5.0 fixture was corrected. [Rebase review](idf_553_rebase_review.md) retains old findings as history, not current blockers, and records all packaged Wi-Fi/PHY/coexistence/supplicant files matching the locked framework archive.
The fix-bearing vendor Wi-Fi bundle is integrated in the validated candidate, **not radio-hardware vulnerability closure**. PMF/WPA3 is unchanged. Whole-phase target/radio/resource/recovery acceptance, complete dependency closure and release/source/legal gates remain pending; **Phase 9 is not complete or production-ready**. These are supplied parent results, not builds or hardware tests rerun by this documentation update.
## Scope and threat model ## 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. 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.
@@ -71,14 +81,14 @@ Hidden UART0 and shared remote-console prompts now reject overflow or unsupporte
### Reproducible source corrections ### Reproducible source corrections
`tools/security_overrides.py` verifies full original-file SHA-256 values and ESP-IDF 5.5.0, applies exact-once edits, and generates eight corrected C sources plus one header under the build directory (the original four 9C sources, three IDF advisory sources, and the additional wolfSSH `ssh.c`/`internal.h` ordering inputs). `cmake/security_overrides.cmake`, included after `project()`, replaces exactly the corresponding sources in existing IDF/component targets, retaining compilation properties. Installed SDK/managed sources and their notices remain unchanged. Missing, changed or ambiguous sources fail configuration; there is no unpatched fallback. Do not edit derived files or repin a hash merely to make an upgrade build. `tools/security_overrides.py` verifies full original-file SHA-256 values and ESP-IDF 5.5.3, applies exact-once edits, and generates nine corrected C sources plus one header under the build directory (the original four 9C sources, three IDF advisory sources, the additional wolfSSH `ssh.c`/`internal.h` ordering inputs, and the IDF 5.5.3 WS signed-receive correction). `cmake/security_overrides.cmake`, included after `project()`, replaces exactly the corresponding sources in existing IDF/component targets, retaining compilation properties. Installed SDK/managed sources and their notices remain unchanged. Missing, changed or ambiguous sources fail configuration; there is no unpatched fallback. Do not edit derived files or repin a hash merely to make an upgrade build.
- **HTTPS:** delete TLS on post-handshake transport-allocation failure; fully destroy retained TLS configuration on failed HTTPD start; wipe the copied raw private key before free. Failed stop still retains live ownership. - **HTTPS:** delete TLS on post-handshake transport-allocation failure; fully destroy retained TLS configuration on failed HTTPD start; wipe the copied raw private key before free. Failed stop still retains live ownership.
- **HTTPD parser:** allocate/copy/wipe/free scratch on resize, preserve the old pointer on allocation failure, wipe final scratch, and handle the null initial parser pointer without undefined subtraction. Pending/unread bytes retain their existing behavior. - **HTTPD parser:** allocate/copy/wipe/free scratch on resize, preserve the old pointer on allocation failure, wipe final scratch, and handle the null initial parser pointer without undefined subtraction. Pending/unread bytes retain their existing behavior.
- **wolfSSH password parser:** bound both password lengths against the actual packet before application callbacks, reject malformed change-password fields without calling authentication, and wipe the bounded method-specific payload suffix before failure responses. Username/service/method prefixes remain intact. The current project callbacks are synchronous; library `WS_AUTH_PENDING` retains the payload for retry and is not claimed wiped. - **wolfSSH password parser:** bound both password lengths against the actual packet before application callbacks, reject malformed change-password fields without calling authentication, and wipe the bounded method-specific payload suffix before failure responses. Username/service/method prefixes remain intact. The current project callbacks are synchronous; library `WS_AUTH_PENDING` retains the payload for retry and is not claimed wiped.
- **ESP-TLS server configuration:** enforce the static-lifetime TLS list below before handshake setup; client defaults and global cryptographic primitives remain unchanged. IDF dynamic TLS buffers are rejected because their cleanup bypasses the reviewed upstream record-buffer wipe. - **ESP-TLS server configuration:** enforce the static-lifetime TLS list below before handshake setup; client defaults and global cryptographic primitives remain unchanged. IDF dynamic TLS buffers are rejected because their cleanup bypasses the reviewed upstream record-buffer wipe.
The new `src/ssh_memory.{c,h}` wolfSSL/wolfCrypt allocation hooks wipe the full owned usable allocation before release, including library import-failure and dynamic packet-buffer copies. They require the reviewed unpoisoned IDF 5.5.0 heap configuration; poisoning modes fail compilation rather than risking canary writes. No allocation header is added. Shrink retains capacity and wipes the tail; growth allocates/copies before wiping/freeing the old block, preserving it on allocation failure. PSRAM preference/internal fallback is unchanged. **Growth and HTTPD scratch resizing temporarily need both blocks; lower linked size is not evidence of safe runtime headroom.** Live inline buffers, in-place compaction tails, stack spills and every crypto intermediate are not comprehensively covered. The new `src/ssh_memory.{c,h}` wolfSSL/wolfCrypt allocation hooks wipe the full owned usable allocation before release, including library import-failure and dynamic packet-buffer copies. They require the reviewed unpoisoned IDF 5.5.3 heap configuration; poisoning modes fail compilation rather than risking canary writes. No allocation header is added. Shrink retains capacity and wipes the tail; growth allocates/copies before wiping/freeing the old block, preserving it on allocation failure. PSRAM preference/internal fallback is unchanged. **Growth and HTTPD scratch resizing temporarily need both blocks; lower linked size is not evidence of safe runtime headroom.** Live inline buffers, in-place compaction tails, stack spills and every crypto intermediate are not comprehensively covered.
### Explicit network protocol policy ### Explicit network protocol policy
@@ -107,7 +117,7 @@ The existing global five-verifications/60-second fixed window is unchanged: malf
- [Security operations](security_operations.md) supplies source-checked provisioning, explicit-save, account/key/identity rotation, recovery, reconstruction and approved erase/decommissioning procedures. There is no implemented configuration backup/export or private-identity restore workflow. Commands and checklists are not device execution evidence; the user will validate Phase 9 as a whole. - [Security operations](security_operations.md) supplies source-checked provisioning, explicit-save, account/key/identity rotation, recovery, reconstruction and approved erase/decommissioning procedures. There is no implemented configuration backup/export or private-identity restore workflow. Commands and checklists are not device execution evidence; the user will validate Phase 9 as a whole.
- [wolfSSL/wolfSSH review and implementation addendum](wolf_security_review.md): the **CVE-2025-12888** Xtensa mitigation now selects `CURVE25519_SMALL` and `ED25519_SMALL` consistently for wolfSSL and consumers. The PUBLIC forced-include resolved-settings guard rejects missing small implementations, X25519 blinding (incompatible with small in this pinned version), and unreviewed Curve448/Ed448 enablement. The existing generated wolfSSH override now bounds IGNORE/service strings, rejects zero-capacity string output and channel-window overflow, corrects ECC/Ed25519 key/signature labels, and enforces exact signature framing (ECC nested r/s bounds plus inner/outer consumption; Ed25519 outer consumption). These are PR892/881/880 subsets plus local framing corrections, not full backports; password wiping/async retention remain unchanged. PUBLIC `WOLFSSL_VALIDATE_ECC_IMPORT` and `WOLFSSL_ECDHX_SHARED_NOT_ZERO` now enable P-256 import validation and X25519 all-zero-result rejection, with effective production flags confirmed and fail-closed backend guards. See [key-validation evidence](ssh_key_validation_review.md) and [parser scope/limits](../tests/wolfssh_parser_contract/README.md). The restricted existing-profile correction for **CVE-2025-14942** is implemented as described below. The [finite remaining SSH review](ssh_parser_remaining_review.md) is complete: bounded exact CHANNEL_FAILURE recipient parsing (fatal policy retained), exact `ssh-userauth` service validation, and exact length/byte dispatch for all nine channel-request names are implemented. Unknown-request/trailing-payload behavior is preserved. PR899 client key skips are unchanged and blocked by current server role/ordering; PR918/919 forwarding is disabled. The inspected generic signature caller trace found no attacker-selected short-digest/OID path; generic APIs remain unpatched. These are profile-specific dispositions, not exhaustive parser/library safety; revisit on caller, feature, KEX or source changes. No exploit or whole-library clearance is demonstrated; added validation CPU/allocation cost and target interoperability remain unmeasured. - [wolfSSL/wolfSSH review and implementation addendum](wolf_security_review.md): the **CVE-2025-12888** Xtensa mitigation now selects `CURVE25519_SMALL` and `ED25519_SMALL` consistently for wolfSSL and consumers. The PUBLIC forced-include resolved-settings guard rejects missing small implementations, X25519 blinding (incompatible with small in this pinned version), and unreviewed Curve448/Ed448 enablement. The existing generated wolfSSH override now bounds IGNORE/service strings, rejects zero-capacity string output and channel-window overflow, corrects ECC/Ed25519 key/signature labels, and enforces exact signature framing (ECC nested r/s bounds plus inner/outer consumption; Ed25519 outer consumption). These are PR892/881/880 subsets plus local framing corrections, not full backports; password wiping/async retention remain unchanged. PUBLIC `WOLFSSL_VALIDATE_ECC_IMPORT` and `WOLFSSL_ECDHX_SHARED_NOT_ZERO` now enable P-256 import validation and X25519 all-zero-result rejection, with effective production flags confirmed and fail-closed backend guards. See [key-validation evidence](ssh_key_validation_review.md) and [parser scope/limits](../tests/wolfssh_parser_contract/README.md). The restricted existing-profile correction for **CVE-2025-14942** is implemented as described below. The [finite remaining SSH review](ssh_parser_remaining_review.md) is complete: bounded exact CHANNEL_FAILURE recipient parsing (fatal policy retained), exact `ssh-userauth` service validation, and exact length/byte dispatch for all nine channel-request names are implemented. Unknown-request/trailing-payload behavior is preserved. PR899 client key skips are unchanged and blocked by current server role/ordering; PR918/919 forwarding is disabled. The inspected generic signature caller trace found no attacker-selected short-digest/OID path; generic APIs remain unpatched. These are profile-specific dispositions, not exhaustive parser/library safety; revisit on caller, feature, KEX or source changes. No exploit or whole-library clearance is demonstrated; added validation CPU/allocation cost and target interoperability remain unmeasured.
- [Focused IDF review and implementation addendum](idf_security_review.md): pinned backports now implement DHCP option bounds (**CVE-2026-45160**), TLS 1.2 EMS failure return (**CVE-2026-50581**) and X.509 OID allocation-failure handling (**CVE-2026-34874**). Explicit nested-target validation places the mbedTLS edits on `mbedtls`/`mbedx509`, retaining source properties and exactly-one-source checks. WS negotiation **CVE-2026-45541** and ASN.1 named-data **CVE-2025-48965** remain unpatched with the review's qualified applicability, not blanket closure. The [finite IDF applicability completion](idf_security_review.md#finite-applicability-completion--2026-09-16) dispositions all six named findings: ECDH small-output, zero-length ECC PK parse, basicConstraints, server NewSessionTicket and stale ASN.1 length are not current paths/configurations for their documented reasons; optimized ECC reduction is active but its privileged-local/physical side-channel attacker model is excluded. No new current-path correction was established. That earlier review screened only the first advisory-index page. The subsequent [finite dependency coverage](dependency_advisory_coverage.md) accounts for all **22 IDF entries across three pages**, plus named managed-component and browser publication channels. The declared dated search is complete, not exhaustive advisory clearance; unpatched primitives, inaccessible full-release databases and fresh findings remain. - [Focused IDF review and implementation addendum](idf_security_review.md): pinned backports now implement DHCP option bounds (**CVE-2026-45160**), TLS 1.2 EMS failure return (**CVE-2026-50581**) and X.509 OID allocation-failure handling (**CVE-2026-34874**). Explicit nested-target validation places the mbedTLS edits on `mbedtls`/`mbedx509`, retaining source properties and exactly-one-source checks. WS negotiation **CVE-2026-45541** and ASN.1 named-data **CVE-2025-48965** remain unpatched with the review's qualified applicability, not blanket closure. The [finite IDF applicability completion](idf_security_review.md#finite-applicability-completion--2026-09-16) dispositions all six named findings: ECDH small-output, zero-length ECC PK parse, basicConstraints, server NewSessionTicket and stale ASN.1 length are not current paths/configurations for their documented reasons; optimized ECC reduction is active but its privileged-local/physical side-channel attacker model is excluded. No new current-path correction was established. That earlier review screened only the first advisory-index page. The subsequent [finite dependency coverage](dependency_advisory_coverage.md) accounts for all **22 IDF entries across three pages**, plus named managed-component and browser publication channels. The declared dated search is complete, not exhaustive advisory clearance; unpatched primitives, inaccessible full-release databases and fresh findings remain.
- [Dependency license inventory](dependency_licenses.md) is a bounded engineering inventory, not legal or distribution clearance. All **eight generated C sources plus one header** carry prominent modification notices: the baseline **2026-09-15** notice plus **2026-09-16** ordering/provenance notices on wolfSSH outputs, with upstream notices retained, including both mbedTLS dual-license headers. That narrow finding is resolved. Radio-blob corresponding-source/exception questions, actual firmware/device/browser notice delivery, preferred-source packaging, wolfSSH package-license discrepancy and manual bitmap derivation, the distinct mockup Wi-Fi path and recipient license delivery remain open; [exact retained SVG provenance](icon_provenance.md) is resolved. Notice assembly does not prove recipient delivery. [Offline notice assembly](release_packaging.md) is implemented by [tools/release_notices.py](../tools/release_notices.py): 75 mandatory hash/size-pinned inputs, deterministic bounded outputs, fail-closed preflight and no overwrite/fetch/build/device access. Supplied agent evidence: **36 notice fixture tests PASS; two actual bundles verified deterministic, each 77 files / 4,433,930 bytes**. This includes retained SVGs and [exact icon provenance](icon_provenance.md), not corresponding-source delivery or legal clearance. These are agent measurements, not a parent rebuild or reassembly in this documentation turn. - [Dependency license inventory](dependency_licenses.md) is a bounded engineering inventory, not legal or distribution clearance. All **nine generated C sources plus one header** carry prominent modification notices: the baseline **2026-09-15** notice plus **2026-09-16** ordering/provenance notices on wolfSSH outputs, with upstream notices retained, including both mbedTLS dual-license headers. That narrow finding is resolved. Radio-blob corresponding-source/exception questions, actual firmware/device/browser notice delivery, preferred-source packaging, wolfSSH package-license discrepancy and manual bitmap derivation, the distinct mockup Wi-Fi path and recipient license delivery remain open; [exact retained SVG provenance](icon_provenance.md) is resolved. Notice assembly does not prove recipient delivery. [Offline notice assembly](release_packaging.md) is implemented by [tools/release_notices.py](../tools/release_notices.py): 75 mandatory hash/size-pinned inputs (nine IDF/toolchain notice entries semantically rebased for the candidate; the other 66 unchanged), deterministic bounded outputs, fail-closed preflight and no overwrite/fetch/build/device access. Supplied agent evidence: **36 notice fixture tests PASS; two actual bundles verified deterministic, each 77 files / 4,433,930 bytes**. This includes retained SVGs and [exact icon provenance](icon_provenance.md), not corresponding-source delivery or legal clearance. These are agent measurements, not a parent rebuild or reassembly in this documentation turn.
- **Restricted ordering correction implemented, not a full upstream backport or sign-off.** The [ordering review](wolf_security_review.md#restricted-ordering-correction-and-remaining-gates) and [provenance/prerequisite disposition](../tools/wolfssh_order/README.md) document the audited PR793/819/840/855/921 subsets plus local gates. Existing X25519/P-256 KEX only; independent SELF/PEER state, exact expected replies and authentication-phase checks cover both roles. Queued NEWKEYS survives WANT_WRITE without duplication. **EXT_INFO is deliberately disabled; no `server-sig-algs` is sent**, and `extInfoSent` stays zero. CMake applies the generated ABI header BEFORE PUBLIC and via a PUBLIC forced include; joined `-include/path` flags fix PlatformIO sorting/deduplication for ordering and crypto guards. Review's misplaced EOF guard is corrected before channel mutation; verification found no scoped blocker. Target cleanup during rekey and no-EXT_INFO client compatibility remain pending. - **Restricted ordering correction implemented, not a full upstream backport or sign-off.** The [ordering review](wolf_security_review.md#restricted-ordering-correction-and-remaining-gates) and [provenance/prerequisite disposition](../tools/wolfssh_order/README.md) document the audited PR793/819/840/855/921 subsets plus local gates. Existing X25519/P-256 KEX only; independent SELF/PEER state, exact expected replies and authentication-phase checks cover both roles. Queued NEWKEYS survives WANT_WRITE without duplication. **EXT_INFO is deliberately disabled; no `server-sig-algs` is sent**, and `extInfoSent` stays zero. CMake applies the generated ABI header BEFORE PUBLIC and via a PUBLIC forced include; joined `-include/path` flags fix PlatformIO sorting/deduplication for ordering and crypto guards. Review's misplaced EOF guard is corrected before channel mutation; verification found no scoped blocker. Target cleanup during rekey and no-EXT_INFO client compatibility remain pending.
- No dependency versions were upgraded. Beyond the implemented backports above, proposed upgrades/backports remain candidates, not approved compatible versions. Re-audit coherent source/header changes, effective compile policy, exact-hash overrides and callback/parser contracts, then obtain host/build and whole-phase target evidence. 9A9C passes below are historical scoped evidence, not closure of these newly recorded findings. - No dependency versions were upgraded. Beyond the implemented backports above, proposed upgrades/backports remain candidates, not approved compatible versions. Re-audit coherent source/header changes, effective compile policy, exact-hash overrides and callback/parser contracts, then obtain host/build and whole-phase target evidence. 9A9C passes below are historical scoped evidence, not closure of these newly recorded findings.
@@ -206,15 +216,15 @@ Retain these checks for the user's final whole-phase test session; do not stop i
4. Repeatedly start/stop HTTPS and SSH and stress header parsing/authentication under the full transport mix. Capture internal/DMA/PSRAM free/minimum/largest-block and stack margins alongside serial/broker loss counters. Specifically measure old-plus-new allocation peaks and secure-free CPU cost; previous very low internal minima remain important. 4. Repeatedly start/stop HTTPS and SSH and stress header parsing/authentication under the full transport mix. Capture internal/DMA/PSRAM free/minimum/largest-block and stack margins alongside serial/broker loss counters. Specifically measure old-plus-new allocation peaks and secure-free CPU cost; previous very low internal minima remain important.
5. Verify exhausted web login returns early without stalled-body work, clears the used pre-login challenge, and recovers after the documented window. Check malformed requests below quota and correct credentials for normal behavior; do not infer fairness from a rate-limit pass. 5. Verify exhausted web login returns early without stalled-body work, clears the used pre-login challenge, and recovers after the documented window. Check malformed requests below quota and correct credentials for normal behavior; do not infer fairness from a rate-limit pass.
**Bounded host validation:** [tools/validate_phase9.py](../tools/validate_phase9.py) orchestrates **23 commands** by default, with no build or OpenSSH interop unless separately opted in. The stale console-boundary anchor was fixed by its owning agent; supplied follow-up agent result is **23/23 PASS**. Final supplied parent execution: `CCACHE_DISABLE=1 python3 -B tools/validate_phase9.py` **PASS 23/23**; `tests/phase9_validation/run.py` **PASS 10 tests**. No build or interop ran this turn. See [usage and execution boundaries](phase9_validation.md). No parent rebuild this turn; firmware source is unchanged. Historical build/interop results above are not fresh target or release evidence. **Bounded host validation:** 23 commands by default; build, OpenSSH interop and web performance are explicit options. Historical default 23/23 and 10 orchestrator fixtures passed on the earlier snapshot. Current supplied parent validation selects the isolated IDF 5.5.3 build/core/SDK, with `--interop --web-performance`: **PASS 24/24**. See [exact command, source equality and evidence limits](phase9_validation.md#current-candidate-execution--2026-09-18). This does not claim a successful default root build or whole-phase acceptance.
## Staged next work ## Staged next work
- **Implementation/maintenance gate:** the finite SSH and six-finding IDF reviews are complete for their stated profiles; do not re-list them as unimplemented. The [declared finite advisory search](dependency_advisory_coverage.md) is complete. Resolve the **unfixed PMF-capable SoftAP security blocker** through the [coherent vendor framework/ABI update plan](wifi_security_update_plan.md), retaining PMF/WPA3, exact override guards and fresh production-source/target tests. Residual direct-SDK AES-DMA applicability and publication-channel gaps remain; recheck the bounded snapshot before release. Optional ASN.1/ECDH/basicConstraints defense-in-depth backports are not implemented or required by a demonstrated current-path finding. - **Implementation/maintenance gate:** the finite SSH and six-finding IDF reviews are complete for their stated profiles; do not re-list them as unimplemented. The [declared finite advisory search](dependency_advisory_coverage.md) is complete. The fix-bearing vendor bundle is integrated in the validated IDF 5.5.3 candidate; complete the [remaining Wi-Fi target gates](wifi_security_update_plan.md), retaining PMF/WPA3 and exact override guards. Integration is not radio-hardware closure. Residual direct-SDK AES-DMA applicability and publication-channel gaps remain; recheck the bounded snapshot before release. Optional ASN.1/ECDH/basicConstraints defense-in-depth backports are not implemented or required by a demonstrated current-path finding.
- **Release gate:** notice assembly is implemented; validate actual firmware/device/browser delivery, corresponding source and preferred asset sources, radio-blob legal basis, wolfSSH packaging clarification, remaining manual bitmap/mockup provenance, final runtime/bootloader attribution and any Installation Information. See [packaging gates](release_packaging.md#separate-work-before-distribution). - **Release gate:** notice assembly is implemented; validate actual firmware/device/browser delivery, corresponding source and preferred asset sources, radio-blob legal basis, wolfSSH packaging clarification, remaining manual bitmap/mockup provenance, final runtime/bootloader attribution and any Installation Information. See [packaging gates](release_packaging.md#separate-work-before-distribution).
- **Target/acceptance gate:** rehearse operations and the combined target checklist above, including panic/recovery, abuse/isolation, cleanup during rekey, intended-client compatibility without EXT_INFO, loaded KEX/rekey and heap/stack/CPU timing. Obtain explicit whole-phase acceptance; no scoped reviewer or host PASS can substitute. - **Target/acceptance gate:** rehearse operations and the combined target checklist above, including panic/recovery, abuse/isolation, cleanup during rekey, intended-client compatibility without EXT_INFO, loaded KEX/rekey and heap/stack/CPU timing. Obtain explicit whole-phase acceptance; no scoped reviewer or host PASS can substitute.
- **Continue 9D maintenance and lifecycle.** Execute the [remaining ordering gates and advisory work](wolf_security_review.md#restricted-ordering-correction-and-remaining-gates), implement and validate the coherent Wi-Fi correction identified by the completed finite dependency search, and revisit its explicit residual questions, and address the [release source/notice work](dependency_licenses.md#actionable-release-work-not-performed). Runbooks are documented, not rehearsed; remaining mitigations/reviews, distribution clearance and whole-phase acceptance remain outstanding. - **Continue 9D maintenance and lifecycle.** Execute the [remaining ordering gates and advisory work](wolf_security_review.md#restricted-ordering-correction-and-remaining-gates), target-validate the integrated coherent Wi-Fi correction identified by the completed finite dependency search, and revisit its explicit residual questions, and address the [release source/notice work](dependency_licenses.md#actionable-release-work-not-performed). Runbooks are documented, not rehearsed; remaining mitigations/reviews, distribution clearance and whole-phase acceptance remain outstanding.
- **Retained evidence limits:** 9C completes a bounded cleanup/protocol review, not every-library-copy zeroization. Live inline residue, compaction tails, hardware/stack intermediates, global admission starvation and resource/interop measurements remain documented limitations or combined target gates. Any additional hardening must preserve owner lifetimes and bounded recovery. - **Retained evidence limits:** 9C completes a bounded cleanup/protocol review, not every-library-copy zeroization. Live inline residue, compaction tails, hardware/stack intermediates, global admission starvation and resource/interop measurements remain documented limitations or combined target gates. Any additional hardening must preserve owner lifetimes and bounded recovery.
- **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. - **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.
+20 -18
View File
@@ -1,12 +1,12 @@
# Wi-Fi management-frame security update plan # Wi-Fi management-frame security update plan
Review date: **2026-09-16**. Focused documentation-only follow-up to [dependency advisory coverage](dependency_advisory_coverage.md#new-release-note-finding-wi-fi-management-frame-corruption). **No correction implemented, exploit reproduced, build run, device accessed, or upgrade approved.** This document is the only file owned/created by this review; existing concurrent work is untouched. Initial review date: **2026-09-16**; integration status updated **2026-09-18**. The original review was a focused documentation-only follow-up to [dependency advisory coverage](dependency_advisory_coverage.md#new-release-note-finding-wi-fi-management-frame-corruption). **At that initial review, no correction was implemented, exploit reproduced, build run, device accessed, or upgrade approved.** The fix-bearing complete IDF 5.5.3 vendor bundle is now integrated in the validated candidate; this is not radio-hardware vulnerability closure or deployment approval. See [current integration evidence](idf_candidate_integration.md#final-integration-evidence--2026-09-18) and [semantic rebase review](idf_553_rebase_review.md).
## Decision ## Decision
**Prefer a complete, immutable Espressif IDF 5.5 maintenance-release bundle, integrated through a compatible pinned PlatformIO package/toolchain, over transplanting Wi-Fi archives into IDF 5.5.0.** IDF **5.5.3 is the verified fix-bearing reference candidate/minimum demonstrated release here**, not a recommendation to deploy it in preference to all later maintenance releases. The earlier coverage records 5.5.4/5.5.5; selecting a newer deployment candidate requires the same immutable provenance and compatibility gates below, not a repeat broad advisory audit. **Prefer a complete, immutable Espressif IDF 5.5 maintenance-release bundle, integrated through a compatible pinned PlatformIO package/toolchain, over transplanting Wi-Fi archives into IDF 5.5.0.** IDF **5.5.3 is the verified fix-bearing reference candidate/minimum demonstrated release here**, not a recommendation to deploy it in preference to all later maintenance releases. The earlier coverage records 5.5.4/5.5.5; selecting a newer deployment candidate requires the same immutable provenance and compatibility gates below, not a repeat broad advisory audit.
Do not change PMF/WPA3 policy as an automatic mitigation. Do not replace only `libnet80211.a`, edit only a callback declaration, relax source guards, or overwrite the installed SDK. Current PMF-capable SoftAP exposure remains open pending implementation and target validation. Do not change PMF/WPA3 policy as an automatic mitigation. Do not replace only `libnet80211.a`, edit only a callback declaration, relax source guards, or overwrite the installed SDK. The vendor correction is integrated in the fresh candidate build; deployed old firmware remains exposed, and target/radio validation remains pending. PMF/WPA3 has not been weakened.
## Current applicability and trigger limits ## Current applicability and trigger limits
@@ -43,13 +43,13 @@ The fix changes four paths:
| `components/esp_wifi/lib` | Submodule `d7373a90dc3f0be841b29911e33de5f99988dbff`**`a4e903fe43bf09a95022f9802db43d39740ccc0b`** | | `components/esp_wifi/lib` | Submodule `d7373a90dc3f0be841b29911e33de5f99988dbff`**`a4e903fe43bf09a95022f9802db43d39740ccc0b`** |
| `components/esp_wifi/include/esp_wifi_types_generic.h` | Two SAE PWE documentation comments record default `WPA3_SAE_PWE_BOTH`; not structure-layout changes | | `components/esp_wifi/include/esp_wifi_types_generic.h` | Two SAE PWE documentation comments record default `WPA3_SAE_PWE_BOTH`; not structure-layout changes |
The installed header and callback still take `uint8_t *`; `esp_wpa_main.c` registers the callback for SoftAP. The callback slot/pointer size need not change for this to be an ABI break: the width and interpretation of the pointed-to output change (one byte versus a `size_t`, four bytes on ESP32-S3). Mixed old/new caller and callee can corrupt storage or leave an incorrect length. Casting the callback does not fix the contract. Both sides, including the opaque caller, must agree. The historical shared IDF 5.5.0 header and callback take `uint8_t *`; `esp_wpa_main.c` registers the callback for SoftAP. The callback slot/pointer size need not change for this to be an ABI break: the width and interpretation of the pointed-to output change (one byte versus a `size_t`, four bytes on ESP32-S3). Mixed old/new caller and callee can corrupt storage or leave an incorrect length. Casting the callback does not fix the contract. Both sides, including the opaque caller, must agree.
The visible C edits are the length correction/interface marker. **The management-encryption fix is in the vendor binary update, not a public C hunk that can be copied into this application.** The visible C edits are the length correction/interface marker. **The management-encryption fix is in the vendor binary update, not a public C hunk that can be copied into this application.**
### Bundle identity: baseline, fix point, release point are different ### Bundle identity: baseline, fix point, release point are different
- Installed baseline matches Wi-Fi submodule **`8a1b7bbc00e895d040c5c9a6fb9d1db2bbfc7958`** from IDF v5.5, not the fix's immediate predecessor `d7373a90…`. - Historical shared installed baseline matches Wi-Fi submodule **`8a1b7bbc00e895d040c5c9a6fb9d1db2bbfc7958`** from IDF v5.5, not the fix's immediate predecessor `d7373a90…`.
- The exact fix-point bundle is **`a4e903fe43bf09a95022f9802db43d39740ccc0b`**. - The exact fix-point bundle is **`a4e903fe43bf09a95022f9802db43d39740ccc0b`**.
- The complete 5.5.3 release points to **`e0befaa593277b4e80726079fbd521b4681754c2`**, verified again at the immutable release commit. [Wi-Fi comparison](https://api.github.com/repos/espressif/esp32-wifi-lib/compare/a4e903fe43bf09a95022f9802db43d39740ccc0b...e0befaa593277b4e80726079fbd521b4681754c2) reports ahead 3 / behind 0. Do not call the fix-point bundle the final 5.5.3 bundle. - The complete 5.5.3 release points to **`e0befaa593277b4e80726079fbd521b4681754c2`**, verified again at the immutable release commit. [Wi-Fi comparison](https://api.github.com/repos/espressif/esp32-wifi-lib/compare/a4e903fe43bf09a95022f9802db43d39740ccc0b...e0befaa593277b4e80726079fbd521b4681754c2) reports ahead 3 / behind 0. Do not call the fix-point bundle the final 5.5.3 bundle.
@@ -65,7 +65,7 @@ Calculated Git blob SHA-1 (`blob <length>\0` plus bytes) of **all seven installe
| `libsmartconfig.a` | `31518eb9336f07de14b4a71c0389217a54ed3ec7` | `19e8274e0961fdf88351a0dec6e0d696b8ee2b94` | `4dc759e25617aa00b9e12887fd092a1d5780a170` | | `libsmartconfig.a` | `31518eb9336f07de14b4a71c0389217a54ed3ec7` | `19e8274e0961fdf88351a0dec6e0d696b8ee2b94` | `4dc759e25617aa00b9e12887fd092a1d5780a170` |
| `libwapi.a` | `0604ff76aa9134b475daf08496e03350d5ef8f09` | `e43863a22e93990b8a4deb7c4efc8e4c27566789` | `65655b6feab0572a6e8a1200946d53a21f3f4722` | | `libwapi.a` | `0604ff76aa9134b475daf08496e03350d5ef8f09` | `e43863a22e93990b8a4deb7c4efc8e4c27566789` | `65655b6feab0572a6e8a1200946d53a21f3f4722` |
All installed archives match baseline; all differ from both newer bundles. This is artifact identity, not proof that every archive is linked or contains the defect. Remote binaries were **not downloaded**. Git IDs are provenance identifiers, not independently measured remote SHA-256 digests or a vendor signature verification. All historical shared-SDK archives matched baseline; all differ from both newer bundles. This is artifact identity, not proof that every archive is linked or contains the defect. Remote binaries were **not downloaded during the initial review**. Later candidate archive download/verification and complete Wi-Fi/PHY/coexistence/supplicant package comparisons are recorded in the integration/rebase reviews; the candidate uses the release-point column, not this old baseline. Git IDs are provenance identifiers, not independently measured remote SHA-256 digests or a vendor signature verification.
The release API advertises the submodule-inclusive `esp-idf-v5.5.3.zip`, asset ID **357881088**, SHA-256 **`d16e6b57b1df1a523aff6a1d0ef2f99cf058cb71e73f694429769eaaea171428`**. This is vendor-published metadata, not a locally verified download. The release is marked `immutable=false`; therefore pin the resolved commit/submodules and verify artifact hashes, not merely a tag URL. Espressif warns that GitHub's automatic source archives omit submodules; those are not complete SDK inputs. The release API advertises the submodule-inclusive `esp-idf-v5.5.3.zip`, asset ID **357881088**, SHA-256 **`d16e6b57b1df1a523aff6a1d0ef2f99cf058cb71e73f694429769eaaea171428`**. This is vendor-published metadata, not a locally verified download. The release is marked `immutable=false`; therefore pin the resolved commit/submodules and verify artifact hashes, not merely a tag URL. Espressif warns that GitHub's automatic source archives omit submodules; those are not complete SDK inputs.
@@ -90,19 +90,18 @@ If operational containment is urgently required before an update, the owner may
Use all IDF source, private/public headers, Wi-Fi/PHY/coexistence libraries and other submodules from one resolved release, with its toolchain and Python/build requirements. Do not install final-release blobs alongside fix-point-only C edits or mix two release revisions. Preserve the application security policies separately through audited overrides. Use all IDF source, private/public headers, Wi-Fi/PHY/coexistence libraries and other submodules from one resolved release, with its toolchain and Python/build requirements. Do not install final-release blobs alongside fix-point-only C edits or mix two release revisions. Preserve the application security policies separately through audited overrides.
Verified local integration constraints: Current integration disposition (supplied parent evidence, 2026-09-18):
| Input | Current state | Required next action | | Input | Current state | Remaining boundary |
| --- | --- | --- | | --- | --- | --- |
| `platformio.ini` | `platformio/espressif32@6.12.0`, ESP-IDF, ESP32-S3 N16R8 | Select a supported pinned platform/framework/toolchain combination; no floating update | | Root `platformio.ini` | Platform 6.13.0, framework 3.50503.0 / IDF 5.5.3, Xtensa and RISC-V 14.2.0+20251107 | Exact version pins, not complete immutable root dependency closure; default root build timed out during installation at 200 seconds before compilation |
| Installed platform `platform.json` | Framework range `~3.50500.0`; installed `framework-espidf/package.json` is `3.50500.0` | Does not select 5.5.3; establish package provenance and adapter support before proposing a package override | | Isolated `app-validated` | Fresh build PASS; 95,552 B RAM / 1,749,493 B flash (+1,212 / 19,408 B versus historical 94,340 / 1,768,901 B) | Linked size is not runtime reserve |
| Installed Xtensa toolchain | `14.2.0+20241119` | Candidate's immutable [`tools/tools.json`](https://github.com/espressif/esp-idf/blob/2c211b236707889e8400c4dc5644dd5c4ee071e0/tools/tools.json) recommends **`esp-14.2.0_20251107`**; same GCC major/minor is not identical tooling or compatibility evidence | | Protected corrections | Nine C sources plus forced header; per-entry semantic rebase, five new signed WS receive checks; 982 cases / 10 mutation checks | No original correction removed as superseded; retain exact fail-closed guards |
| `tools/security_overrides.py` | `verify_version()` requires exactly **5.5.0**; each original source has an exact SHA-256 and executable edit contract | A full upgrade intentionally fails until semantic re-audit/rebase; never make the version range permissive just to build | | Actual build/host validation | Explicit candidate SDK/core/build with interop and web performance PASS 24/24; source equality 3,237 files, identical pre/post hash recorded in [validation](phase9_validation.md#current-candidate-execution--2026-09-18) | Not radio-hardware vulnerability closure or full target acceptance |
| `cmake/security_overrides.cmake` | Exact-once source replacement, nested mbedTLS ownership checks, source-property preservation, forced PUBLIC wolfSSH ABI overlay | Revalidate generated and actual compiler inputs, target identities and include order | | Radio delivery | All packaged Wi-Fi/PHY/coexistence/supplicant files match locked framework archive; candidate callback is `size_t *` | Not independent opaque-code correctness or recursive upstream submodule provenance |
| Notices | Nine entries semantically rebased, 66 unchanged, 75 total; 36 fixtures PASS; two actual bundles each 77 files / 4,433,930 bytes | Recipient/source/legal clearance pending |
No known-good PlatformIO 6.12 + IDF 5.5.3 package combination was established here. The installed adapter was read only for package/version/toolchain selection references, not certified for the candidate. A raw IDF Git checkout is not automatically a PlatformIO framework package. Prefer a supported platform release carrying a suitable complete SDK; otherwise a maintained, exact-pinned framework/toolchain packaging effort is a separate explicit implementation deliverable. The completed [semantic rebase review](idf_553_rebase_review.md#3-per-entry-semantic-disposition) covers candidate originals, not only Wi-Fi: HTTPS cleanup/private-key release, HTTPD scratch/null-read handling, server-local TLS policy, DHCP bounds, TLS EMS failure, X.509 allocation failure; retain/revalidate wolfSSH password/parser/ordering/ABI changes and wolfCrypt settings. Mark each correction **retained, rebased, or superseded with source/test evidence**. Upstream fixes do not automatically supersede local lifetime/policy additions in the same source. Also retain silent-panic/no-coredump and heap-wipe policy guards. The durable architecture's older four-correction summary is not the complete current inventory; the current generator and code map are authoritative.
Re-audit all existing corrections against candidate originals, not only Wi-Fi: HTTPS cleanup/private-key release, HTTPD scratch/null-read handling, server-local TLS policy, DHCP bounds, TLS EMS failure, X.509 allocation failure; retain/revalidate wolfSSH password/parser/ordering/ABI changes and wolfCrypt settings. Mark each correction **retained, rebased, or superseded with source/test evidence**. Upstream fixes do not automatically supersede local lifetime/policy additions in the same source. Also retain silent-panic/no-coredump and heap-wipe policy guards. The durable architecture's older four-correction summary is not the complete current inventory; the current generator and code map are authoritative.
### Contingency: vendor-supported backport onto 5.5.0 ### Contingency: vendor-supported backport onto 5.5.0
@@ -112,7 +111,9 @@ A candidate backport must at least pair both callback changes with one complete
Ask Espressif for a supported 5.5.0 backport bundle or an explicit compatibility statement with its required source/header/library/toolchain prerequisites, confirming both management-frame and IE-length corrections. The immediate-parent blob pointer differs from baseline; successful linking alone cannot establish binary layout/calling-contract compatibility. Without that evidence or a complete audited dependency closure, choose the full-release path rather than treating the two C signature edits plus blobs as approved. Ask Espressif for a supported 5.5.0 backport bundle or an explicit compatibility statement with its required source/header/library/toolchain prerequisites, confirming both management-frame and IE-length corrections. The immediate-parent blob pointer differs from baseline; successful linking alone cannot establish binary layout/calling-contract compatibility. Without that evidence or a complete audited dependency closure, choose the full-release path rather than treating the two C signature edits plus blobs as approved.
## Bounded implementation and acceptance plan ## Implementation plan history and remaining acceptance
Steps 14 below preserve the original plan: package selection, isolated preparation, override rebase and fresh candidate build/host checks now have the evidence above. Complete immutable dependency closure is not claimed. Steps 56 remain acceptance/release gates; historical “no code change yet” language is not a new blocker.
1. **Compatibility/provenance gate first, no code change yet.** Resolve one deployment release and PlatformIO delivery combination. Record immutable SDK commit, recursive submodule pins, package/toolchain identities and SHA-256 manifests. Confirm vendor inclusion of the fix. Obtain missing packaging/ABI evidence; if unavailable, report the blocker instead of guessing a framework URL or transplanting archives. 1. **Compatibility/provenance gate first, no code change yet.** Resolve one deployment release and PlatformIO delivery combination. Record immutable SDK commit, recursive submodule pins, package/toolchain identities and SHA-256 manifests. Confirm vendor inclusion of the fix. Obtain missing packaging/ABI evidence; if unavailable, report the blocker instead of guessing a framework URL or transplanting archives.
2. **Prepare isolated inputs after approval.** Do not modify the user's shared installed SDK. Obtain complete vendor inputs in a dedicated location, verify provenance/hashes, and compare only relevant integration surfaces and protected-source entries. Keep existing managed components pinned where compatible; explicitly review any resolver-required changes. Review license/notice/source-delivery updates for new radio/SDK artifacts. 2. **Prepare isolated inputs after approval.** Do not modify the user's shared installed SDK. Obtain complete vendor inputs in a dedicated location, verify provenance/hashes, and compare only relevant integration surfaces and protected-source entries. Keep existing managed components pinned where compatible; explicitly review any resolver-required changes. Review license/notice/source-delivery updates for new radio/SDK artifacts.
@@ -124,6 +125,7 @@ Ask Espressif for a supported 5.5.0 backport bundle or an explicit compatibility
## Open evidence and review boundary ## Open evidence and review boundary
- **Resolved:** official fix IDs/description; exact visible ABI delta; all seven installed S3 archives match baseline; distinct fix and final-release bundles; release/fix ancestry; current application configuration; PMF-disable restrictions; exact-IDF/hash guards; recommended versus installed toolchain mismatch. - **Resolved:** official fix IDs/description; exact visible ABI delta; all seven installed S3 archives match baseline; distinct fix and final-release bundles; release/fix ancestry; current application configuration; PMF-disable restrictions; exact-IDF/hash guards; recommended versus installed toolchain mismatch.
- **Unresolved:** supported PlatformIO delivery for the chosen maintenance release; full private ABI prerequisite closure for a 5.5.0 transplant; exact PHY/coexistence submodule pins for the candidate in this review; complete tool/Python/managed-component compatibility; opaque binary implementation correctness; packet-level attacker requirements; target/runtime/resource evidence; latest deployment-release selection. - **Current resolved integration:** official PlatformIO 6.13.0 / IDF 5.5.3 delivery and exact toolchain selection, semantic override rebase, fresh candidate build and 24/24 host validation, whole packaged radio-component equality and notice rebase.
- **Still unresolved:** complete immutable ancillary/tool/Python/managed dependency closure; independent PHY/coexistence upstream submodule provenance; opaque binary implementation correctness; packet-level attacker requirements; target/runtime/resource evidence and deployment acceptance. A 5.5.0 transplant remains unproved and was not selected.
- A follow-up GitHub API request for candidate `components/esp_phy/lib` returned **HTTP 403 rate limit exceeded**. The sequential metadata query stopped there, so PHY/coexistence pins were not established; no inference was drawn from that failure. Already completed Wi-Fi/release/ancestry queries succeeded. Official raw documentation remained available. Do not treat this partial metadata boundary as a compatibility clearance. - A follow-up GitHub API request for candidate `components/esp_phy/lib` returned **HTTP 403 rate limit exceeded**. The sequential metadata query stopped there, so PHY/coexistence pins were not established; no inference was drawn from that failure. Already completed Wi-Fi/release/ancestry queries succeeded. Official raw documentation remained available. Do not treat this partial metadata boundary as a compatibility clearance.
- Validation performed here: read-only local source/package/config inspection; local seven-archive Git-blob hashing; official patch/release/tag/content/comparison/toolchain/documentation reads. No broad repeated dependency audit, binary download, PlatformIO invocation, build, test suite, device operation, configuration edit, or asset generation. Documentation links/provenance do not constitute signed-vendor attestation, exhaustive security certification, or Phase 9 sign-off. - Historical initial-review validation only: read-only local source/package/config inspection; local seven-archive Git-blob hashing; official patch/release/tag/content/comparison/toolchain/documentation reads. No broad repeated dependency audit, binary download, PlatformIO invocation, build, test suite, device operation, configuration edit, or asset generation. Documentation links/provenance do not constitute signed-vendor attestation, exhaustive security certification, or Phase 9 sign-off.
+5 -1
View File
@@ -2,7 +2,11 @@
default_envs = esp32-s3-devkitc-1-n16r8 default_envs = esp32-s3-devkitc-1-n16r8
[env:esp32-s3-devkitc-1-n16r8] [env:esp32-s3-devkitc-1-n16r8]
platform = platformio/espressif32@6.12.0 platform = platformio/espressif32@6.13.0
platform_packages =
platformio/framework-espidf@3.50503.0
platformio/toolchain-xtensa-esp-elf@14.2.0+20251107
platformio/toolchain-riscv32-esp@14.2.0+20251107
board = esp32-s3-devkitc-1-n16r8 board = esp32-s3-devkitc-1-n16r8
framework = espidf framework = espidf
+2 -2
View File
@@ -14,7 +14,7 @@
#error "SSH memory requires heap poisoning disabled" #error "SSH memory requires heap poisoning disabled"
#endif #endif
#if ESP_IDF_VERSION != ESP_IDF_VERSION_VAL(5, 5, 0) #if ESP_IDF_VERSION != ESP_IDF_VERSION_VAL(5, 5, 3)
#error "Reaudit SSH memory usable extent contract for this IDF" #error "Reaudit SSH memory usable extent contract for this IDF"
#endif #endif
@@ -44,7 +44,7 @@ void *ssh_memory_realloc(void *pointer, size_t size)
return NULL; return NULL;
} }
/* Audited unpoisoned IDF 5.5.0 reports the owned usable extent, including /* Audited unpoisoned IDF 5.5.3 reports the owned usable extent, including
* rounding. Do not substitute an interior-pointer/block-containing query. */ * rounding. Do not substitute an interior-pointer/block-containing query. */
size_t capacity = heap_caps_get_allocated_size(pointer); size_t capacity = heap_caps_get_allocated_size(pointer);
if (size <= capacity) { if (size <= capacity) {
+4 -4
View File
@@ -10,11 +10,11 @@
#include "esp_httpd_priv.h" #include "esp_httpd_priv.h"
#include "secure_random.h" #include "secure_random.h"
#if ESP_IDF_VERSION != ESP_IDF_VERSION_VAL(5, 5, 0) #if ESP_IDF_VERSION != ESP_IDF_VERSION_VAL(5, 5, 3)
#error "Reaudit HTTPD headers, upgrade, idle cleanup and WS/TLS send contracts for this IDF" #error "Reaudit HTTPD headers, upgrade, idle cleanup and WS/TLS send contracts for this IDF"
#endif #endif
/* IDF 5.5.0 httpd_sess_process increments lru_counter only AFTER successful /* IDF 5.5.3 httpd_sess_process increments lru_counter only AFTER successful
* req_new + req_delete (handler, response, leftover-body purge and cleanup). * req_new + req_delete (handler, response, leftover-body purge and cleanup).
* Work callbacks run between sessions, never inside synchronous parse/TLS/send. * Work callbacks run between sessions, never inside synchronous parse/TLS/send.
* This counter is an observation marker, NOT permission to enable LRU purge. */ * This counter is an observation marker, NOT permission to enable LRU purge. */
@@ -81,7 +81,7 @@ esp_err_t web_httpd_ws_send_binary(httpd_handle_t server, int fd,
sd->ws_close || sd->for_async_req || !sd->send_fn) sd->ws_close || sd->for_async_req || !sd->send_fn)
return ESP_ERR_INVALID_STATE; return ESP_ERR_INVALID_STATE;
/* IDF 5.5.0 httpd_ws_send_frame_async emits header/payload separately. /* IDF 5.5.3 httpd_ws_send_frame_async emits header/payload separately.
* Keep the existing send override (HTTPS -> esp_tls_conn_write), not raw * Keep the existing send override (HTTPS -> esp_tls_conn_write), not raw
* socket IO. Owner-local scratch lives through the synchronous call only. */ * socket IO. Owner-local scratch lives through the synchronous call only. */
uint8_t wire[WEB_HTTPD_WS_BINARY_MAX_PAYLOAD + 4U]; uint8_t wire[WEB_HTTPD_WS_BINARY_MAX_PAYLOAD + 4U];
@@ -219,7 +219,7 @@ esp_err_t web_httpd_register_optional(httpd_handle_t server, const httpd_uri_t *
return ESP_ERR_INVALID_STATE; return ESP_ERR_INVALID_STATE;
} }
if (slot < 0) return ESP_ERR_NO_MEM; if (slot < 0) return ESP_ERR_NO_MEM;
/* IDF 5.5.0 publishes its descriptor before strdup; strdup failure leaves a /* IDF 5.5.3 publishes its descriptor before strdup; strdup failure leaves a
* freed hd_calls entry. Optional registration must leave the table intact. */ * freed hd_calls entry. Optional registration must leave the table intact. */
httpd_uri_t *copy = malloc(sizeof(*copy)); httpd_uri_t *copy = malloc(sizeof(*copy));
if (!copy) return ESP_ERR_NO_MEM; if (!copy) return ESP_ERR_NO_MEM;
+76
View File
@@ -3,6 +3,7 @@
import contextlib import contextlib
import importlib.util import importlib.util
import io import io
import json
import os import os
from pathlib import Path from pathlib import Path
import subprocess import subprocess
@@ -66,6 +67,81 @@ class ValidationTests(unittest.TestCase):
self.assertNotIn('build', [c.name for c in commands]) self.assertNotIn('build', [c.name for c in commands])
self.assertEqual([c.name for c in commands if '--interop' in c.argv], ['wolfssh_order_contract']) self.assertEqual([c.name for c in commands if '--interop' in c.argv], ['wolfssh_order_contract'])
def test_explicit_paths_and_web_performance(self):
options = self.options('--build-dir', '/isolated/build', '--idf-path', '/isolated/idf',
'--platformio-core-dir', '/isolated/core', '--web-performance')
commands = {c.name: c for c in runner.plan(options)}
self.assertEqual(len(commands), 24)
for name in ('sdk_security_overrides', 'web_serial_performance'):
self.assertEqual(commands[name].argv[3:],
('--build-dir', '/isolated/build', '--idf-path', '/isolated/idf'))
self.assertEqual(commands['ssh_memory'].argv[3:], ('--idf-path', '/isolated/idf'))
for name in ('wolfssh_auth_contract', 'ssh_protocol_policy', 'wolf_crypto_policy'):
self.assertEqual(commands[name].argv[3:],
('--compile-commands', '/isolated/build/compile_commands.json'))
self.assertEqual(commands['security_build_policy'].argv[3:],
('--sdkconfig-header', '/isolated/build/config/sdkconfig.h'))
def test_explicit_environment_is_narrow_and_not_global(self):
with patch.dict(os.environ, {'IDF_PATH': 'old-idf', 'PLATFORMIO_CORE_DIR': 'old-core'}):
with patch.object(runner, 'run_command', return_value=('PASS', 'fixture')) as run:
self.assertEqual(self.execute([self.command('pass')], idf_path=Path('/sdk'),
platformio_core_dir=Path('/core'))[0], 0)
env = run.call_args.args[2]
self.assertEqual(env['IDF_PATH'], '/sdk')
self.assertEqual(env['PLATFORMIO_CORE_DIR'], '/core')
expected = dict(os.environ, CCACHE_DISABLE='1', IDF_PATH='/sdk', PLATFORMIO_CORE_DIR='/core')
self.assertEqual(env, expected)
self.assertEqual(os.environ['IDF_PATH'], 'old-idf')
self.assertEqual(os.environ['PLATFORMIO_CORE_DIR'], 'old-core')
def test_snapshot_content_sets_and_ambiguity_fail_closed(self):
with tempfile.TemporaryDirectory() as tmp:
root, stage, build = [Path(tmp) / name for name in ('root', 'stage', 'build')]
for base in (root, stage):
for directory in ('src', 'cmake', 'boards', 'managed_components', 'tools/wolfssh_order'):
(base / directory).mkdir(parents=True)
for name in ('src/main.c', 'tools/security_overrides.py', 'CMakeLists.txt',
'extra_script.py', 'sdkconfig.defaults', 'partitions.csv'):
(base / name).write_text('identical fixture')
build.mkdir()
database = build / 'compile_commands.json'
entry = {'directory': str(stage), 'file': 'src/main.c'}
database.write_text(json.dumps([entry]))
with contextlib.redirect_stdout(io.StringIO()):
evidence = runner.verify_snapshot(root, build)
self.assertEqual(evidence[:2], (stage, 6))
self.assertEqual(runner.verify_snapshot(root, build), evidence)
(stage / 'src/main.c').write_text('stale')
with self.assertRaisesRegex(RuntimeError, 'content differs'):
runner.verify_snapshot(root, build)
(stage / 'src/main.c').write_text('identical fixture')
(stage / 'src/extra.c').touch()
with self.assertRaisesRegex(RuntimeError, 'file set differs'):
runner.verify_snapshot(root, build)
(stage / 'src/extra.c').unlink()
for entries in ([], [entry, entry]):
database.write_text(json.dumps(entries))
with self.assertRaisesRegex(RuntimeError, 'exactly one'):
runner.verify_snapshot(root, build)
def test_external_build_cannot_rebuild_default(self):
with contextlib.redirect_stderr(io.StringIO()), self.assertRaises(SystemExit):
runner.main(['--build', '--build-dir', '/isolated/build'])
def test_snapshot_failure_never_runs_suites(self):
with patch.object(runner, 'verify_snapshot', side_effect=RuntimeError('mismatch')):
with patch.object(runner, 'execute', side_effect=AssertionError('executed')):
with contextlib.redirect_stdout(io.StringIO()):
self.assertEqual(runner.main(['--build-dir', '/isolated/build']), 1)
def test_snapshot_checked_after_execution(self):
for final, expected in ((('stage', 1, 'same'), 0), (('stage', 1, 'changed'), 1)):
with patch.object(runner, 'verify_snapshot', side_effect=[('stage', 1, 'same'), final]) as verify:
with patch.object(runner, 'execute', return_value=0), contextlib.redirect_stdout(io.StringIO()):
self.assertEqual(runner.main(['--build-dir', '/isolated/build']), expected)
self.assertEqual(verify.call_count, 2)
def test_bad_timeout(self): def test_bad_timeout(self):
for value in ('0', '-1', 'nan', 'inf', '3601', 'junk'): for value in ('0', '-1', 'nan', 'inf', '3601', 'junk'):
with contextlib.redirect_stderr(io.StringIO()), self.assertRaises(SystemExit): with contextlib.redirect_stderr(io.StringIO()), self.assertRaises(SystemExit):
+41 -4
View File
@@ -7,7 +7,7 @@ client ciphersuite setting is changed.
## Build contract ## Build contract
`tools/security_overrides.py` requires the installed ESP-IDF version header to `tools/security_overrides.py` requires the installed ESP-IDF version header to
identify **5.5.0**, and checks each complete original source against its reviewed identify **5.5.3**, and checks each complete original source against its reviewed
SHA256. Every text substitution must match **exactly once**. The entire input SHA256. Every text substitution must match **exactly once**. The entire input
plan is validated before any output is written. A changed SDK, missing source, plan is validated before any output is written. A changed SDK, missing source,
ambiguous edit, duplicate source, or missing/ambiguous component target fails ambiguous edit, duplicate source, or missing/ambiguous component target fails
@@ -80,8 +80,9 @@ Source generator expressions are rejected rather than guessed through.
[bfaf4a47fd33da860796feaba6235847acb71127](https://github.com/Mbed-TLS/mbedtls/commit/bfaf4a47fd33da860796feaba6235847acb71127.patch). [bfaf4a47fd33da860796feaba6235847acb71127](https://github.com/Mbed-TLS/mbedtls/commit/bfaf4a47fd33da860796feaba6235847acb71127.patch).
These three patches were fetched from the official repositories and compared These three patches were fetched from the official repositories and compared
with the installed pinned sources on 2026-09-15. No dependency versions or with the installed pinned sources on 2026-09-15. That original implementation did not change dependency versions or source hashes;
existing original-source hashes changed. The optional WS subprotocol backport the current 5.5.3 rebase has separately reviewed original hashes (see
`docs/idf_553_rebase_review.md`). The optional WS subprotocol backport
and separate ASN.1 repeated-OID/empty-value correction are **not** implemented. and separate ASN.1 repeated-OID/empty-value correction are **not** implemented.
Clients that cannot negotiate this server profile will no longer connect. Clients that cannot negotiate this server profile will no longer connect.
@@ -109,7 +110,7 @@ entry tuple as well as the default registry. The manifest maps each entry to
its component, optional nested target, original and derived source. CMake's its component, optional nested target, original and derived source. CMake's
`sak_security_replace_source(component original generated nested_target)` handles `sak_security_replace_source(component original generated nested_target)` handles
replacement on the actual source owner, not the IDF mbedTLS wrapper. The current replacement on the actual source owner, not the IDF mbedTLS wrapper. The current
registry has six IDF sources and one project-managed wolfSSH source. Update the registry has seven IDF C sources, two project-managed wolfSSH C sources and one wolfSSH header overlay. Update the
corresponding library-specific feature/behavior tests when extending the registry. Multiple corresponding library-specific feature/behavior tests when extending the registry. Multiple
sources in the same real component are supported by the replacement function. sources in the same real component are supported by the replacement function.
@@ -130,6 +131,42 @@ old generated copies lack the new entries/notice and must not pass. It checks re
Ninja registration: exactly one compilation of each derived source, no original Ninja registration: exactly one compilation of each derived source, no original
compilation, and exact generated bytes. It does **not** run a firmware build. compilation, and exact generated bytes. It does **not** run a firmware build.
### Candidate WebSocket receive regression
```sh
CCACHE_DISABLE=1 python3 -B tests/sdk_security_overrides/run.py --idf-path .pio/idf-candidate-5.5.3/core/packages/framework-espidf
CCACHE_DISABLE=1 python3 -B tests/web_serial_performance/run.py --idf-path .pio/idf-candidate-5.5.3/core/packages/framework-espidf
```
Either runner accepts `--build-dir .pio/idf-candidate-5.5.3/app/.pio/build/esp32-s3-devkitc-1-n16r8`
for strict existing-build evidence. The performance runner otherwise renders the
current hash-verified WS override; it never falls back to vendor WS code.
Its build mode verifies exactly one generated compilation input, its HTTPD owner,
original-source absence and byte equality with the current override. A stale build
missing the WS replacement must fail; parent reconfiguration/build is separate.
`ws.c` executes complete generated `httpd_ws_get_frame_type`,
`httpd_ws_recv_frame`, unmask/check/send-wrapper functions and complete hash-pinned
vendor `httpd_recv_with_opt`/`httpd_recv_pending`, not copied conditional snippets.
The send endpoint and socket receive callback are bounded doubles. Its 982 cases
cover all five header sites (first/second byte, 2/8-byte length, 4-byte mask),
negative failure/timeout, EOF, every shorter prefix, split socket reads, pending
prefixes plus timeout (positive short returns), successful binary decode, length
probe/resume, nontrivial extended lengths, automatic PING/PONG and CLOSE.
Input ends at a guard page; output/mask canaries, exact byte consumption and
send/callback counts enforce no payload read or output after framing failure.
UBSan trap instrumentation is enabled.
The tests deliberately do **not** assert transactional rollback: failed first-byte
reads return `ESP_OK` with final/CLOSE metadata; other failed reads retain already
decoded type/final/length or partially received mask bytes. They assert that exact
state and that no automatic reply follows failed control framing. Five individual
cast removals must fail strict compilation with `sign-compare`; five explicit
unsigned-promotion equivalents must compile with warnings-as-errors and then fail
behavioral assertions. No diagnostic suppression is used for these mutations.
This is host framing evidence, not live sockets, target ABI/timing or firmware
integration evidence.
Coverage: Coverage:
- Generator full-source hashes, version, missing/duplicate/ambiguous inputs, - Generator full-source hashes, version, missing/duplicate/ambiguous inputs,
+43 -4
View File
@@ -41,8 +41,8 @@ def source_path(entry, idf, project=ROOT):
AUXILIARY = { AUXILIARY = {
"components/esp_http_server/src/httpd_main.c": "a16ef65069dda13889c67b922f25eb566573983d6c24f01c089a902d5fd26149", "components/esp_http_server/src/httpd_main.c": "55fccf1ec01265dd9be45609c4d8eeb5d9f80da99fee23b2309a6b372c56e24f",
"components/esp_http_server/src/httpd_txrx.c": "7659ad52c32f29b9a08208dc8b22d023edf274047835ed58107d82a47ccce00e", "components/esp_http_server/src/httpd_txrx.c": "f0978034ae0acc7e5f5c52fcb4aabc424afd403d433a417e04ff70319a7dd140",
} }
FEATURES = ["MBEDTLS_SSL_PROTO_TLS1_2", "MBEDTLS_SSL_SRV_C", FEATURES = ["MBEDTLS_SSL_PROTO_TLS1_2", "MBEDTLS_SSL_SRV_C",
"MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED", "MBEDTLS_ECDH_C", "MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED", "MBEDTLS_ECDH_C",
@@ -145,8 +145,8 @@ def generator_tests(idf, work):
expect_error(lambda: sdk.generate(fake, ROOT, binary), "SHA256 mismatch") expect_error(lambda: sdk.generate(fake, ROOT, binary), "SHA256 mismatch")
assert before == {p: (p.read_bytes(), p.stat().st_mtime_ns) for p in before} assert before == {p: (p.read_bytes(), p.stat().st_mtime_ns) for p in before}
shutil.copyfile(idf / TLS_ENTRY.source, last) shutil.copyfile(idf / TLS_ENTRY.source, last)
(fake / version).write_text((fake / version).read_text().replace("VERSION_PATCH 0", "VERSION_PATCH 1")) (fake / version).write_text((fake / version).read_text().replace("VERSION_PATCH 3", "VERSION_PATCH 1"))
expect_error(lambda: sdk.generate(fake, ROOT, failed), "5.5.0") expect_error(lambda: sdk.generate(fake, ROOT, failed), "5.5.3")
shutil.copyfile(idf / version, fake / version) shutil.copyfile(idf / version, fake / version)
last.unlink() last.unlink()
expect_error(lambda: sdk.generate(fake, ROOT, failed), "No such file") expect_error(lambda: sdk.generate(fake, ROOT, failed), "No such file")
@@ -215,6 +215,44 @@ def extracted_tests(idf, binary, work):
"-fsanitize=undefined", "-fsanitize-undefined-trap-on-error"]) "-fsanitize=undefined", "-fsanitize-undefined-trap-on-error"])
def websocket_tests(idf, binary, work):
entry = next(e for e in sdk.ENTRIES if e.name == "httpd_ws")
text = generated_path(binary, entry).read_text()
assert text.encode() == sdk.render_entry(entry, {"idf": idf, "project": ROOT})[1]
txrx = (idf / "components/esp_http_server/src/httpd_txrx.c").read_text()
assert hashlib.sha256(txrx.encode()).hexdigest() == AUXILIARY["components/esp_http_server/src/httpd_txrx.c"]
private = (idf / "components/esp_http_server/src/esp_httpd_priv.h").read_text()
options = re.search(r"typedef enum \{[^}]*\} httpd_recv_opt_t;", private)
assert options
functions = "".join(extract(txrx, n) for n in ("httpd_recv_pending", "httpd_recv_with_opt"))
functions += "".join(extract(text, n) for n in (
"httpd_ws_check_req", "httpd_ws_unmask_payload", "httpd_ws_recv_frame",
"httpd_ws_send_frame", "httpd_ws_get_frame_type"))
template = (HERE / "ws.c").read_text().replace("/* SDK_OPTIONS */", options.group())
source = template.replace("/* SDK_FUNCTIONS */", functions)
flags = ["-fsanitize=undefined", "-fsanitize-undefined-trap-on-error"]
compile_run("ws", source, work, flags)
# Independently enumerate all five sites, rather than trusting the edit registry.
sites = list(re.finditer(r"httpd_recv_with_opt\([^\n]+HTTPD_RECV_OPT_BLOCKING\) < \(int\)sizeof\(([^)]+)\)", functions))
assert [m[1] for m in sites] == ["second_byte", "length_bytes", "length_bytes", "aux->mask_key", "first_byte"]
for i, match in enumerate(sites):
removed = functions[:match.start()] + match[0].replace("(int)sizeof", "sizeof") + functions[match.end():]
c = work / f"ws_removed_cast_{i}.c"
c.write_text(template.replace("/* SDK_FUNCTIONS */", removed))
output = run(["cc", "-std=gnu11", "-Wall", "-Wextra", "-Werror", "-fsyntax-only", c], ok=False)
assert "sign-compare" in output, output
# Explicit unsigned conversion reproduces the vendor's implicit promotion
# without disabling sign-compare diagnostics: rejection must be behavioral.
mutant = functions[:match.start()] + "(size_t)" + match[0].replace("(int)sizeof", "sizeof") + functions[match.end():]
c = work / f"ws_mutant_{i}.c"
exe = work / f"ws_mutant_{i}"
c.write_text(template.replace("/* SDK_FUNCTIONS */", mutant))
run(["cc", "-std=gnu11", "-O2", "-Wall", "-Wextra", "-Werror", *flags, c, "-o", exe])
output = run([exe], ok=False)
assert "Assertion" in output or "assertion" in output, output
print("WS five removed casts rejected by strict compilation and five unsigned-comparison behavioral mutations rejected PASS")
def compile_run(name, source, work, flags=()): def compile_run(name, source, work, flags=()):
c = work / (name + ".c"); exe = work / name c = work / (name + ".c"); exe = work / name
c.write_text("/* Extracted SDK sections retain their upstream Apache-2.0 license. */\n" + source) c.write_text("/* Extracted SDK sections retain their upstream Apache-2.0 license. */\n" + source)
@@ -410,6 +448,7 @@ def main():
work = Path(tmp) work = Path(tmp)
binary = generator_tests(idf, work) binary = generator_tests(idf, work)
extracted_tests(idf, binary, work) extracted_tests(idf, binary, work)
websocket_tests(idf, binary, work)
cmake_fixture_tests(idf, work) cmake_fixture_tests(idf, work)
extension_fixture_tests(idf, work) extension_fixture_tests(idf, work)
if args.build_dir: build_registration(args.build_dir.resolve(), idf) if args.build_dir: build_registration(args.build_dir.resolve(), idf)
+215
View File
@@ -0,0 +1,215 @@
/* SPDX-License-Identifier: GPL-3.0-only */
#define _GNU_SOURCE
#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>
typedef int esp_err_t;
typedef void *httpd_handle_t;
typedef int httpd_ws_type_t;
#define ESP_OK 0
#define ESP_FAIL -1
#define ESP_ERR_INVALID_ARG -2
#define ESP_ERR_INVALID_STATE -3
#define ESP_ERR_INVALID_SIZE -4
#define HTTPD_SOCK_ERR_TIMEOUT -3
#define HTTPD_WS_FIN_BIT 0x80
#define HTTPD_WS_MASK_BIT 0x80
#define HTTPD_WS_LENGTH_BITS 0x7f
#define HTTPD_WS_OPCODE_BITS 0x0f
#define HTTPD_WS_TYPE_CLOSE 8
#define HTTPD_WS_TYPE_PING 9
#define HTTPD_WS_TYPE_PONG 10
#define MIN(a,b) ((a) < (b) ? (a) : (b))
#define ESP_LOGW(...) ((void)0)
#define ESP_LOGD(...) ((void)0)
/* SDK_OPTIONS */
struct sock_db {
httpd_handle_t handle;
int fd;
bool ws_handshake_done, ws_control_frames;
unsigned char pending_data[32];
size_t pending_len;
int (*recv_fn)(httpd_handle_t, int, char *, size_t, int);
};
struct httpd_req_aux {
struct sock_db *sd;
bool ws_final;
httpd_ws_type_t ws_type;
unsigned char before[8], mask_key[4], after[8];
};
typedef struct { void *aux; httpd_handle_t handle; } httpd_req_t;
typedef struct {
bool final, fragmented;
httpd_ws_type_t type;
unsigned char *payload;
size_t len;
} httpd_ws_frame_t;
static unsigned char *wire;
static size_t position, limit, chunk;
static int terminal_result, terminal_calls, sends, sent_type;
static size_t sent_len;
static unsigned char sent_payload[128];
static int receive(httpd_handle_t h, int fd, char *buf, size_t len, int flags)
{
(void)h; (void)fd; (void)flags;
assert(len > 0);
if (position == limit) {
/* Any continuation past failed framing is an observable failure. */
assert(terminal_calls++ == 0);
return terminal_result;
}
size_t n = MIN(len, MIN(chunk, limit - position));
memcpy(buf, wire + position, n);
position += n;
return (int)n;
}
static int httpd_req_to_sockfd(httpd_req_t *req) { (void)req; return 7; }
static esp_err_t httpd_ws_send_frame_async(httpd_handle_t h, int fd, httpd_ws_frame_t *f)
{
(void)h; assert(fd == 7); assert(f->len <= sizeof(sent_payload));
sends++; sent_type = f->type; sent_len = f->len;
if (f->len) memcpy(sent_payload, f->payload, f->len);
return ESP_OK;
}
/* SDK_FUNCTIONS */
static struct sock_db sd;
static struct httpd_req_aux aux;
static httpd_req_t req;
static unsigned char packet[32];
static size_t packet_len, header_len;
static const unsigned char plain[] = {0, 0xff, 0x80};
static void setup(int encoding, int opcode, size_t available, int result, size_t split, size_t pending)
{
memset(&sd, 0, sizeof(sd)); memset(&aux, 0, sizeof(aux));
memset(aux.before, 0xa5, sizeof(aux.before));
memset(aux.mask_key, 0xcc, sizeof(aux.mask_key));
memset(aux.after, 0x5a, sizeof(aux.after));
sd.ws_handshake_done = true; sd.recv_fn = receive; aux.sd = &sd;
aux.ws_type = 2; req.aux = &aux;
size_t n = 0;
packet[n++] = 0x80 | opcode;
packet[n++] = 0x80 | (encoding ? (encoding == 2 ? 126 : 127) : sizeof(plain));
for (int i = 0; i < encoding; i++) packet[n++] = i == encoding - 1 ? sizeof(plain) : 0;
for (int i = 0; i < 4; i++) packet[n++] = (unsigned char)(0x10 + i);
header_len = n;
for (size_t i = 0; i < sizeof(plain); i++) packet[n++] = plain[i] ^ (0x10 + i);
packet_len = n;
limit = MIN(available, n); position = pending; chunk = split;
assert(pending <= limit && pending <= sizeof(sd.pending_data));
memcpy(sd.pending_data + sizeof(sd.pending_data) - pending, packet, pending);
sd.pending_len = pending;
/* Input ends exactly at an inaccessible page, including partial headers. */
long page = sysconf(_SC_PAGESIZE);
wire = mmap(NULL, (size_t)page * 2, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
assert(wire != MAP_FAILED);
assert(mprotect(wire + page, (size_t)page, PROT_NONE) == 0);
wire += page - limit;
memcpy(wire, packet, limit);
terminal_result = result; terminal_calls = sends = 0;
}
static void finish(void)
{
for (size_t i = 0; i < 8; i++) assert(aux.before[i] == 0xa5 && aux.after[i] == 0x5a);
long page = sysconf(_SC_PAGESIZE);
assert(munmap(wire + limit - page, (size_t)page * 2) == 0);
}
static unsigned checks;
static void failure(int encoding, int stage, size_t partial, int error, bool pending, int opcode)
{
/* stage: first byte, second byte, extended length, mask. */
size_t start = stage == 0 ? 0 : stage == 1 ? 1 : stage == 2 ? 2 : 2 + encoding;
setup(encoding, opcode, start + partial, error, 1, pending ? start + partial : 0);
unsigned char output[5]; memset(output, 0xab, sizeof(output));
httpd_ws_frame_t frame = {.payload = output + 1};
esp_err_t ret = httpd_ws_get_frame_type(&req);
if (stage == 0) {
assert(ret == ESP_OK && aux.ws_final && aux.ws_type == HTTPD_WS_TYPE_CLOSE);
assert(frame.len == 0);
} else if (opcode == 2) {
assert(ret == ESP_OK && aux.ws_type == 2 && aux.ws_final);
assert(httpd_ws_recv_frame(&req, &frame, 3) == ESP_FAIL);
assert(frame.type == 2 && frame.final);
assert(frame.len == (stage == 3 ? 3u : 0u));
} else {
assert(ret == ESP_ERR_INVALID_STATE);
assert(aux.ws_type == opcode && aux.ws_final);
}
assert(position == limit && terminal_calls == 1 && sends == 0);
assert(sd.pending_len == 0 && sd.ws_handshake_done && !sd.ws_control_frames);
assert(req.aux == &aux && aux.sd == &sd);
for (size_t i = 0; i < sizeof(output); i++) assert(output[i] == 0xab);
for (size_t i = 0; i < sizeof(aux.mask_key); i++)
assert(aux.mask_key[i] == (stage == 3 && i < partial ? 0x10 + i : 0xcc));
finish(); checks++;
}
static void valid(int encoding, size_t split, size_t pending, int opcode, bool probe)
{
setup(encoding, opcode, 32, 0, split, pending);
unsigned char output[5]; memset(output, 0xab, sizeof(output));
httpd_ws_frame_t frame = {.payload = output + 1};
assert(httpd_ws_get_frame_type(&req) == ESP_OK);
if (opcode == 2) {
if (probe) {
assert(httpd_ws_recv_frame(&req, &frame, 0) == ESP_OK);
assert(frame.len == 3 && position == header_len);
assert(output[1] == 0xab);
}
assert(httpd_ws_recv_frame(&req, &frame, 3) == ESP_OK);
assert(frame.len == 3 && frame.type == 2 && frame.final);
assert(memcmp(output + 1, plain, 3) == 0 && sends == 0);
} else {
assert(sends == 1 && sent_type == (opcode == 9 ? 10 : 8));
assert(sent_len == (opcode == 9 ? 3u : 0u));
if (opcode == 9) assert(memcmp(sent_payload, plain, 3) == 0);
}
assert(output[0] == 0xab && output[4] == 0xab);
assert(position == packet_len && sd.pending_len == 0 && terminal_calls == 0);
finish(); checks++;
}
static void extended_length(int encoding, uint64_t length)
{
setup(encoding, 2, (size_t)(6 + encoding), 0, 1, 0);
for (int i = 0; i < encoding; i++)
wire[2 + i] = (unsigned char)(length >> (8 * (encoding - i - 1)));
httpd_ws_frame_t frame = {0};
assert(httpd_ws_get_frame_type(&req) == ESP_OK);
assert(httpd_ws_recv_frame(&req, &frame, 0) == ESP_OK);
assert(frame.len == length && frame.type == 2 && frame.final);
assert(position == header_len && sends == 0 && terminal_calls == 0);
finish(); checks++;
}
int main(void)
{
const int errors[] = {-1, HTTPD_SOCK_ERR_TIMEOUT, 0};
const int encodings[] = {0, 2, 8};
const int opcodes[] = {2, 9, 8};
for (size_t e = 0; e < 3; e++) {
int encoding = encodings[e];
for (size_t o = 0; o < 3; o++) {
for (int stage = 0; stage < 4; stage++) {
if (stage == 2 && encoding == 0) continue;
size_t extent = stage < 2 ? 1 : stage == 2 ? (size_t)encoding : 4;
for (size_t part = 0; part < extent; part++)
for (size_t err = 0; err < 3; err++)
for (int pending = 0; pending < 2; pending++)
failure(encoding, stage, part, errors[err], pending, opcodes[o]);
}
for (size_t split = 1; split <= 16; split *= 2)
for (size_t pending = 0; pending <= (size_t)(6 + encoding); pending++)
valid(encoding, split, pending, opcodes[o], false);
valid(encoding, 1, 0, opcodes[o], true);
}
}
extended_length(2, 126);
extended_length(2, 65535);
extended_length(8, 65536);
extended_length(8, UINT64_C(0x0102030405060708));
printf("WS actual full receive/blocking/pending functions: %u guard-page/canary cases PASS\n", checks);
return 0;
}
+4 -4
View File
@@ -86,7 +86,7 @@ void heap_caps_free(void *pointer);
""") """)
version = """ version = """
#define ESP_IDF_VERSION_VAL(a,b,c) (((a) << 16) | ((b) << 8) | (c)) #define ESP_IDF_VERSION_VAL(a,b,c) (((a) << 16) | ((b) << 8) | (c))
#define ESP_IDF_VERSION ESP_IDF_VERSION_VAL(5,5,0) #define ESP_IDF_VERSION ESP_IDF_VERSION_VAL(5,5,3)
""" """
version_header = directory / "esp_idf_version.h" version_header = directory / "esp_idf_version.h"
version_header.write_text(version) version_header.write_text(version)
@@ -123,11 +123,11 @@ void heap_caps_free(void *pointer);
"#define CONFIG_HEAP_POISONING_LIGHT 0\n" "#define CONFIG_HEAP_POISONING_LIGHT 0\n"
"#define CONFIG_HEAP_POISONING_COMPREHENSIVE 0\n") "#define CONFIG_HEAP_POISONING_COMPREHENSIVE 0\n")
compile_only() compile_only()
for unsupported in ("5,4,0", "5,5,1", "5,6,0", "6,0,0"): for unsupported in ("5,4,0", "5,5,0", "5,5,1", "5,5,2", "5,5,4", "5,6,0", "6,0,0"):
version_header.write_text(version.replace("5,5,0", unsupported)) version_header.write_text(version.replace("5,5,3", unsupported))
compile_only("Reaudit SSH memory usable extent contract for this IDF") compile_only("Reaudit SSH memory usable extent contract for this IDF")
version_header.write_text(version) version_header.write_text(version)
print("PASS compile guards: 6 invalid poisoning profiles, explicit disabled profile, 4 unsupported IDF versions") print("PASS compile guards: 6 invalid poisoning profiles, explicit disabled profile, 7 unsupported IDF versions")
if args.idf_path: if args.idf_path:
sdk_contract(args.idf_path) sdk_contract(args.idf_path)
version_header.write_text((args.idf_path / "components/esp_common/include/esp_idf_version.h").read_text()) version_header.write_text((args.idf_path / "components/esp_common/include/esp_idf_version.h").read_text())
+1 -1
View File
@@ -29,7 +29,7 @@ int httpd_req_recv(httpd_req_t *, char *, size_t);
""" """
HEADERS["esp_idf_version.h"] = """ HEADERS["esp_idf_version.h"] = """
#define ESP_IDF_VERSION_VAL(a,b,c) ((a)*10000+(b)*100+(c)) #define ESP_IDF_VERSION_VAL(a,b,c) ((a)*10000+(b)*100+(c))
#define ESP_IDF_VERSION ESP_IDF_VERSION_VAL(5,5,0) #define ESP_IDF_VERSION ESP_IDF_VERSION_VAL(5,5,3)
""" """
HEADERS["esp_httpd_priv.h"] = """#pragma once HEADERS["esp_httpd_priv.h"] = """#pragma once
#include <stdint.h> #include <stdint.h>
+1 -1
View File
@@ -38,7 +38,7 @@ assert 'httpd_sess_delete(hd, sock_db);' in sess # queued reusable-pointer clos
assert ssl.index('httpd_sess_set_pending_override') < ssl.index('HTTPD_SSL_USER_CB_SESS_CREATE') assert ssl.index('httpd_sess_set_pending_override') < ssl.index('HTTPD_SSL_USER_CB_SESS_CREATE')
adapter = (ROOT / 'src/web_httpd_adapter.c').read_text() adapter = (ROOT / 'src/web_httpd_adapter.c').read_text()
idle = (ROOT / 'src/web_httpd_idle.c').read_text() idle = (ROOT / 'src/web_httpd_idle.c').read_text()
assert 'ESP_IDF_VERSION_VAL(5, 5, 0)' in adapter assert 'ESP_IDF_VERSION_VAL(5, 5, 3)' in adapter
for forbidden in ('httpd_sess_trigger_close', 'web_diagnostics', 'xTaskCreate', 'malloc(', 'calloc(', 'ESP_LOG'): for forbidden in ('httpd_sess_trigger_close', 'web_diagnostics', 'xTaskCreate', 'malloc(', 'calloc(', 'ESP_LOG'):
assert forbidden not in idle, forbidden assert forbidden not in idle, forbidden
sweep = function(adapter, 'web_httpd_idle_sweep') sweep = function(adapter, 'web_httpd_idle_sweep')
+45 -5
View File
@@ -1,5 +1,8 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
"""Compile actual transport callbacks using the existing serial/store doubles.""" """Compile actual transport callbacks using the existing serial/store doubles."""
import argparse
import importlib.util
import json
import os import os
import pathlib import pathlib
import re import re
@@ -14,7 +17,40 @@ sys.path.insert(0, str(BASE))
from run import HEADERS from run import HEADERS
from serial_headers import SERIAL_HEADERS from serial_headers import SERIAL_HEADERS
os.environ['CCACHE_DISABLE'] = '1' os.environ['CCACHE_DISABLE'] = '1'
IDF = pathlib.Path(os.environ.get('IDF_PATH', str(pathlib.Path.home() / '.platformio/packages/framework-espidf'))) parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--idf-path', type=pathlib.Path, default=pathlib.Path(os.environ.get(
'IDF_PATH', str(pathlib.Path.home() / '.platformio/packages/framework-espidf'))))
parser.add_argument('--build-dir', type=pathlib.Path,
help='Require the actual generated WS compilation input and verify its bytes')
parser.add_argument('--sanitize', action='store_true')
args = parser.parse_args()
IDF = args.idf_path.resolve()
def generated_ws():
spec = importlib.util.spec_from_file_location('performance_security_overrides', ROOT / 'tools/security_overrides.py')
sdk = importlib.util.module_from_spec(spec)
sys.modules[spec.name] = sdk
spec.loader.exec_module(sdk)
sdk.verify_version(IDF)
entry = next(e for e in sdk.ENTRIES if e.name == 'httpd_ws')
original, rendered = sdk.render_entry(entry, {'idf': IDF, 'project': ROOT})
if args.build_dir:
build = args.build_dir.resolve()
expected = build / 'security_overrides/httpd_ws/httpd_ws.c'
commands = json.loads((build / 'compile_commands.json').read_text())
def input_path(command):
return (pathlib.Path(command['directory']) / command['file']).resolve()
matches = [c for c in commands if input_path(c) == expected]
assert len(matches) == 1, ('missing/ambiguous generated WS compilation input', matches)
assert not any(input_path(c) == original.resolve() for c in commands), 'vendor WS still compiled'
command = matches[0].get('command') or ' '.join(matches[0]['arguments'])
assert 'CMakeFiles/__idf_esp_http_server.dir/' in command, 'wrong WS owner'
assert expected.read_bytes() == rendered, 'stale generated WS input'
print('WS input: verified actual compilation input', expected)
return expected.read_text()
print('WS input: current hash-verified override render (not firmware build evidence)')
return rendered.decode()
def function(source, name): def function(source, name):
match = re.search(r'^(?:static )?(?:esp_err_t|int|ssize_t) ' + name + r'\(.*?^\}', source, re.M | re.S) match = re.search(r'^(?:static )?(?:esp_err_t|int|ssize_t) ' + name + r'\(.*?^\}', source, re.M | re.S)
@@ -22,8 +58,8 @@ def function(source, name):
return match.group() + '\n' return match.group() + '\n'
adapter = (ROOT / 'src/web_httpd_adapter.c').read_text() adapter = (ROOT / 'src/web_httpd_adapter.c').read_text()
assert 'ESP_IDF_VERSION_VAL(5, 5, 0)' in adapter assert 'ESP_IDF_VERSION_VAL(5, 5, 3)' in adapter
ws_source = (IDF / 'components/esp_http_server/src/httpd_ws.c').read_text() ws_source = generated_ws()
ws = function(ws_source, 'httpd_ws_send_frame_async') ws = function(ws_source, 'httpd_ws_send_frame_async')
main = (IDF / 'components/esp_http_server/src/httpd_main.c').read_text() main = (IDF / 'components/esp_http_server/src/httpd_main.c').read_text()
assert main.index('/* Case0:') < main.index('httpd_process_ctrl_msg(hd);') < main.index('/* Case1:') assert main.index('/* Case0:') < main.index('httpd_process_ctrl_msg(hd);') < main.index('/* Case1:')
@@ -53,6 +89,10 @@ with tempfile.TemporaryDirectory(prefix='web-performance-') as directory:
(tmp / 'fixture.c').write_text(fixture) (tmp / 'fixture.c').write_text(fixture)
(tmp / 'binary.inc').write_text(function(adapter, 'web_httpd_aborted_send') + function(adapter, 'web_httpd_ws_send_binary')) (tmp / 'binary.inc').write_text(function(adapter, 'web_httpd_aborted_send') + function(adapter, 'web_httpd_ws_send_binary'))
(tmp / 'sdk_ws.inc').write_text(ws.replace('httpd_ws_send_frame_async(', 'sdk_ws_send_frame(')) (tmp / 'sdk_ws.inc').write_text(ws.replace('httpd_ws_send_frame_async(', 'sdk_ws_send_frame('))
private = (IDF / 'components/esp_http_server/src/esp_httpd_priv.h').read_text()
receive_options = re.search(r'typedef enum \{[^}]*\} httpd_recv_opt_t;', private)
assert receive_options, 'Reaudit SDK receive option type'
(tmp / 'sdk_recv_options.inc').write_text(receive_options.group())
automatic = ''.join(function(ws_source, name) for name in automatic = ''.join(function(ws_source, name) for name in
('httpd_ws_check_req', 'httpd_ws_send_frame', 'httpd_ws_get_frame_type')) ('httpd_ws_check_req', 'httpd_ws_send_frame', 'httpd_ws_get_frame_type'))
automatic = automatic.replace('httpd_ws_send_frame_async(', 'sdk_ws_send_frame(') automatic = automatic.replace('httpd_ws_send_frame_async(', 'sdk_ws_send_frame(')
@@ -65,10 +105,10 @@ with tempfile.TemporaryDirectory(prefix='web-performance-') as directory:
(tmp / 'console.inc').write_text( (tmp / 'console.inc').write_text(
'static const char *esp_err_to_name(esp_err_t e) { (void)e; return "error"; }\n' 'static const char *esp_err_to_name(esp_err_t e) { (void)e; return "error"; }\n'
+ console[start:end]) + console[start:end])
flags = ['-fsanitize=address,undefined', '-fno-omit-frame-pointer'] if '--sanitize' in sys.argv else [] flags = ['-fsanitize=address,undefined', '-fno-omit-frame-pointer'] if args.sanitize else []
subprocess.run(['cc', '-std=c11', '-Wall', '-Wextra', '-Werror', '-g', *flags, subprocess.run(['cc', '-std=c11', '-Wall', '-Wextra', '-Werror', '-g', *flags,
'-I'+str(tmp), '-I'+str(ROOT / 'src'), '-ffunction-sections', '-fdata-sections', '-I'+str(tmp), '-I'+str(ROOT / 'src'), '-ffunction-sections', '-fdata-sections',
'-Wl,--gc-sections', str(HERE / 'test.c'), str(ROOT / 'src/web_session_store.c'), '-Wl,--gc-sections', str(HERE / 'test.c'), str(ROOT / 'src/web_session_store.c'),
str(ROOT / 'src/web_auth_parse.c'), '-o', str(tmp / 'test')], check=True, timeout=30) str(ROOT / 'src/web_auth_parse.c'), '-o', str(tmp / 'test')], check=True, timeout=30)
subprocess.run([str(tmp / 'test')], check=True, timeout=15) subprocess.run([str(tmp / 'test')], check=True, timeout=15)
print('PASS installed IDF WS two-write / HTTPS forwarding / TLS partial-return contract guards') print('PASS generated IDF WS two-write / HTTPS forwarding / TLS partial-return contract guards')
+3 -2
View File
@@ -61,9 +61,10 @@ static int host_shutdown(int fd, int how)
#define HTTPD_WS_TYPE_PONG 10 #define HTTPD_WS_TYPE_PONG 10
static uint8_t incoming_opcode; static uint8_t incoming_opcode;
static unsigned automatic_reads; static unsigned automatic_reads;
static int httpd_recv_with_opt(httpd_req_t *r, char *out, size_t n, bool peek) #include "sdk_recv_options.inc"
static int httpd_recv_with_opt(httpd_req_t *r, char *out, size_t n, httpd_recv_opt_t opt)
{ {
(void)r; assert(n == 1 && !peek); *out = 0x80 | incoming_opcode; (void)r; assert(n == 1 && opt == HTTPD_RECV_OPT_BLOCKING); *out = 0x80 | incoming_opcode;
++automatic_reads; return 1; ++automatic_reads; return 1;
} }
static esp_err_t sdk_control_recv(httpd_req_t *r, httpd_ws_frame_t *f, size_t n) static esp_err_t sdk_control_recv(httpd_req_t *r, httpd_ws_frame_t *f, size_t n)
+25 -25
View File
@@ -279,8 +279,8 @@
122 122
], ],
"root": "sdk", "root": "sdk",
"sha256": "09210c5a601647ca5775d127a2951bab2f3e509192b53487bbea8a93d8731b78", "sha256": "edc39052244526cb91c93a16bc765194031060e5560fd432e75542f2f6c8db53",
"size": 50176 "size": 54323
}, },
{ {
"output_sha256": "cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30", "output_sha256": "cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30",
@@ -300,7 +300,7 @@
124 124
], ],
"root": "sdk", "root": "sdk",
"sha256": "6bba77064aaa68a06f8d4c01432064a1b050c91ed22741c547785b0d8a6c07d8", "sha256": "db7fbbb322bccb4a21bc1607208a6bb7d1f4c0d7ecc3299de10c9e60edaa943b",
"size": 42937 "size": 42937
}, },
{ {
@@ -312,8 +312,8 @@
123 123
], ],
"root": "sdk", "root": "sdk",
"sha256": "6481942b62e51125e2a43441fa0900cbda74bd2ea05c82f0c29eb4933c31946e", "sha256": "a2a5ca0549fbe8d1ddd7f9647a48a31fdd6329b997e1550ab69d6a10efecff0d",
"size": 15257 "size": 15459
}, },
{ {
"output_sha256": "cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30", "output_sha256": "cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30",
@@ -355,7 +355,7 @@
"size": 1036 "size": 1036
}, },
{ {
"output_sha256": "8e3d9f79bbbdf6dfd6fbf132982a7ec3a45ff6dac92277034316efae4cccad84", "output_sha256": "e53c9bcefd4327c713696e870a3568a27cedf8833467b9cd9536cb2e09ab7ad8",
"path": "components/freertos/FreeRTOS-Kernel/include/freertos/task.h", "path": "components/freertos/FreeRTOS-Kernel/include/freertos/task.h",
"purpose": "Original opening attribution/license comment(s), exact full-source hash and excerpt", "purpose": "Original opening attribution/license comment(s), exact full-source hash and excerpt",
"range": [ "range": [
@@ -363,8 +363,8 @@
1455 1455
], ],
"root": "sdk", "root": "sdk",
"sha256": "ac666bcbe4acb21c44cc820631f8c579d089698967015054c5ab42d092dddc2d", "sha256": "8b908b964d2c87377838762478253eec04312b985e6bb680bacd3f03ab253897",
"size": 133072 "size": 133108
}, },
{ {
"output_sha256": "968c3f0d60fb9ee8cc1047503823e75ccc28bf6e7e7b0451894b01c1e400d939", "output_sha256": "968c3f0d60fb9ee8cc1047503823e75ccc28bf6e7e7b0451894b01c1e400d939",
@@ -465,8 +465,8 @@
223 223
], ],
"root": "sdk", "root": "sdk",
"sha256": "b726c0c55bc5f32255f129d55f9f2fface85ce83de90a2d16c9017b93b738bff", "sha256": "0154e70f20b465213d3f97a9c2b75e686d79ee83592f90e1d7e7d995593ca7e1",
"size": 329921 "size": 336963
}, },
{ {
"output_sha256": "0664aa34d3da2b71799883b2fc3250286f76ee69ed0b3676d53d25b635ee721d", "output_sha256": "0664aa34d3da2b71799883b2fc3250286f76ee69ed0b3676d53d25b635ee721d",
@@ -477,8 +477,8 @@
169 169
], ],
"root": "sdk", "root": "sdk",
"sha256": "fd399239aee30384786a19b47bfe5dd22b979d5d89bb38f29f0c82a3d81daaf7", "sha256": "55edce5b8fcb039a404b84ea830f06d018128b155dfbf0d74d26b17247b546c6",
"size": 19803 "size": 19934
}, },
{ {
"output_sha256": "20ed622a5f1a273258b415097609f6a236fb2f23f8c593c63e29e6cc77de0032", "output_sha256": "20ed622a5f1a273258b415097609f6a236fb2f23f8c593c63e29e6cc77de0032",
@@ -547,30 +547,30 @@
"size": 64919 "size": 64919
}, },
{ {
"output_sha256": "1cc05e4ea172de6be2e6b961994de42a0f7433406619f6474811b11066718908", "output_sha256": "230c52993499adf9f64d1d6477495ea46c3f14d63022bd52081858373098da68",
"path": "package.json", "path": "package.json",
"purpose": "Installed SDK package 3.50500.0 identity", "purpose": "Installed SDK package 3.50503.0 identity",
"range": null, "range": null,
"root": "sdk", "root": "sdk",
"sha256": "1cc05e4ea172de6be2e6b961994de42a0f7433406619f6474811b11066718908", "sha256": "230c52993499adf9f64d1d6477495ea46c3f14d63022bd52081858373098da68",
"size": 536 "size": 536
}, },
{ {
"output_sha256": "6474d3ae428762830d36e3b179c6e1622f8f5dc7d32ea94f47be9c122c467979", "output_sha256": "b473c8e2ed91b38d7e96e54d4ce094474ca6015c0641b7cdfc0ab94b1f8b4668",
"path": "version.txt", "path": "version.txt",
"purpose": "Installed IDF 5.5.0 identity", "purpose": "Installed IDF 5.5.3 identity",
"range": null, "range": null,
"root": "sdk", "root": "sdk",
"sha256": "6474d3ae428762830d36e3b179c6e1622f8f5dc7d32ea94f47be9c122c467979", "sha256": "b473c8e2ed91b38d7e96e54d4ce094474ca6015c0641b7cdfc0ab94b1f8b4668",
"size": 5 "size": 5
}, },
{ {
"output_sha256": "dd464ff1928a475656430e04342a94f0293d2aaf4447bdae438a69c7accc1698", "output_sha256": "bdd3b852d051d6513912e71d29851bd04b800e2d33d78aec48067af5d83da5c9",
"path": "package.json", "path": "package.json",
"purpose": "Installed Xtensa toolchain 14.2.0+20241119 identity; not runtime license classification", "purpose": "Installed Xtensa toolchain 14.2.0+20251107 identity; not runtime license classification",
"range": null, "range": null,
"root": "toolchain", "root": "toolchain",
"sha256": "dd464ff1928a475656430e04342a94f0293d2aaf4447bdae438a69c7accc1698", "sha256": "bdd3b852d051d6513912e71d29851bd04b800e2d33d78aec48067af5d83da5c9",
"size": 472 "size": 472
}, },
{ {
@@ -720,7 +720,7 @@
], ],
"schema": 1, "schema": 1,
"snapshot": { "snapshot": {
"idf": "5.5.0", "idf": "5.5.3",
"managed": { "managed": {
"espressif__esp_tinyusb": "2.2.1", "espressif__esp_tinyusb": "2.2.1",
"espressif__led_strip": "3.0.3", "espressif__led_strip": "3.0.3",
@@ -729,8 +729,8 @@
"wolfssl__wolfssh": "1.4.20", "wolfssl__wolfssh": "1.4.20",
"wolfssl__wolfssl": "5.8.2~1" "wolfssl__wolfssl": "5.8.2~1"
}, },
"reviewed": "2026-09-16", "reviewed": "2026-09-18",
"sdk_package": "3.50500.0", "sdk_package": "3.50503.0",
"toolchain_package": "14.2.0+20241119" "toolchain_package": "14.2.0+20251107"
} }
} }
+58
View File
@@ -0,0 +1,58 @@
{
"review_date": "2026-09-16",
"host": "linux_x86_64",
"artifacts": [
{
"package": "platformio/platform/espressif32",
"version": "6.13.0",
"registry_url": "https://api.registry.platformio.org/v3/packages/platformio/platform/espressif32",
"name": "espressif32-6.13.0.tar.gz",
"size": 1009115,
"checksum": {
"sha256": "5d1032b43828773ba87cf2e509432202c0bfe64f7304b58c9d669f13b116c6e0"
},
"system": "*",
"download_url": "https://dl.registry.platformio.org/download/platformio/platform/espressif32/6.13.0/espressif32-6.13.0.tar.gz"
},
{
"package": "platformio/tool/framework-espidf",
"version": "3.50503.0",
"registry_url": "https://api.registry.platformio.org/v3/packages/platformio/tool/framework-espidf",
"name": "framework-espidf-3.50503.0.tar.gz",
"size": 76402966,
"checksum": {
"sha256": "8353f6fd5030dd7e662500891428fad15d46efd7e4b718cab2fe6bfb9e7f13fc"
},
"system": "*",
"download_url": "https://dl.registry.platformio.org/download/platformio/tool/framework-espidf/3.50503.0/framework-espidf-3.50503.0.tar.gz"
},
{
"package": "platformio/tool/toolchain-xtensa-esp-elf",
"version": "14.2.0+20251107",
"registry_url": "https://api.registry.platformio.org/v3/packages/platformio/tool/toolchain-xtensa-esp-elf",
"name": "toolchain-xtensa-esp-elf-linux_x86_64-14.2.0+20251107.tar.gz",
"size": 322439270,
"checksum": {
"sha256": "a5de49ce3299b0d9253ab6a423648bc23113db96b34a7cc8e57702cae1bb190e"
},
"system": [
"linux_x86_64"
],
"download_url": "https://dl.registry.platformio.org/download/platformio/tool/toolchain-xtensa-esp-elf/14.2.0+20251107/toolchain-xtensa-esp-elf-linux_x86_64-14.2.0+20251107.tar.gz"
},
{
"package": "platformio/tool/toolchain-riscv32-esp",
"version": "14.2.0+20251107",
"registry_url": "https://api.registry.platformio.org/v3/packages/platformio/tool/toolchain-riscv32-esp",
"name": "toolchain-riscv32-esp-linux_x86_64-14.2.0+20251107.tar.gz",
"size": 592894688,
"checksum": {
"sha256": "1af8e233931500b8712079808e4974413d95d3601d03275dff79665c436e9d33"
},
"system": [
"linux_x86_64"
],
"download_url": "https://dl.registry.platformio.org/download/platformio/tool/toolchain-riscv32-esp/14.2.0+20251107/toolchain-riscv32-esp-linux_x86_64-14.2.0+20251107.tar.gz"
}
]
}
+139
View File
@@ -0,0 +1,139 @@
#!/usr/bin/env python3
"""Verify official candidate archives; optionally fetch or prepare an isolated smoke project."""
import argparse
import hashlib
import json
import pathlib
import platform
import tarfile
import urllib.parse
import urllib.request
ROOT = pathlib.Path(__file__).resolve().parents[2]
BASE = ROOT / ".pio/idf-candidate-5.5.3"
LOCK = pathlib.Path(__file__).with_name("artifacts.json")
class RegistryRedirects(urllib.request.HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
check_url(newurl)
return super().redirect_request(req, fp, code, msg, headers, newurl)
def check_url(url):
parsed = urllib.parse.urlsplit(url)
if parsed.scheme != "https" or parsed.hostname not in {
"dl.registry.platformio.org", "dl.registry.nm1.platformio.org",
}:
raise ValueError("Unapproved artifact host: " + url)
def verify(path, item):
digest = hashlib.sha256()
with path.open("rb") as stream:
for block in iter(lambda: stream.read(1024 * 1024), b""):
digest.update(block)
if path.stat().st_size != item["size"] or digest.hexdigest() != item["checksum"]["sha256"]:
raise ValueError("Artifact size/hash mismatch: " + str(path))
def member(archive, name):
matches = [m for m in archive.getmembers() if m.name.removeprefix("./") == name and m.isfile()]
if len(matches) != 1:
raise ValueError("Missing/ambiguous archive member: " + name)
return archive.extractfile(matches[0]).read()
def require(condition):
if not condition:
raise ValueError("Candidate package contract mismatch")
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--fetch", action="store_true", help="Download absent pinned archives; never install shared packages")
parser.add_argument("--prepare", action="store_true", help="Create a fresh, separate smoke project after verifying all four archives")
parser.add_argument("--sdk-only", action="store_true", help="Verify/download platform and SDK only, not toolchains; cannot prepare")
args = parser.parse_args()
if args.prepare and args.sdk_only:
parser.error("--prepare requires both toolchain archives")
if platform.system() != "Linux" or platform.machine() != "x86_64":
parser.error("This lock is for Linux x86_64 only")
# Do not follow a user-created candidate/cache symlink outside the workspace.
for path in (ROOT / ".pio", BASE, BASE / "archives"):
if path.is_symlink():
raise ValueError("Refusing symlink: " + str(path))
path.mkdir(exist_ok=True)
items = json.loads(LOCK.read_text())["artifacts"]
selected = items[:2] if args.sdk_only else items
opener = urllib.request.build_opener(RegistryRedirects())
for item in selected:
path = BASE / "archives" / item["name"]
if path.is_symlink():
raise ValueError("Refusing symlink: " + str(path))
if not path.exists() and args.fetch:
check_url(item["download_url"])
temporary = path.with_suffix(".partial")
with temporary.open("xb") as target:
try:
with opener.open(item["download_url"], timeout=60) as source:
total = 0
while block := source.read(1024 * 1024):
total += len(block)
if total > item["size"]:
raise ValueError("Download exceeds pinned size")
target.write(block)
except BaseException:
temporary.unlink()
raise
try:
verify(temporary, item)
temporary.rename(path)
finally:
temporary.unlink(missing_ok=True)
verify(path, item)
print("SHA256 PASS", item["name"], flush=True)
with tarfile.open(BASE / "archives" / items[0]["name"]) as archive:
manifest = json.loads(member(archive, "platform.json"))
require(manifest["version"] == "6.13.0")
require(manifest["packages"]["framework-espidf"]["version"] == "~3.50503.0")
require(manifest["packages"]["toolchain-xtensa-esp-elf"]["version"] == "14.2.0+20251107")
with tarfile.open(BASE / "archives" / items[1]["name"]) as archive:
require(json.loads(member(archive, "package.json"))["version"] == "3.50503.0")
tools = json.loads(member(archive, "tools/tools.json"))
xtensa = next(t for t in tools["tools"] if t["name"] == "xtensa-esp-elf")
require(any(v["name"] == "esp-14.2.0_20251107" and v["status"] == "recommended" for v in xtensa["versions"]))
header = member(archive, "components/wpa_supplicant/esp_supplicant/src/esp_wifi_driver.h").decode()
require("(*wpa_ap_get_wpa_ie)(size_t *len)" in header)
expected = {
"core": "9f7b14a8bf6eec64973da8adc65d35b5ba9bee49",
"espnow": "132b4f67e339ca2081d2add91c14eefa39476ff9",
"mesh": "2e9dc1c8c7afbf033337b4175032e9b1161e3262",
"net80211": "2800d447ec385d33869373696ba8191292647694",
"pp": "8944bcad7371621045f376cf74c62fde6f368cbb",
"smartconfig": "4dc759e25617aa00b9e12887fd092a1d5780a170",
"wapi": "65655b6feab0572a6e8a1200946d53a21f3f4722",
}
for name, digest in expected.items():
data = member(archive, "components/esp_wifi/lib/esp32s3/lib" + name + ".a")
require(hashlib.sha1(b"blob " + str(len(data)).encode() + b"\0" + data).hexdigest() == digest)
print("Official manifest/toolchain, fixed callback ABI and all seven release Wi-Fi archives PASS")
if args.prepare:
project = BASE / "smoke"
project.mkdir(exist_ok=False)
(project / "src").mkdir()
(project / "src/main.c").write_text("void app_main(void) {}\n")
(project / "src/CMakeLists.txt").write_text('idf_component_register(SRCS "main.c" INCLUDE_DIRS ".")\n')
(project / "CMakeLists.txt").write_text('cmake_minimum_required(VERSION 3.16)\ninclude($ENV{IDF_PATH}/tools/cmake/project.cmake)\nproject(idf_candidate_smoke)\n')
config = "[platformio]\ncore_dir = " + str(BASE / "core") + "\n\n[env:candidate]\n"
config += "platform = " + (BASE / "archives" / items[0]["name"]).as_uri().replace("%2B", "+").replace("%", "%%") + "\n"
config += "board = esp32-s3-devkitc-1\nframework = espidf\nplatform_packages =\n"
for item in items[1:]:
config += " " + item["package"].split("/")[-1] + " @ " + (BASE / "archives" / item["name"]).as_uri().replace("%2B", "+").replace("%", "%%") + "\n"
(project / "platformio.ini").write_text(config)
print("Prepared", project)
print("Smoke only: not production sources, board customization, managed dependencies or security overrides.")
if __name__ == "__main__":
main()
+74
View File
@@ -0,0 +1,74 @@
#!/usr/bin/env python3
"""Stage current tracked working-tree inputs plus explicitly reviewed test inputs."""
import argparse
import hashlib
import json
import pathlib
import subprocess
ROOT = pathlib.Path(__file__).resolve().parents[2]
BASE = ROOT / ".pio/idf-candidate-5.5.3"
EXACT = {"CMakeLists.txt", "platformio.ini", "extra_script.py", "partitions.csv",
"sdkconfig.defaults", "dependencies.lock", "tools/security_overrides.py"}
PREFIXES = ("src/", "cmake/", "boards/", "tests/", "tools/wolfssh_order/")
UNTRACKED = {"tests/sdk_security_overrides/ws.c"}
def stage(destination, root=ROOT, base=BASE, items=None):
# A single fresh child prevents traversal into archives/core or old app outputs.
if destination in {"", ".", ".."} or pathlib.Path(destination).name != destination:
raise ValueError("Destination must be a single fresh candidate directory name")
dest = base / destination
for path in (root / ".pio", base, dest):
if path.is_symlink():
raise ValueError("Refusing symlink destination: " + str(path))
if base.resolve() != root.resolve() / ".pio/idf-candidate-5.5.3":
raise ValueError("Candidate root is not confined to the project")
if dest.exists():
raise FileExistsError(dest)
if items is None:
items = json.loads(pathlib.Path(__file__).with_name("artifacts.json").read_text())["artifacts"]
tracked = subprocess.check_output(["git", "--no-pager", "ls-files", "-z"], cwd=root).decode().split("\0")
names = {name for name in tracked if name in EXACT or name.startswith(PREFIXES)}
names.update(name for name in UNTRACKED if (root / name).exists())
inputs = {}
for name in sorted(names):
source = root / name
if source.is_symlink() or source.resolve() != root.resolve() / name:
raise ValueError("Refusing symlink source: " + name)
inputs[name] = source.read_bytes()
config = inputs["platformio.ini"].decode()
replacements = {"[platformio]\n": "[platformio]\ncore_dir = " + str(base / "core") + "\n"}
for index, item in enumerate(items):
package = "platformio/" + item["package"].split("/")[-1]
uri = (base / "archives" / item["name"]).as_uri().replace("%2B", "+").replace("%", "%%")
replacements[package + "@" + item["version"]] = uri if index == 0 else package.split("/")[-1] + " @ " + uri
for old, new in replacements.items():
if config.count(old) != 1:
raise ValueError("Missing/ambiguous platform configuration anchor: " + old)
config = config.replace(old, new, 1)
inputs["platformio.ini"] = config.encode()
dest.mkdir(exist_ok=False)
manifest = {}
for name, data in inputs.items():
target = dest / name
target.parent.mkdir(parents=True, exist_ok=True)
target.write_bytes(data)
manifest[name] = hashlib.sha256(data).hexdigest()
(dest / "app-source-manifest.json").write_text(json.dumps(manifest, indent=2) + "\n")
print("Staged", len(manifest), "working-tree inputs in", dest)
print("No existing sdkconfig, secrets, .git, cache or managed components copied.")
print("Copy the six hash-verified locked managed-component directories separately before building.")
return dest
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--destination", required=True, help="Fresh direct child name under .pio/idf-candidate-5.5.3 (e.g. app-validated)")
args = parser.parse_args()
stage(args.destination)
if __name__ == "__main__":
main()
+98
View File
@@ -0,0 +1,98 @@
"""Offline fail-closed helper checks; does not download/install/build."""
import hashlib
import io
import pathlib
import tarfile
import tempfile
import unittest
import json
from unittest import mock
import stage_app
import prepare
class Contracts(unittest.TestCase):
def test_hash_and_size(self):
with tempfile.TemporaryDirectory() as directory:
path = pathlib.Path(directory) / "artifact"
path.write_bytes(b"candidate")
item = {"size": 9, "checksum": {"sha256": hashlib.sha256(b"candidate").hexdigest()}}
prepare.verify(path, item)
path.write_bytes(b"Candidate")
with self.assertRaises(ValueError):
prepare.verify(path, item)
path.write_bytes(b"candidate-extra")
with self.assertRaises(ValueError):
prepare.verify(path, item)
def test_network_allowlist(self):
prepare.check_url("https://dl.registry.platformio.org/download/a")
prepare.check_url("https://dl.registry.nm1.platformio.org/download/a")
for url in ("http://dl.registry.platformio.org/a", "https://example.com/a", "https://dl.registry.platformio.org.evil/a"):
with self.assertRaises(ValueError):
prepare.check_url(url)
def test_contract_failure(self):
with self.assertRaises(ValueError):
prepare.require(False)
def test_member_ambiguity(self):
stream = io.BytesIO()
with tarfile.open(fileobj=stream, mode="w") as archive:
for name in ("package.json", "./package.json"):
entry = tarfile.TarInfo(name)
entry.size = 2
archive.addfile(entry, io.BytesIO(b"{}"))
stream.seek(0)
with tarfile.open(fileobj=stream) as archive:
with self.assertRaises(ValueError):
prepare.member(archive, "package.json")
with self.assertRaises(ValueError):
prepare.member(archive, "missing")
class Staging(unittest.TestCase):
def test_fresh_snapshot_and_manifest(self):
with tempfile.TemporaryDirectory() as directory:
root = pathlib.Path(directory)
base = root / ".pio/idf-candidate-5.5.3"
base.mkdir(parents=True)
(base / "app").mkdir()
(base / "app/sentinel").write_text("old")
(root / "platformio.ini").write_text("[platformio]\nplatform = platformio/espressif32@6.13.0\n")
(root / "src").mkdir()
(root / "src/main.c").write_text("modified working tree")
(root / "src/secret").write_text("never copy")
ws = root / "tests/sdk_security_overrides/ws.c"
ws.parent.mkdir(parents=True)
ws.write_text("explicit fixture")
items = [{"package": "platformio/platform/espressif32", "version": "6.13.0", "name": "platform.tar.gz"}]
with mock.patch.object(stage_app.subprocess, "check_output", return_value=b"platformio.ini\0src/main.c\0"):
dest = stage_app.stage("app-validated", root, base, items)
manifest = json.loads((dest / "app-source-manifest.json").read_text())
for name, digest in manifest.items():
self.assertEqual(hashlib.sha256((dest / name).read_bytes()).hexdigest(), digest)
self.assertIn(str(base / "core"), (dest / "platformio.ini").read_text())
self.assertEqual((dest / "src/main.c").read_text(), "modified working tree")
self.assertTrue((dest / "tests/sdk_security_overrides/ws.c").exists())
self.assertFalse((dest / "src/secret").exists())
self.assertEqual((base / "app/sentinel").read_text(), "old")
for name in ("app", "app-validated"):
with self.assertRaises(FileExistsError):
stage_app.stage(name, root, base, items)
for name in ("../escape", "/tmp/escape", "", ".", "..", "app/nested"):
with self.assertRaises(ValueError):
stage_app.stage(name, root, base, items)
(base / "linked").symlink_to(root, target_is_directory=True)
with self.assertRaises(ValueError):
stage_app.stage("linked", root, base, items)
(root / "src/main.c").unlink()
(root / "src/main.c").symlink_to(root / "platformio.ini")
with self.assertRaises(ValueError):
stage_app.stage("unsafe", root, base, items)
self.assertFalse((base / "unsafe").exists())
if __name__ == "__main__":
unittest.main()
+24 -7
View File
@@ -461,13 +461,13 @@ ENTRIES = (
)), )),
Entry("mbedtls_ssl_tls", "mbedtls", "idf", Entry("mbedtls_ssl_tls", "mbedtls", "idf",
"components/mbedtls/mbedtls/library/ssl_tls.c", "components/mbedtls/mbedtls/library/ssl_tls.c",
"b726c0c55bc5f32255f129d55f9f2fface85ce83de90a2d16c9017b93b738bff", ( "0154e70f20b465213d3f97a9c2b75e686d79ee83592f90e1d7e7d995593ca7e1", (
Edit(' MBEDTLS_SSL_DEBUG_RET(1, "calc_verify", ret);\n', Edit(' MBEDTLS_SSL_DEBUG_RET(1, "calc_verify", ret);\n',
' MBEDTLS_SSL_DEBUG_RET(1, "calc_verify", ret);\n return ret;\n'), ' MBEDTLS_SSL_DEBUG_RET(1, "calc_verify", ret);\n return ret;\n'),
), target="mbedtls"), ), target="mbedtls"),
Entry("mbedtls_x509_create", "mbedtls", "idf", Entry("mbedtls_x509_create", "mbedtls", "idf",
"components/mbedtls/mbedtls/library/x509_create.c", "components/mbedtls/mbedtls/library/x509_create.c",
"fd399239aee30384786a19b47bfe5dd22b979d5d89bb38f29f0c82a3d81daaf7", ( "55edce5b8fcb039a404b84ea830f06d018128b155dfbf0d74d26b17247b546c6", (
Edit(" oid.p = mbedtls_calloc(1, oid.len);\n", Edit(" oid.p = mbedtls_calloc(1, oid.len);\n",
""" oid.p = mbedtls_calloc(1, oid.len); """ oid.p = mbedtls_calloc(1, oid.len);
if (oid.p == NULL) { if (oid.p == NULL) {
@@ -553,7 +553,7 @@ ENTRIES = (
)), )),
Entry("https_server", "esp_https_server", "idf", Entry("https_server", "esp_https_server", "idf",
"components/esp_https_server/src/https_server.c", "components/esp_https_server/src/https_server.c",
"6481942b62e51125e2a43441fa0900cbda74bd2ea05c82f0c29eb4933c31946e", ( "a2a5ca0549fbe8d1ddd7f9647a48a31fdd6329b997e1550ab69d6a10efecff0d", (
Edit('const static char *TAG = "esp_https_server";\n', Edit('const static char *TAG = "esp_https_server";\n',
WIPE + 'const static char *TAG = "esp_https_server";\n'), WIPE + 'const static char *TAG = "esp_https_server";\n'),
Edit(""" if (!transport_ctx) { Edit(""" if (!transport_ctx) {
@@ -598,7 +598,7 @@ ENTRIES = (
)), )),
Entry("httpd_parse", "esp_http_server", "idf", Entry("httpd_parse", "esp_http_server", "idf",
"components/esp_http_server/src/httpd_parse.c", "components/esp_http_server/src/httpd_parse.c",
"6bba77064aaa68a06f8d4c01432064a1b050c91ed22741c547785b0d8a6c07d8", ( "db7fbbb322bccb4a21bc1607208a6bb7d1f4c0d7ecc3299de10c9e60edaa943b", (
Edit('static const char *TAG = "httpd_parse";\n', Edit('static const char *TAG = "httpd_parse";\n',
WIPE + SCRATCH_RESIZE + 'static const char *TAG = "httpd_parse";\n'), WIPE + SCRATCH_RESIZE + 'static const char *TAG = "httpd_parse";\n'),
Edit(" size_t at_offset = parser_data->last.at - raux->scratch;\n", Edit(" size_t at_offset = parser_data->last.at - raux->scratch;\n",
@@ -623,12 +623,25 @@ ENTRIES = (
)), )),
Entry("esp_tls_mbedtls", "esp-tls", "idf", Entry("esp_tls_mbedtls", "esp-tls", "idf",
"components/esp-tls/esp_tls_mbedtls.c", "components/esp-tls/esp_tls_mbedtls.c",
"09210c5a601647ca5775d127a2951bab2f3e509192b53487bbea8a93d8731b78", ( "edc39052244526cb91c93a16bc765194031060e5560fd432e75542f2f6c8db53", (
Edit('static const char *TAG = "esp-tls-mbedtls";\n', Edit('static const char *TAG = "esp-tls-mbedtls";\n',
TLS_GUARDS + 'static const char *TAG = "esp-tls-mbedtls";\n'), TLS_GUARDS + 'static const char *TAG = "esp-tls-mbedtls";\n'),
Edit(" mbedtls_ssl_conf_set_user_data_p(&tls->conf, cfg->userdata);\n", Edit(" mbedtls_ssl_conf_set_user_data_p(&tls->conf, cfg->userdata);\n",
TLS_POLICY + " mbedtls_ssl_conf_set_user_data_p(&tls->conf, cfg->userdata);\n"), TLS_POLICY + " mbedtls_ssl_conf_set_user_data_p(&tls->conf, cfg->userdata);\n"),
)), )),
# 5.5.3 introduced blocking header reads, but sizeof promotes negative
# receive errors to unsigned. These fixed header extents are at most 8 bytes.
Entry("httpd_ws", "esp_http_server", "idf",
"components/esp_http_server/src/httpd_ws.c",
"a02194bc8adb1a1707f680390f30ccfab95cd83230cbe4aedcadda2999c9649c",
tuple(Edit(f"HTTPD_RECV_OPT_BLOCKING) < sizeof({name}))",
f"HTTPD_RECV_OPT_BLOCKING) < (int)sizeof({name}))")
for name in ("second_byte", "aux->mask_key", "first_byte")) +
tuple(Edit(f'HTTPD_RECV_OPT_BLOCKING) < sizeof(length_bytes)) {{\n'
f' ESP_LOGW(TAG, LOG_FMT("Failed to receive {size} bytes length"));',
f'HTTPD_RECV_OPT_BLOCKING) < (int)sizeof(length_bytes)) {{\n'
f' ESP_LOGW(TAG, LOG_FMT("Failed to receive {size} bytes length"));')
for size in (2, 8))),
) )
@@ -656,10 +669,10 @@ def apply_edits(text: str, edits: tuple[Edit, ...]) -> str:
def verify_version(idf: Path) -> Path: def verify_version(idf: Path) -> Path:
version = idf / "components/esp_common/include/esp_idf_version.h" version = idf / "components/esp_common/include/esp_idf_version.h"
text = version.read_text(encoding="utf-8") text = version.read_text(encoding="utf-8")
for part, expected in (("MAJOR", "5"), ("MINOR", "5"), ("PATCH", "0")): for part, expected in (("MAJOR", "5"), ("MINOR", "5"), ("PATCH", "3")):
found = re.findall(r"^#define ESP_IDF_VERSION_" + part + r"\s+(\d+)\s*$", text, re.M) found = re.findall(r"^#define ESP_IDF_VERSION_" + part + r"\s+(\d+)\s*$", text, re.M)
if found != [expected]: if found != [expected]:
raise OverrideError(f"requires ESP-IDF 5.5.0: {version} ({part}={found})") raise OverrideError(f"requires ESP-IDF 5.5.3: {version} ({part}={found})")
return version return version
@@ -683,6 +696,10 @@ def render_entry(entry: Entry, roots: dict[str, Path]) -> tuple[Path, bytes]:
if actual != entry.sha256: if actual != entry.sha256:
raise OverrideError(f"{entry.name}: SHA256 mismatch for {source}: expected {entry.sha256}, got {actual}; reaudit, do not repin blindly") raise OverrideError(f"{entry.name}: SHA256 mismatch for {source}: expected {entry.sha256}, got {actual}; reaudit, do not repin blindly")
notice = MODIFICATION_NOTICE notice = MODIFICATION_NOTICE
if entry.root == "idf":
notice += ("/* Rebased to audited ESP-IDF 5.5.3 originals on 2026-09-16.\n"
" * All local corrections retained; see docs/idf_candidate_integration.md.\n"
" */\n")
if entry.component == "wolfssl__wolfssh": if entry.component == "wolfssl__wolfssh":
notice += ("/* Ordering profile modified 2026-09-16: PR793/819/840/855/921\n" notice += ("/* Ordering profile modified 2026-09-16: PR793/819/840/855/921\n"
" * plus project restricted no-EXT_INFO correction. Provenance and\n" " * plus project restricted no-EXT_INFO correction. Provenance and\n"
+70 -6
View File
@@ -3,6 +3,8 @@
import argparse import argparse
from dataclasses import dataclass from dataclasses import dataclass
import math import math
import hashlib
import json
import os import os
from pathlib import Path from pathlib import Path
import signal import signal
@@ -31,6 +33,10 @@ def positive_seconds(value):
def parser(): def parser():
cli = argparse.ArgumentParser(description=__doc__) cli = argparse.ArgumentParser(description=__doc__)
cli.add_argument('--build-dir', type=Path, default=BUILD, help='existing firmware build directory')
cli.add_argument('--idf-path', type=Path, help='explicit ESP-IDF SDK for SDK-aware checks')
cli.add_argument('--platformio-core-dir', type=Path, help='explicit isolated PlatformIO core directory')
cli.add_argument('--web-performance', action='store_true', help='also validate generated WebSocket performance contracts')
cli.add_argument('--build', action='store_true', help='run pio run before host checks (may fetch dependencies)') cli.add_argument('--build', action='store_true', help='run pio run before host checks (may fetch dependencies)')
cli.add_argument('--interop', action='store_true', help='enable ordering OpenSSH local Unix-socket matrix') cli.add_argument('--interop', action='store_true', help='enable ordering OpenSSH local Unix-socket matrix')
cli.add_argument('--dry-run', action='store_true', help='print plan only; no execution or prerequisite validation') cli.add_argument('--dry-run', action='store_true', help='print plan only; no execution or prerequisite validation')
@@ -50,12 +56,15 @@ def plan(options):
commands.append(Command(label or name, (sys.executable, '-B', str(path), *map(str, args)), commands.append(Command(label or name, (sys.executable, '-B', str(path), *map(str, args)),
options.timeout, (path, *required))) options.timeout, (path, *required)))
header = BUILD / 'config/sdkconfig.h' build = options.build_dir
database = BUILD / 'compile_commands.json' idf_args = ('--idf-path', options.idf_path) if options.idf_path else ()
header = build / 'config/sdkconfig.h'
database = build / 'compile_commands.json'
suite('security_build_policy', '--sdkconfig-header', header, required=(header,)) suite('security_build_policy', '--sdkconfig-header', header, required=(header,))
for name in ('ssh_auth_policy', 'ssh_auth_transport', 'hidden_input', 'ssh_memory'): for name in ('ssh_auth_policy', 'ssh_auth_transport', 'hidden_input'):
suite(name) suite(name)
suite('sdk_security_overrides', '--build-dir', BUILD, required=(database,)) suite('ssh_memory', *idf_args)
suite('sdk_security_overrides', '--build-dir', build, *idf_args, required=(database,))
for name in ('wolfssh_auth_contract', 'ssh_protocol_policy', 'wolf_crypto_policy'): for name in ('wolfssh_auth_contract', 'ssh_protocol_policy', 'wolf_crypto_policy'):
suite(name, '--compile-commands', database, required=(database,)) suite(name, '--compile-commands', database, required=(database,))
suite('wolfssh_parser_contract') suite('wolfssh_parser_contract')
@@ -68,9 +77,47 @@ def plan(options):
for mode in ('base', 'admin', 'accounts', 'ssh', 'lifecycle'): for mode in ('base', 'admin', 'accounts', 'ssh', 'lifecycle'):
suite('web_cookie_auth', *([] if mode == 'base' else ['--' + mode]), suite('web_cookie_auth', *([] if mode == 'base' else ['--' + mode]),
label='web_cookie_auth:' + mode) label='web_cookie_auth:' + mode)
if options.web_performance:
suite('web_serial_performance', '--build-dir', build, *idf_args, required=(database,))
return commands return commands
def verify_snapshot(root, build):
"""Compare source inputs, not path-dependent generated build configuration."""
entries = json.loads((build / 'compile_commands.json').read_text())
mains = [(Path(e['directory']) / e['file']).resolve() for e in entries
if Path(e['file']).parts[-2:] == ('src', 'main.c')]
if len(mains) != 1:
raise RuntimeError('expected exactly one application main.c compilation input')
snapshot = mains[0].parents[1]
scopes = ('src', 'cmake', 'boards', 'managed_components', 'tools/wolfssh_order',
'tools/security_overrides.py', 'CMakeLists.txt', 'extra_script.py',
'sdkconfig.defaults', 'partitions.csv')
digest = hashlib.sha256()
count = 0
for scope in scopes:
def files(base):
path = base / scope
if path.is_file():
return {Path(scope)}
if not path.is_dir():
raise RuntimeError(f'missing snapshot input: {path}')
return {p.relative_to(base) for p in path.rglob('*') if p.is_file()
and not {'.git', '__pycache__'} & set(p.parts)}
paths = files(root)
if paths != files(snapshot):
raise RuntimeError(f'root/build snapshot file set differs: {scope}')
for relative in sorted(paths):
data = (root / relative).read_bytes()
if data != (snapshot / relative).read_bytes():
raise RuntimeError(f'root/build snapshot content differs: {relative}')
digest.update(str(relative).encode() + b'\0' + hashlib.sha256(data).digest())
count += 1
evidence = (snapshot, count, digest.hexdigest())
print(f'SNAPSHOT source equality: {snapshot}; {count} files; SHA256 {evidence[2]}', flush=True)
return evidence
def kill_group(process): def kill_group(process):
try: try:
os.killpg(process.pid, signal.SIGKILL) os.killpg(process.pid, signal.SIGKILL)
@@ -105,12 +152,16 @@ def run_command(command, root, env):
return ('PASS', 'exit 0') if code == 0 else ('FAIL', f'exit {code}') return ('PASS', 'exit 0') if code == 0 else ('FAIL', f'exit {code}')
def execute(commands, root, *, dry_run=False, fail_fast=False): def execute(commands, root, *, dry_run=False, fail_fast=False, idf_path=None, platformio_core_dir=None):
if os.name != 'posix': if os.name != 'posix':
print('PREREQ: POSIX process groups required', flush=True) print('PREREQ: POSIX process groups required', flush=True)
return 1 return 1
env = os.environ.copy() env = os.environ.copy()
env['CCACHE_DISABLE'] = '1' env['CCACHE_DISABLE'] = '1'
if idf_path is not None:
env['IDF_PATH'] = str(idf_path.resolve())
if platformio_core_dir is not None:
env['PLATFORMIO_CORE_DIR'] = str(platformio_core_dir.resolve())
results = [] results = []
stopped = False stopped = False
for command in commands: for command in commands:
@@ -143,7 +194,20 @@ def main(argv=None):
print('SKIP optional build: --build not requested; existing artifacts required', flush=True) print('SKIP optional build: --build not requested; existing artifacts required', flush=True)
if not options.interop: if not options.interop:
print('SKIP optional interop: --interop not requested', flush=True) print('SKIP optional interop: --interop not requested', flush=True)
return execute(plan(options), ROOT, dry_run=options.dry_run, fail_fast=options.fail_fast) if options.build and options.build_dir != BUILD:
parser().error('--build cannot be combined with a non-default --build-dir; build it separately')
try:
evidence = None
if not options.dry_run and options.build_dir != BUILD:
evidence = verify_snapshot(ROOT, options.build_dir.resolve())
result = execute(plan(options), ROOT, dry_run=options.dry_run, fail_fast=options.fail_fast,
idf_path=options.idf_path, platformio_core_dir=options.platformio_core_dir)
if evidence is not None and verify_snapshot(ROOT, options.build_dir.resolve()) != evidence:
raise RuntimeError('snapshot changed during validation')
return result
except (OSError, ValueError, RuntimeError) as error:
print(f'PREREQ snapshot: {error}', flush=True)
return 1
if __name__ == '__main__': if __name__ == '__main__':