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.
This commit is contained in:
@@ -0,0 +1,155 @@
|
||||
# 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 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.
|
||||
Reference in New Issue
Block a user