Apply hash-pinned generated edits for CVE-2025-14942 while keeping wolfSSH 1.4.20 managed sources unchanged. Add the ABI header overlay, provenance records, and real state-machine interoperability contracts.
Generated wolfSSH ordering contract
Implementation/provenance and restricted-profile rationale:
tools/wolfssh_order/README.md.
python3 tests/wolfssh_order_contract/run.py
python3 tests/wolfssh_order_contract/run.py --interop --target-contracts
python3 tests/wolfssh_order_contract/run.py --interop --interop-repeat 3
python3 tests/wolfssh_parser_contract/run.py
python3 tests/wolfssh_auth_contract/run.py --host-only
python3 tests/sdk_security_overrides/run.py
python3 tests/wolf_crypto_policy/run.py --host-only
Requires the already-installed pinned managed components, a host C compiler,
and (for --interop) OpenSSH ssh/ssh-keygen with ProxyUseFdpass, OpenSSL,
and POSIX Unix sockets with SCM_RIGHTS descriptor passing. The
optional --target-contracts also requires the existing ESP-IDF compiler and
.pio/build/esp32-s3-devkitc-1-n16r8/compile_commands.json. It does not run
PlatformIO. Everything generated by this suite is in a temporary directory;
it never writes managed sources, persistent keys or the production build tree.
No downloads or IP network sockets: a short-lived ProxyCommand passes a temporary
local Unix socket to OpenSSH. Python independently owns/reaps the server, whose
stdio uses the accepted socket. User/global SSH configuration and agents are disabled.
Sandboxes that prohibit even AF_UNIX sockets require permission for --interop.
All keys
and the fixed test password are disposable fixtures, not production credentials.
Follow-up fixes and validation — 2026-09-16
python3 tests/wolfssh_order_contract/pio_adapter.py executes the installed
PlatformIO get_app_flags function and SCons ParseFlags/AppendUnique on the
configured project flags. Both forced headers must remain joined -include/path
arguments. A real Xtensa -c/-o consumer compiles; a split-option mutation must
reproduce the multiple-input failure. Also available as run.py --pio-adapter.
Real EOF, shutdown and exit-status tests now cover SELF, PEER and SELF|PEER,
empty/pending output, stale WANT_WRITE and repeated calls: no bytes, callbacks,
sequence changes, expectation consumption, eofTxd or closeTxd mutation.
EOF succeeds exactly once after keying clears. The misplaced PR793 EOF guard is
corrected; the additional exit-status guard is intentionally retained. Removing
the EOF guard is the seventh required failing mutation. Latest run: 8,028 checks.
Authorized firmware build PASS: pio run, 21.31 s, 94,340 B RAM / 1,768,701 B
flash. Strict SDK registration, auth, protocol and crypto suites PASS against
that build. This supersedes the initial no-build/stale-artifact status below;
no device validation occurred. The 12-session OpenSSH matrix was not rerun in
this narrowly scoped follow-up; its previous evidence remains historical.
What executes
The generator verifies original source hashes and applies the real checked-in
edits. The test includes the entire generated internal.c, links generated
ssh.c, unmodified pinned IO/log/port sources, and real wolfCrypt code. Static
functions are directly visible to the test; they are not rewritten copies or
models. Every test translation unit uses the generated ABI header. The original
include directory is deliberately searched first to exercise the forced overlay.
An original-header-before-overlay compile must fail explicitly.
The host crypto settings enable portable small X25519/Ed25519, TFM P-256,
import/shared-secret validation and AES-GCM. They are host settings, not an
assertion that every ESP compile option is identical. UBSan trap instrumentation
is enabled; the host's standalone UBSan runtime is unavailable. WOLFSSL_USE_ALIGN
selects bytewise encoding so x86 unaligned fast-path stores do not mask protocol
tests with alignment traps. Production crypto policy is not changed.
Coverage:
- Every byte-sized message ID at initial state for both roles; every ID during all combinations of self/peer keying; explicit service/auth phase boundaries.
- Actual
DoPacketrejects malformed pre-auth auth/channel/extension payloads before dispatch, for both roles, without consuming the input. - Missing, wrong, duplicate and prematurely received KEX/NEWKEYS messages; benign transport notifications do not consume an expectation.
- Wrong optimistic INIT guess preserves the real INIT expectation; duplicate
DoKexInitfails before parsing another exchange. - Actual NEWKEYS framing, queued bytes, sequence counter and key installation: zero-through-complete one-byte write quotas, repeated output flush, no duplicate NEWKEYS, independent self/peer bits and exactly-once handshake disposal. Fatal IO and real invalid-AES-key errors retain the required keying state.
- Generated client/server complete password-authenticated handshakes with both KEX algorithms and AES256-GCM, followed by server-, client- and simultaneously initiated rekeys. IO fragments to 11/13 bytes and injects WANT_WRITE regularly.
- Six deliberately bad generated-source mutations must fail: pre-auth injection, wrong expected KEX, unnegotiated EXT_INFO, clearing both keying bits, accepting peer NEWKEYS before local NEWKEYS, and removing PR921's server expectation.
- Archived upstream patch hashes/commit IDs, source/header pins and unchanged logging ABI are independently checked.
OpenSSH interoperability (--interop)
Twelve real sessions cover:
- X25519 and P-256 KEX, P-256 host identity and AES128-GCM.
- Ed25519 public-key, P-256 public-key and password authentication.
- Client rekey every 32 KiB, or a server-initiated rekey with 31/37-byte fragmented IO and forced WANT_WRITE every third send callback.
- Exact 256 KiB binary echo per session, successful channel close/status, at least two completed exchanges and no EXT_INFO/server-sig-algs received.
Verified with OpenSSH 10.2p1 / OpenSSL 3.5.8 on 2026-09-16. The unfragmented client-rekey fixtures completed ten exchanges each; fragmented server-rekey fixtures completed two and forced roughly 3,600 write stalls each. These are host interoperability results, not device measurements or universal-client claims. Test-only authentication authorizes a generated key blob or a synthetic password; public-key signature verification is still performed by real wolfSSH.
Closure-race diagnosis and regression — 2026-09-16
The original harness failure was reproduced: exact echo and INTEROP PASS were
followed by OpenSSH Broken pipe while sending channel close. The harness called
wolfSSH_shutdown() once, accepted WANT_READ as retryable, drained only output,
and exited without receiving the peer's close. Flushing is not shutdown completion.
A second lifecycle issue is that OpenSSH terminates its ProxyCommand on exit;
keeping the server itself as that proxy cannot reliably prove server completion.
The harness now queues shutdown once, flushes output, pumps the real worker until
WS_CHANNEL_CLOSED and channel removal, then keeps receiving through transport
EOF. It accepts the expected socket-close error only with an actual zero-length
transport read, the library closed flag, and no reset. Python owns the server
independently via a local fd-passing proxy; no sleep-based grace period or return
code waiver is used. The existing 100-us polling backoff is not a close deadline.
Shutdown remains unfragmented, as before; this does not expand nonblocking-shutdown
coverage beyond the separate contract tests.
Each session requires both process exit codes zero, byte-for-byte 262,144-byte
echo, a single complete server evidence record, at least two completed exchanges,
the expected signed/password authentication counts, stalls in fragmented cases,
peer channel-close completion and transport EOF. OpenSSH must independently report
receipt of exit-status and channel close and Exit status 0; EXT_INFO remains
forbidden. Early server exit, missing evidence, nonzero client/server status,
truncated/corrupted echo, and missing rekey evidence all fail.
--interop-repeat N repeats the full 12-session matrix (1–20, default 1), compiling
the contracts once and generating new disposable keys per matrix. Bounds: 10 s
proxy connection, 30 s server alarm, 45 s client communication, 5 s server reap;
failed/timed-out processes are killed and reaped. No production code changes are
needed for the reproduced harness race. Final validation on OpenSSH 10.2p1:
python3 tests/wolfssh_order_contract/run.py --interop --interop-repeat 3 passed
36/36 sessions, plus 8,028 contract checks and seven rejected mutations.
Client-rekey cases completed ten exchanges and server-rekey cases two; each
session had exact echo and both clean process exits. An earlier revision of the
fix also passed 36 sessions before the final close/EOF-error assertions were added.
These results do not establish general library shutdown correctness or hardware
behavior.
Candidate target contracts (--target-contracts)
Reads, but does not modify, existing compiler commands. Preserves their flags and crypto definitions while substituting freshly generated SSH sources and the PUBLIC-equivalent header overlay in a temporary candidate database. Runs:
- Xtensa syntax checks for generated
internal.c/ssh.cand SSH application consumers. ssh_protocol_policy, including independently specified exact changes to KEXINIT and disabled EXT_INFO serialization (not an arbitrary delta allowlist).wolf_crypto_policy, including real vendor vectors, independently checked protected crypto/parser function bodies and resolved compiler flags.wolfssh_auth_contract, including password bounds/wiping/async contracts.
Those existing suites label some checks “production compiler/profile”; in this mode they use production-derived flags with candidate source/header paths. The wrapper explicitly labels the run candidate-only. This does not establish actual firmware build registration, linking, flashing, resource reserve or hardware behavior. Their ordinary strict commands correctly reject the old production generated source until a firmware reconfigure is performed.
The SDK override suite separately executes the actual CMake module against a mock IDF target graph: source ownership/properties, header hash-drift atomic failure, PUBLIC overlay/forced include propagation to direct and transitive consumers, configure dependencies and rejection cases. This is real CMake wiring evidence, not a full ESP-IDF firmware build.
Limits
No whole-upstream-fix claim. EXT_INFO and keyboard-interactive are excluded; KEX is intentionally limited to the project algorithms. No RSA-SHA2 discovery, DH/GEX/PQ, arbitrary-client, hardware, timing, memory-headroom, or complete SSH parser audit claim. Existing parser/password protections remain covered by their own suites. Firmware reconfigure/build and whole-phase device validation remain follow-up work, deliberately not executed in this task.