# wolfSSH 1.4.20 restricted ordering correction Project modification: **2026-09-16**. This is a bounded project-profile correction for the CVE-2025-14942 ordering defect, **not a complete wolfSSH 1.4.22/1.5.0 backport or general-purpose upstream-fix claim**. wolfSSH remains pinned to 1.4.20; wolfSSL and all existing password/parser/crypto protections remain pinned and intact. No managed source is edited. ## Inputs and provenance `793.patch`, `819.patch`, `840.patch`, `855.patch`, and `921.patch` are the exact upstream mail-patch responses fetched on 2026-09-16. `provenance.json` records URLs, byte SHA-256 hashes, and every embedded full commit ID. PR endpoints can change: the archived bytes and commit identities, not a future PR response, identify this review. Commit pages are recoverable as `https://github.com/wolfSSL/wolfssh/commit/`. `delta.json` is the **authoritative executable consolidated delta**, not a claim that all archived hunks apply. Its exact old/new edits are applied before the existing parser/password edits by `tools/security_overrides.py`; every anchor must occur exactly once. It pins: | Input | Original SHA-256 | | --- | --- | | `src/internal.c` | `81ff1f9166708abd5c2911e9fe57c0aee01c88b5d3f68c909ee8a856d37f36a9` | | `src/ssh.c` | `a4f479ff87eea0980ec1ebdf2c7dd090da473780181b695a56799cb9611f4366` | | `wolfssh/internal.h` | `8e417149a68f8a6c0506957adf014b3e6c1727a723536826ce5fb0c9e1f1aba3` | Original copyright/license notices remain in each generated file, preceded by project modification/provenance notices. Configuration needs no network, `patch(1)`, Git, or fuzzy matching. All inputs validate before outputs are written. ### Prerequisite disposition - **PR793:** retain stream/channel read rekey fencing, stream-send fatal/error convention, EOF/window-adjust send checks and send/receive direction constants. Exclude example, SFTP, workflow and upstream-test changes. The intermediate `IsMessageAllowedKeying` implementation was removed by PR855 itself; it is not resurrected. Local send checks are distinct from receive checks. The follow-up corrects a misapplied EOF hunk: `SendChannelEof` now checks before lookup, serialization or `eofTxd` mutation. The extra `SendChannelExit` guard is retained intentionally as local hardening, using `MSGID_CHANNEL_REQUEST` (exit-status), not falsely attributed to PR793's EOF hunk. Real shutdown/rekey tests cover both. - **PR819:** retain `extInfoSent` ABI field, but supersede its negotiation/send logic by disabling EXT_INFO uniformly. The field stays zero; no reset-on-rekey or exactly-once extension continuation is needed. - **PR840:** initialize keying to zero; maintain independent SELF/PEER bits; reject duplicate peer KEXINIT; respond only if SELF was not already keying; reject peer NEWKEYS while SELF still owes NEWKEYS; clear PEER and release the handshake only after successful incoming key installation. - **PR855:** retain `expectMsgId`, message IDs/ranges, and expected-reply writes **before** nonblocking sends. Adapt the client `DoKexDhReply` hunk to 1.4.20's `useEccKyber` spelling; no PQ algorithm is enabled. Replace permissive range fallthrough with the explicit restricted gates described below. Upstream test-only entry points/build rules are not imported. Its new log macro is local to generated `internal.c`: the pinned `log.h` has a different WLOG implementation, so neither `log.h` nor `log.c` needs an ABI/source change. - **PR921:** set server `expectMsgId = MSGID_NEWKEYS` before `SendNewKeys`, including WANT_WRITE. The former post-NEWKEYS EXT_INFO call is deliberately removed. Local additions also set the server's expected INIT after valid KEXINIT, retain that expectation after skipping a wrong optimistic INIT guess, reject absent expectations instead of accepting arbitrary KEX packets, and only allow client rekey dispatch after successful/queued KEXINIT with a live handshake. Client rekey dispatch includes the final `CONNECT_DONE` state. ## Restricted protocol contract - Negotiate only the existing project KEX profile: **Curve25519-SHA256 and ECDH-P256**. A caller trying to widen the algorithm list to DH/GEX/PQ gets a negotiation error. Those continuation paths are not represented as supported. - Receive transport notifications 1–4 without consuming an expected KEX reply. KEXINIT is legal only before the peer has begun this exchange; other KEX messages require a live handshake and an exact nonzero expectation. Acceptance consumes that expectation once; the handler sets the next expectation. - Reject authentication/service/connection traffic after **peer** KEXINIT until peer NEWKEYS. When only SELF has initiated rekey, pre-peer-KEXINIT in-flight traffic remains legal according to the authentication phase (RFC4253). - Server requires keyed service request, then service acceptance before userauth requests, then completed authentication before connection messages. Reject wrong-direction and repeated authentication/service messages. Client rejects premature auth results/channel messages and accepts auth responses only in its request phase. Keyboard-interactive is outside the project profile and incoming INFO_RESPONSE is rejected before dispatch. - Send-side EOF/window-adjust checks never mutate receive expectations. Existing channel-data rekey fencing is preserved. ### NEWKEYS/backpressure and the EXT_INFO choice `SendNewKeys` bundles NEWKEYS with the old sending keys, installs the new sending keys, then calls `wolfSSH_SendPacket`. `WS_WANT_WRITE` means that **same bundled packet** remains in the bounded output buffer. Clear SELF on SUCCESS or WANT_WRITE, retain PEER and the expected peer NEWKEYS. The existing accept/connect/worker flush paths send the remaining bytes; they must not call `SendNewKeys` again. A fatal send does not clear SELF. Peer NEWKEYS installs the receive keys and releases the handshake once; premature/duplicate NEWKEYS fails. Previously `SendKexDhReply` called `SendExtInfo` only on SUCCESS from NEWKEYS. A partial send skipped it with no continuation. Rather than invent another pending send state and its handshake-lifetime rules, this profile: 1. Does not append `ext-info-c` to client KEXINIT. 2. Ignores peers' extension willingness; sets `sendExtInfo` to zero on KEXINIT. 3. Removes the post-NEWKEYS extension send and makes `SendExtInfo` return `WS_NOT_COMPILED` even if called directly. 4. Rejects incoming EXT_INFO in every phase, since it was never negotiated. This follows RFC8308's optional-negotiation model. **There is no `server-sig-algs` advertisement.** The existing KeyAccepted setter remains valid but no longer produces that extension on the wire. This matters particularly to RSA-SHA2 discovery: RSA user keys are outside the project's enrolled/advertised profile, and RSA interoperability is not claimed. Host tests establish that OpenSSH 10.2p1 still authenticates using Ed25519, P-256 and passwords with both project KEX algorithms, initial KEX and rekeys, without receiving EXT_INFO. Other clients and hardware remain validation work. ## Header overlay and build ownership The ABI changes require **all** consumers to use the generated header, not only the relocated C file. `cmake/security_overrides.cmake` installs `security_overrides/wolfssh_include/wolfssh/internal.h` with BEFORE PUBLIC include propagation and a PUBLIC forced include. The original header's include guard prevents a later vendor-first include path from defining a stale layout. A dated ABI marker rejects an original header forcibly included *before* the overlay, rather than silently skipping the corrected layout. Existing unmodified wolfSSH files and transitive application consumers therefore use the same layout. Source properties are retained; header originals, generated header, and `delta.json` are configure dependencies. Compiler dependencies track the forced header too. There are now **eight source overrides plus one header**. Tests intentionally put the original include root before the overlay when compiling full host translation units. The SDK CMake fixture checks PUBLIC propagation to library, direct and transitive consumers. Forced headers use joined `-include/path` arguments: PlatformIO sorts app flags and deduplicates component flags, which breaks split option/operand pairs. The installed adapter/SCons test compiles a real Xtensa consumer and reproduces the failure with a split-option mutation. The crypto guard remains enabled with its existing joined argument. The obsolete unused EXT_INFO name constant is removed, without relaxing warnings. ## Validation and boundaries Run `python3 tests/wolfssh_order_contract/run.py --interop` and the related commands listed in that test's README. Tests execute the actual generated full `internal.c` and `ssh.c` with real wolfCrypt, bounded host IO, hostile message-ID matrices, partial writes, both roles and all three rekey initiation directions. Guard-removal mutations must fail. The parser/password suites separately preserve prior protections; SDK tests exercise real override/overlay CMake wiring. Follow-up validation on 2026-09-16: authorized `pio run` PASS in 21.31 seconds, **94,340 B linked RAM / 1,768,701 B flash**. Strict auth, protocol, crypto and SDK override tests against the rebuilt production artifacts PASS. Ordering tests PASS 8,028 checks and seven rejected mutations; parser tests PASS 3,124 cases in each stack mode. No upload, erase, dependency upgrade or device operation. This does not establish stack/heap reserve, timing, arbitrary-algorithm, strict-KEX-extension, full-client compatibility or phase-wide security sign-off.