Skip to content

Bound LMS, XMSS and ML-KEM DMA response frames in the crypto client#476

Open
yosuke-wolfssl wants to merge 1 commit into
wolfSSL:mainfrom
yosuke-wolfssl:fix/lms_xmss
Open

Bound LMS, XMSS and ML-KEM DMA response frames in the crypto client#476
yosuke-wolfssl wants to merge 1 commit into
wolfSSL:mainfrom
yosuke-wolfssl:fix/lms_xmss

Conversation

@yosuke-wolfssl

Copy link
Copy Markdown

Problem

wh_Client_RecvResponse reports the received payload length but does not validate it against the message being parsed. The comm client reuses one buffer for request and response, so a reply shorter than the response struct leaves the tail holding the client's own outbound request bytes. An unchecked overlay read then returns stale data instead of failing.

Nine DMA response handlers in wh_client_crypto.c overlaid their response struct with no frame bound. What a short frame could fabricate:

Handler Unbounded read
wh_Client_{Lms,Xmss}MakeKeyDma res->keyId, stored into the caller's out-param and the key
wh_Client_{Lms,Xmss}SignDma res->sigLen, bounded against the caller's buffer but not the frame
wh_Client_{Lms,Xmss}VerifyDma resp->res, the verify verdict itself
wh_Client_{Lms,Xmss}SigsLeftDma res->sigsLeft, which gates whether a one-time key is reused
_MlKemMakeKeyDma res->keyId, and res->keySize bounded only by the staging buffer

Follow-up to #457, continuing the family split started in #474 (CMAC) and #475 (AES DMA).

Fix (src/wh_client_crypto.c)

Every one of these responses is fixed size with no trailing payload, so a single bound covers each site:

const uint32_t hdr_sz =
    sizeof(whMessageCrypto_GenericResponseHeader) + sizeof(*res);
if (res_len < hdr_sz) {
    ret = WH_ERROR_ABORTED;
}

These nine functions previously reused req_len as the out-param of wh_Client_RecvResponse. Each now receives into its own res_len, matching every other handler in the file and keeping a response-size comparison off a variable named for the request.

  • Inside the success branch. The DMA dispatcher sends only the generic header when a handler fails (wh_server_crypto.c:8837), so an unconditional bound would convert every legitimate error reply into WH_ERROR_ABORTED and hide the server's rc. The bound sits inside the existing if (ret >= 0) block; these server handlers return 0 or a negative error, never a positive value.
  • Assigns ret, never returns early. MakeKeyDma and SignDma reconcile postRet after the parse block, MakeKey/Sign/Verify unmap their DMA buffers on the way out, and _MlKemMakeKeyDma zeroizes its staging buffer at the bottom.
  • The pre-existing caller-capacity branches stay: res->sigLen > sigCap and res->keySize > buffer_len bound the caller's buffer, a different bound from the frame, and still return WH_ERROR_BADARGS.

Test harness (test-refactor/misc/wh_test_client_malformed_resp.c)

New sub-tests driven by a scripted transport that echoes the request comm header, always writes the response body, and reports whatever frame length the sub-test asks for. Each handler gets:

Case Expected
Frame exactly hdr + sizeof(resp) accepted, claimed value reaches the caller
Frame one byte short WH_ERROR_ABORTED, out-param untouched
Header-only reply with rc = WH_ERROR_NOTFOUND WH_ERROR_NOTFOUND, not a rejection
Claim the frame carries but the caller's buffer cannot hold WH_ERROR_BADARGS

The ML-KEM sub-test always claims an oversized key length, because a staging buffer of zeros cannot deserialize into a real key. An accepted frame is therefore WH_ERROR_BADARGS from the capacity check, and only a frame rejection is WH_ERROR_ABORTED.

The transport is family-agnostic: a sub-test supplies structSz, claimOff and claimVal, so stamping a field into the response struct needs no per-algorithm branch.

This file also exists on #464, #474 and #475 with different contents. Whichever lands last resolves the add/add conflict by concatenating sub-tests and keeping the single whTest_ClientMalformedResp registry entry.

Verification

  • make check clean on every matrix: default, SHE=1 DMA=1 (48 passed, 15 skipped, 0 failed of 63), DMA=1 with LMS_VERIFY_ONLY=1, XMSS_VERIFY_ONLY=1, and both together. Legacy test/ suite passes with SHE=1.
  • Mutation: 27 mutants across the nine guards (halve hdr_sz, flip < to >, hdr_sz - 1), all caught. Two hoist controls that move a bound out of the success branch also fail the suite, so the header-only error reply is genuinely covered.

@yosuke-wolfssl yosuke-wolfssl self-assigned this Jul 22, 2026
Copilot AI review requested due to automatic review settings July 22, 2026 05:23

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR hardens the crypto client’s DMA response parsing for LMS, XMSS, and ML-KEM by validating that the received response frame is large enough to safely overlay and read fixed-size response structs, preventing stale request bytes (from the reused comm buffer) from being interpreted as response fields. It also adds a malformed-response test harness that simulates truncated frames and header-only error replies.

Changes:

  • Add per-handler res_len receive lengths and fixed-size frame bounds before reading response struct fields in src/wh_client_crypto.c.
  • Add a scripted-transport misc test (whTest_ClientMalformedResp) that exercises exact-fit, truncation, server-error header-only replies, and caller-capacity rejection cases.
  • Register the new misc test in test-refactor/wh_test_list.c.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
src/wh_client_crypto.c Adds response-length bounds (via res_len) for LMS/XMSS/ML-KEM DMA response parsing to prevent stale-buffer overlay reads.
test-refactor/misc/wh_test_client_malformed_resp.c Adds a scripted transport + subtests to validate client behavior on truncated/invalid response frames.
test-refactor/wh_test_list.c Registers the new malformed-response misc test entry point.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/wh_client_crypto.c Outdated

@wolfSSL-Fenrir-bot wolfSSL-Fenrir-bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fenrir Automated Review — PR #476

Scan targets checked: wolfhsm-crypto-bugs, wolfhsm-src

No new issues found in the changed files. ✅

@Frauschi Frauschi self-assigned this Jul 23, 2026

@Frauschi Frauschi left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, but has conflicts to main that needs fixing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants