Skip to content

Bound AES DMA response frames in the crypto client#475

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

Bound AES DMA response frames in the crypto client#475
yosuke-wolfssl wants to merge 1 commit into
wolfSSL:mainfrom
yosuke-wolfssl:fix/aes

Conversation

@yosuke-wolfssl

Copy link
Copy Markdown

Problem

The four AES DMA response handlers in wh_client_crypto.c overlaid their
response struct on the comm buffer without validating the length
wh_Client_RecvResponse reported. The comm client reuses one buffer for
request and response, so a short reply leaves the tail holding the client's
own outbound request bytes:

  • AesCtrDmaResponse restores aes->reg and aes->tmp from
    AES_IV_SIZE + AES_BLOCK_SIZE bytes read past the struct.
  • AesCbcDmaResponse restores the chaining IV the same way.
  • AesGcmDmaResponse copies res->authTagSz tag bytes from past the
    struct into the caller's buffer, bounded only by the caller's tag_len.
  • AesEcbDmaResponse parses the header only, but overlays unchecked.

A malicious or malfunctioning server returns stale data instead of an error;
for CTR/CBC that silently corrupts cipher state, for GCM it hands the caller
a tag built from its own request bytes.

Follow-up to #457, continuing the wh_Client_RecvResponse audit. This is the
AES DMA family; CMAC, LMS/XMSS DMA, ML-KEM DMA and EccCheckPubKey follow in
separate PRs.

Fix (src/wh_client_crypto.c)

Each handler now bounds res_len before parsing:

Handler Required size beyond the generic response header
AesCtrDmaResponse sizeof(*res) + AES_IV_SIZE + AES_BLOCK_SIZE
AesEcbDmaResponse sizeof(*res)
AesCbcDmaResponse sizeof(*res) + AES_IV_SIZE
AesGcmDmaResponse sizeof(*res), plus res->authTagSz against the remainder

Each size matches exactly what the server emits (*outSize in
_HandleAes*Dma). Two placement rules the DMA paths impose:

  • The check sits inside the rc == WH_ERROR_OK branch. The DMA
    dispatcher sends a header-only frame when a handler fails, so an
    unconditional bound would convert every real server error into
    WH_ERROR_ABORTED.
  • It assigns ret rather than returning early, so the DMA *_POST
    unmapping at the bottom of each function still runs.

GCM keeps its pre-existing authTagSz > tag_len branch — that bounds the
caller's buffer, which is a different limit from the received frame.

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

New whTest_ClientMalformedResp (misc group). The AES DMA APIs send and
receive in one blocking call, so a scripted transport client callback echoes
the request's comm header and replies with a crafted frame. The body is
always written and only the reported length varies — otherwise the client
reads stale mapped DMA addresses and the wrong check fires.

Per family: accept at exactly the required size, reject at size − 1 with the
caller's state still holding the fill from the accepted exchange, and a
header-only reply carrying WH_ERROR_BADARGS that must return BADARGS.
GCM adds a case where the claimed tag exceeds the frame but still fits
tag_len, so only the new bound can reject it, and one where a fully carried
tag overruns tag_len, leaving the buffer tail untouched.

Verification

  • make check DMA=1 — 42 passed, 0 failed. make check SHE=1 — 43 passed;
    the new test reports skipped via the weak stub in non-DMA builds.
  • Legacy test/ suite builds and exits 0.
  • Mutation-verified, 10/10 killed: neutering, <>, and ±1 on each of
    the four guards, plus deleting and >>= on GCM's tag clause. An extra
    control hoisting the CTR bound above the rc check also fails the test,
    proving the error-reply case is enforced and not merely asserted.

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

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 wolfHSM crypto client’s AES DMA response parsing to prevent truncated/malformed server replies from causing the client to read stale bytes from the reused comm buffer (which can silently corrupt AES state for CTR/CBC or copy unintended bytes into a GCM tag buffer).

Changes:

  • Add response-length bounds checks in the AES DMA response handlers (CTR/ECB/CBC/GCM) in src/wh_client_crypto.c, keeping the checks inside the rc == WH_ERROR_OK path and preserving DMA POST cleanup behavior.
  • Add a new scripted-transport test (whTest_ClientMalformedResp) that exercises accept-at-bound, reject-at-bound−1, and header-only server-error replies for the AES DMA family.
  • Register the new test in the refactor test list.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.

File Description
src/wh_client_crypto.c Adds per-handler res_len bounds checks before parsing/copying trailing AES DMA response payload.
test-refactor/misc/wh_test_client_malformed_resp.c New misc test using a scripted transport to validate the client rejects truncated AES DMA responses without corrupting caller state.
test-refactor/wh_test_list.c Registers whTest_ClientMalformedResp in the misc test suite.

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

@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 #475

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

No new issues found in the changed files. ✅

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