Files
ESP32_Serial_Swiss_Army_Knife/tools/wolfssh_order/README.md
T
Commander1024 bea33e1c95 Add restricted wolfSSH ordering fix
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.
2026-09-16 14:04:34 +02:00

9.5 KiB
Raw Blame History

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/<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 14 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.