# Pinned SDK security overrides The root `CMakeLists.txt` includes `cmake/security_overrides.cmake` **after** `project()`. No component/vendor file is edited, and no global crypto feature or client ciphersuite setting is changed. ## Build contract `tools/security_overrides.py` requires the installed ESP-IDF version header to identify **5.5.3**, and checks each complete original source against its reviewed SHA256. Every text substitution must match **exactly once**. The entire input plan is validated before any output is written. A changed SDK, missing source, ambiguous edit, duplicate source, or missing/ambiguous component target fails configuration; there is no unpatched fallback or automatic hash repinning. Derived **full files**, retaining the original copyright/license notices, live only at `/security_overrides//`. Every generated source is centrally prefixed with an explicit `Modified by the ESP32_serial_swiss_army_knife project on 2026-09-15` notice. This is separate from, and does not rewrite or replace, upstream notices. The manifest and derived files are atomically replaced only when their bytes change. Output paths cannot escape the binary tree or alias SDK/source files. CMake tracks the generator, version header, originals, and derived sources for reconfiguration. The included CMake file is itself an ordinary CMake input. Requirements: Python 3.9+ and CMake 3.18+ (directory-scoped source properties). CMake replaces the exact original entry in the existing component's `SOURCES`; it does not add a second definition or replace the component target. Target compile settings remain intact. Source compile flags/options/definitions, per-configuration definitions, source includes and object dependencies are copied in the target's owning directory. The original C file's directory is prepended to that source's include search path, preserving quoted local headers. Source generator expressions are rejected rather than guessed through. ### Current corrections - `esp_https_server:src/https_server.c`: delete TLS if post-handshake transport allocation fails; destroy the complete secure context if HTTPD start fails; wipe exactly `serverkey_bytes` before releasing the raw private-key copy. Failed start restores the original open callback and clears stale transport context/destructor pointers. Failed stop retains live ownership. - `esp_http_server:src/httpd_parse.c`: allocate/copy/wipe/free scratch resize, retaining old storage on failure; wipe current scratch on final cleanup. Initial reads avoid null-pointer subtraction and preserve a null parser position until a callback sets it; existing positions relocate with scratch. Existing shrink/grow behavior and bounds remain. Resizing briefly owns old plus new allocations; no persistent maximum-size buffer, socket, task or limit increase. Pending/unread bytes are not erased or drained by the patch. - `esp-tls:esp_tls_mbedtls.c`: **server-local** static-lifetime allowlist of `TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256` and `TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384`, terminated by zero, configured after server defaults and before setup. Both version limits are TLS 1.2; renegotiation is disabled (or absent at compile time). Required TLS/ECDHE/ ECDSA/AES/GCM/SHA features are compile-guarded. IDF dynamic buffers are rejected because their destructor bypasses the audited upstream record-buffer wipe. `set_client_config()` and the shared handle/setup path are unchanged. - `wolfssl__wolfssh:src/internal.c`: pins wolfSSH 1.4.20's original source SHA256 to `81ff1f9166708abd5c2911e9fe57c0aee01c88b5d3f68c909ee8a856d37f36a9`. `GetSize()` bounds password and new-password fields before authentication; malformed parsing cannot reach the auth callback. The checked packet suffix is wiped before failure responses, preserving the caller's prefix. Pending asynchronous authentication retains the payload for retry; this is **not** an async secret-lifetime/wipe guarantee. Generated parser/control-flow tests live in `tests/wolfssh_auth_contract/`. - `lwip:apps/dhcpserver/dhcpserver.c`: PAD/END handling, length-byte and declared-payload bounds, minimum message-type/requested-IP lengths, validated advancement. Backport of official [d51b1076092487e533eadf8b48c9c8579d3a6712](https://github.com/espressif/esp-idf/commit/d51b1076092487e533eadf8b48c9c8579d3a6712.patch). The only behavioral-code deviation is using equivalent remaining-length comparisons rather than constructing pointers beyond the input object. Original copyright years stay unchanged under the separate modification notice. - Nested `mbedtls:ssl_tls.c`: propagate `calc_verify()` failure immediately, before PRF/master-secret derivation. Exact TLS 1.2 hunk of [f595df4569c1a1650ad9d077e2f2e819e9f1dddb](https://github.com/Mbed-TLS/mbedtls/commit/f595df4569c1a1650ad9d077e2f2e819e9f1dddb.patch). EMS remains enabled. This does **not** backport that commit's TLS 1.3 hunk. - Nested `mbedx509:x509_create.c`: reject known-attribute OID allocation failure before `memcpy()`, returning `MBEDTLS_ERR_X509_ALLOC_FAILED`. Exact guard from [bfaf4a47fd33da860796feaba6235847acb71127](https://github.com/Mbed-TLS/mbedtls/commit/bfaf4a47fd33da860796feaba6235847acb71127.patch). These three patches were fetched from the official repositories and compared with the installed pinned sources on 2026-09-15. That original implementation did not change dependency versions or source hashes; 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. Clients that cannot negotiate this server profile will no longer connect. Live interoperability and resource/latency testing remain hardware gates. These corrections do not claim comprehensive zeroization of every TLS/library copy, compiler spill, accelerator register, browser buffer or allocator region. ## Parent extension point Add an `Entry` to `tools/security_overrides.py:ENTRIES` with: - unique `name`; - exact IDF `component` name (used by `idf_component_get_property`); - `root="idf"` for installed IDF sources, or `root="project"` for project/vendor sources; - exact relative `source`, full reviewed `sha256`, and a tuple of `Edit(old,new)` exact-once substitutions; - optional `target` for the explicitly supported `mbedtls`, `mbedx509` or `mbedcrypto` nested library under component `mbedtls`. Empty selects the component's `COMPONENT_LIB` as before. CMake validates the nested target exists, is not imported/an alias, and belongs to the installed mbedTLS library directory. `render_entry()` validates/patches an entry; `generate()` accepts an explicit 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 `sak_security_replace_source(component original generated nested_target)` handles replacement on the actual source owner, not the IDF mbedTLS wrapper. The current 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 sources in the same real component are supported by the replacement function. Do not change a source hash merely to make a new SDK configure. Re-audit ownership, cleanup, feature resolution and patches against that source revision first. ## Validation ```sh python3 tests/sdk_security_overrides/run.py python3 tests/sdk_security_overrides/run.py --build-dir .pio/build/esp32-s3-devkitc-1-n16r8 ``` Optional `--idf-path` selects an existing installed SDK. Tests need host `cc`, CMake and Ninja; they install nothing, use temporary directories under `.pio/`, and never edit the selected SDK. Run the second command **after the parent runs `pio run`**; old generated copies lack the new entries/notice and must not pass. It checks real firmware Ninja registration: exactly one compilation of each derived source, no original 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: - Generator full-source hashes, version, missing/duplicate/ambiguous inputs, exact edit counts, validation-before-output, unchanged-byte/mtime idempotence, unsafe output rejection, centrally applied dated modification notice exactly once on every source, and preserved upstream opening notices. - Extracted **patched actual functions**, not reimplemented cleanup logic: HTTPS allocation failure matrix; handshake failure; post-handshake allocation failure; HTTPD start failure; normal close/stop; failed stop preserving ownership. The unmodified installed `httpd_stop()` is extracted and separately pinned. Allocator doubles assert key bytes are zero **before** free, with trailing canaries to reject over-wiping, and detect leaks/double frees. - Actual patched scratch helper/read/cleanup functions: grow, shrink, no-change, null-initial first-read success/failure, nullable parser-position preservation, resize failure preserving the old pointer, receive errors/timeouts, size bounds, final wipe and original right-aligned pending-byte behavior. Installed pending/unrecv functions are separately pinned and executed. This is not a full HTTP parser fuzz test. - Actual server/client configuration functions with crypto/config doubles: allowlist order/terminator/static lifetime, defaults failure, PKI failure, version limits, renegotiation enabled/compiled-out variants, untouched default and caller-provided client suites. Every required feature is individually removed in compile-failure tests; dynamic-buffer enablement also fails. - Extracted DHCP parser: 196,623 guard-page cases covering empty/lone-code, every option code/length with truncated and complete payloads, PAD, END, short type/IP, DISCOVER/REQUEST/DECLINE/RELEASE, renew, matching/mismatching IP. Compare the remaining-length variant with upstream pointer-form checks on safely padded backing storage; post-loop state behavior is retained. - Extracted TLS master calculation: SHA-256/SHA-384-sized transcript doubles, untouched output and no PRF on hash failure (including unchanged length 64), successful EMS, PRF failure, non-EMS and resumed-session behavior. This does not execute actual hashes, the full handshake driver, or its failure destructor. - Extracted X.509 parser and its actual attribute table/helpers, compiled against installed mbedTLS headers: fault each allocation across separate subject/issuer lists, partial-list cleanup, second-attribute OID failure, and successful retry. Named-data storage/free are allocation-counted doubles; certificate signing, writer destruction, persistence/publication and old-identity retention are not exercised here. The three new C harnesses use UBSan trap instrumentation; this does not require the host UBSan runtime. - The actual CMake include under fake IDF target discovery, including missing and duplicate sources/targets, nested target owner validation, and exact Ninja compilation registration on both `mbedtls` and `mbedx509` (not their wrapper). A separate real host compile tests the project-root extension, child-directory relative `SOURCES`, quoted and source-specific includes, source/target flags and per-config source definitions. Changing that fixture's original file makes the next ordinary build reconfigure and reject its hash instead of compiling stale derived code. No tests here perform real TLS handshakes, network/device operations, allocation failure on the target, or whole-Phase-9 hardware acceptance.