Harden SSH parsing and add notice tooling
- Enforce exact service and channel names with bounded failure parsing - Add hash-pinned offline notice assembly and regression coverage - Record advisory dispositions, provenance, integration evidence, and remaining gates
This commit is contained in:
@@ -4,6 +4,7 @@ Run from the project root (installed pinned sources and a host C compiler requir
|
||||
|
||||
```sh
|
||||
CCACHE_DISABLE=1 python3 tests/wolfssh_parser_contract/run.py
|
||||
CCACHE_DISABLE=1 python3 tests/wolfssh_parser_contract/review.py --profile
|
||||
CCACHE_DISABLE=1 python3 tests/wolfssh_auth_contract/run.py --host-only
|
||||
CCACHE_DISABLE=1 python3 tests/sdk_security_overrides/run.py
|
||||
```
|
||||
@@ -17,7 +18,47 @@ run with guard pages and UBSan trap instrumentation, both with and without
|
||||
CMake source replacement using fixtures. This is **not** a claim that an existing
|
||||
production generated file or firmware binary contains these edits.
|
||||
|
||||
## Reviewed upstream evidence and exact implementation scope
|
||||
## 2026-09-16 remaining-parser review
|
||||
|
||||
The [finite applicability review](../../docs/ssh_parser_remaining_review.md) closes
|
||||
PR899's current-profile disposition and corrects the earlier description of PR902:
|
||||
902 is **service-name validation**, not channel-callback hardening.
|
||||
|
||||
- PR899 client RSA/ECC key-skip hunks remain unapplied: RSA is disabled, and the
|
||||
sole client host-key call chain is blocked by current server KEX ordering.
|
||||
- The current `DoChannelFailure` now bounds exactly one recipient ID, verifies the
|
||||
local channel, and consumes it before returning the existing fatal
|
||||
`WS_CHANOPEN_FAILED`. No state or channel mutation is introduced.
|
||||
- The server subset of PR902 rejects every name except exact `ssh-userauth` before
|
||||
publishing the index/state transition. The owner closes on the error; unlike
|
||||
upstream, no best-effort disconnect packet is queued. Client service acceptance
|
||||
stays unchanged and unreachable in the current server role.
|
||||
- PR918/919 forwarding fixes are not applied with `WOLFSSH_FWD` disabled.
|
||||
- The follow-up closes `DoChannelRequest` prefix/NUL aliases: all nine recognized
|
||||
names require exact length **before** exact byte comparison. Supported branch
|
||||
bodies and unknown-request success/ACK handling remain unchanged. Production
|
||||
enables TERM but not SHELL/AGENT: PTY/exit branches are present, window-change and
|
||||
agent branches absent. Disabled branches are tested separately without enabling
|
||||
firmware features.
|
||||
|
||||
`pr899.patch`, `pr902.patch`, `pr918.patch`, `pr919.patch` and `provenance.json`
|
||||
archive bytes fetched on 2026-09-16 and verified equal to the corresponding upstream
|
||||
commit patches. Tests pin hashes, commits and URLs independently. `review.py`
|
||||
reverses only the independently specified new notice, two initial handler deltas
|
||||
and nine exact channel-name predicates and
|
||||
requires the **whole prior original+ordering+parser generated-source hash**. This
|
||||
fences unchanged client parsers, packet dispatch, crypto callers and request branch bodies without
|
||||
normalizing away edits. The existing exact original hash and exact-once anchors
|
||||
remain mandatory. No ordering delta or crypto configuration is changed.
|
||||
|
||||
`--profile` reads the unique saved production compile command, checks whether its
|
||||
input is exactly the known prior baseline or fresh current source, replays real
|
||||
Xtensa feature preprocessing, and syntax-checks a temporary fresh source. A prior
|
||||
input is explicitly reported as **not regenerated**, never current-build evidence.
|
||||
Existing strict build-registration suites still reject stale generated bytes.
|
||||
No network or production build-tree writes occur in either test command.
|
||||
|
||||
## Earlier upstream evidence and retained implementation scope
|
||||
|
||||
Official diffs fetched and inspected on 2026-09-15:
|
||||
|
||||
@@ -39,8 +80,8 @@ Covered:
|
||||
validates the full string with `GetSize` before `GetString`, retaining the old
|
||||
strict `< WOLFSSH_MAX_NAMESZ` limit rather than upstream's truncation behavior.
|
||||
Failure leaves the caller index and client state untouched. Successful state
|
||||
transition remains exactly the old one; service-name semantic validation is
|
||||
not added. The original `GetSize` already uses bounded subtraction and needs
|
||||
transition remains exactly the old one for `ssh-userauth`; the 2026-09-16
|
||||
addition above rejects other names. The original `GetSize` already uses bounded subtraction and needs
|
||||
no change. `GetString` now uses it and rejects zero output capacity before
|
||||
subtraction/copy; ordinary bounded truncation semantics remain unchanged.
|
||||
- **881 subset:** `DoChannelWindowAdjust` rejects addition exceeding the 32-bit
|
||||
@@ -77,22 +118,56 @@ both enabled in this server's reviewed profile. Advertisement is not treated as
|
||||
- **892:** client `DoServiceAccept`, agent key preparation, daemon authentication,
|
||||
Windows terminal changes. Password framing/wiping is the existing local
|
||||
correction, intentionally not replaced with upstream's later formulation.
|
||||
- **899:** no hunks applied. `ParseRSAPubKey`/`ParseECCPubKey` skips require separate
|
||||
client/KEX reachability analysis (not the server's `DoUserAuthRequestEcc`).
|
||||
The old `DoChannelFailure` does not parse a channel ID at all; changing only its
|
||||
`len != 0` typo would not establish a bounded channel-ID parser. Its existing
|
||||
behavior is left unchanged rather than claiming the later parser contract.
|
||||
Windows port/terminal hunks are out of scope.
|
||||
- **899:** unused client key skips and Windows port/terminal hunks are deliberately
|
||||
not applied; their current-profile applicability is resolved in the new review,
|
||||
not a claim that the dependency is fixed for clients. CHANNEL_FAILURE is covered
|
||||
by the bounded local adaptation above.
|
||||
- **880:** certificate RSA, agent, daemon, terminal, TPM and SCP changes are not
|
||||
applied. No complete PR-880 closure is claimed.
|
||||
- Message ordering/state machine (including CVE-2025-14942), service semantics,
|
||||
standalone ECC curve-name/key-blob semantic validation, other parsers, client
|
||||
- Message ordering/state machine (including CVE-2025-14942) has its separate
|
||||
restricted-profile suite. Standalone ECC curve-name/key-blob semantic validation,
|
||||
other parsers, client
|
||||
behavior and broader crypto advisories are outside this slice. ECC point/import
|
||||
validation belongs to the separate crypto-policy owner and is not changed here.
|
||||
Existing account/key authorization, numeric r/s validity and Ed25519 raw
|
||||
signature-size/crypto validity checks remain owned by their existing layers.
|
||||
|
||||
## Test boundaries
|
||||
## Exact channel-request and application-gate tests
|
||||
|
||||
`channel_request.py` extracts complete generated `DoChannelRequest`, `GetBoolean`,
|
||||
`GetUint32`, `GetSize`, `GetString`, `GetStringAlloc` and `GetStringRef` functions.
|
||||
It independently hash-pins/extracts the actual application `accept_shell`,
|
||||
`reject_channel_request` and complete `process_handshake`, and checks callback
|
||||
registration and per-slot context wiring. `channel_request.c` runs those real
|
||||
bodies with channel/context layouts, routing, allocation and platform doubles.
|
||||
This is not a task/broker/console integration test.
|
||||
|
||||
Five feature profiles run in both stack modes with guard pages and UBSan traps:
|
||||
production TERM-only, no-terminal, SHELL-only, TERM+SHELL and TERM+SHELL+AGENT.
|
||||
Alternative flags affect host fixtures only. The matrix covers all nine names:
|
||||
valid requests; every proper prefix (including empty); suffixes; embedded NULs
|
||||
and same-length wrong bytes at every position; 31–65-byte names; every header,
|
||||
name, boolean and payload truncation; nonzero offsets; oversized/wrapping declared
|
||||
lengths; reply/no-reply; invalid channel and send failure; resize callback absence
|
||||
and failure. Instrumented comparisons assert the read length equals the initialized
|
||||
name length, catching removed short-name guards even inside the accessible stack
|
||||
buffer. Unknown/disabled names must not parse payload fields or invoke callbacks,
|
||||
but retain existing success/ACK/consumption behavior.
|
||||
|
||||
Real callback/admission cases verify shell-only routing for both roles; null
|
||||
callback context and absent shell callback; authentication/principal/currentness
|
||||
requirements; rejected exec/subsystem after a prior shell, even with no reply;
|
||||
and optional PTY callback behavior. Malformed exec/subsystem payloads still invoke
|
||||
the existing rejecting callbacks and never become shell admission, matching the
|
||||
unchanged handler bodies.
|
||||
|
||||
**2,737 cases per stack mode** for each TERM-containing profile, **2,735** for each
|
||||
of the other two profiles. **18 prefix/length-guard mutations** and **two actual
|
||||
application admission-gate mutations** are rejected, in addition to the existing
|
||||
11 parser mutations. The independent whole-source reversal checks that only the
|
||||
nine predicates change within `DoChannelRequest`, with no branch refactor.
|
||||
|
||||
## Earlier parser test boundaries
|
||||
|
||||
The C matrix exercises zero/truncated/exact/oversized/wrapping lengths, invalid
|
||||
and nonzero offsets, zero-capacity output, copy canaries, window overflow boundary
|
||||
@@ -115,15 +190,21 @@ covers both labels, all truncations, shortened/oversized/wrapping/trailing
|
||||
signature strings, exact raw-signature forwarding and unchanged streamed message
|
||||
bytes. Both paths retain crypto rejection behavior using doubles.
|
||||
|
||||
Validation: **3,124 cases per stack mode** (both pass with UBSan trap mode), plus
|
||||
**six guard-removal mutations rejected**: ECC nested read bound, inner/outer exact
|
||||
consumption, Ed25519 key/signature OR checks, and Ed25519 exact consumption. The
|
||||
Validation: **3,258 cases per stack mode** (both pass with UBSan trap mode), plus
|
||||
**11 guard-removal mutations rejected**: ECC nested read bound, inner/outer exact
|
||||
consumption, Ed25519 key/signature OR checks, Ed25519 exact consumption, service
|
||||
length/byte equality and channel-failure bounded read/exact end/known recipient.
|
||||
`remaining.c` adds 134 counted cases, plus assertions for null arguments and an
|
||||
unknown channel: all service truncations, prefix/suffix/embedded-NUL names,
|
||||
nonzero offsets, channel truncation/trailing bytes, and wrapping indices. The
|
||||
mutation copies exist only in temporary test files; core dumps are disabled for
|
||||
those intentionally failing runs. These are framing-valid fixtures with crypto
|
||||
doubles, not independently verified real signatures.
|
||||
|
||||
The runner also compares complete password, packet dispatch, public-key dispatch and selected deferred
|
||||
functions against the pre-slice generated baseline to fence accidental changes.
|
||||
The runner also compares complete password, packet dispatch, public-key dispatch and unused
|
||||
key parsers against the pre-parser generated baseline to fence accidental changes.
|
||||
The independent whole-source hash contract separately preserves the exact ordering
|
||||
and all prior parser changes while allowing only this review's three corrections.
|
||||
The separate auth suite executes its 135 password/control-flow cases, including
|
||||
payload wipe, callback framing and asynchronous pending retention. No whole-library
|
||||
fuzzing, real SSH exchange, firmware compile, hardware timing or security sign-off
|
||||
|
||||
Reference in New Issue
Block a user