Stop READTRUSTED transmitting unwritten response bytes#459
Stop READTRUSTED transmitting unwritten response bytes#459yosuke-wolfssl wants to merge 1 commit into
Conversation
There was a problem hiding this comment.
Pull request overview
This PR fixes an information-leak/response-corruption issue in the non-DMA READTRUSTED certificate handler where the server could transmit bytes from the shared comm buffer that were never written (e.g., after failures), by tightening length reporting and response sizing. It also updates wh_Server_CertReadTrusted() to honor its documented contract by reporting the stored certificate length on both success and WH_ERROR_BUFFER_SIZE.
Changes:
- Update
wh_Server_CertReadTrusted()to always reportmeta.lenvia*inout_cert_len, including onWH_ERROR_BUFFER_SIZE. - Fix non-DMA
READTRUSTEDresponse construction to only include payload bytes in*out_resp_sizeon success, and only setresp.cert_lenwhen a length is actually resolved. - Add targeted regression tests (server handler-level poisoning test + client API end-to-end coverage under DMA).
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
src/wh_server_cert.c |
Fixes the wh_Server_CertReadTrusted() length contract and prevents unwritten comm-buffer bytes from being included in READTRUSTED responses. |
test-refactor/server/wh_test_cert_readtrusted.c |
Adds direct handler tests that poison the response packet and assert header-only sizing/no writes past header on non-success paths. |
test-refactor/client-server/wh_test_client_certs.c |
Adds DMA-gated end-to-end client test asserting the reported stored length is preserved on WH_ERROR_BUFFER_SIZE. |
test-refactor/wh_test_list.c |
Registers the new whTest_CertReadTrusted test in the server test group. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
wolfSSL-Fenrir-bot
left a comment
There was a problem hiding this comment.
Fenrir Automated Review — PR #459
Scan targets checked: wolfhsm-core-bugs, wolfhsm-src
No new issues found in the changed files. ✅
dce04bf to
7036dda
Compare
| WH_TEST_RETURN_ON_FAIL(wh_Client_CertAddTrustedDma( | ||
| ctx, cert_id, WH_NVM_ACCESS_ANY, WH_NVM_FLAGS_NONE, NULL, 0, | ||
| big_cert, stored_len, &out_rc)); | ||
| } |
There was a problem hiding this comment.
Hmmm I don't like this: the particular add method (DMA vs regular) this test uses is decided at compile time. With our test config it will always takes the inline path, so the DMA path actually never actually runs in any build we test. The only way to hit it would be rebuilding with a different WOLFHSM_CFG_MAX_CERT_SIZE. I don't want a test that behaves differently depending on the build config, and I also don't want to merge test code that never runs. The server-side test already covers this fix in every config, so IMO this client-side test should just be removed.
There was a problem hiding this comment.
Thanks for reviewing.
You're right, and it's worse than "never runs in our config" — I checked the bounds:
| Path | Bound | DMA=1 test config |
|---|---|---|
| inline add | COMM_DATA_LEN - sizeof(AddTrustedRequest) |
8156 |
| DMA add | WOLFHSM_CFG_MAX_CERT_SIZE |
4096 |
READTRUSTED clamp |
min(MAX_CERT_SIZE, COMM_DATA_LEN - 8) |
4096 |
Defining WOLFHSM_CFG_DMA is exactly what drops MAX_CERT_SIZE to 4096, which makes it the
binding bound, which puts stored_len (4097) inside inline_max (8156) — so the gate that
compiles the test in is the same thing that guarantees the inline arm is selected. The DMA
arm only wins when the clamp is transport-bound, i.e. the non-DMA build, where the whole
function is preprocessed away.
That also rules out just making the branch config-independent: the DMA add can outrun the
clamp only when COMM_DATA_LEN < MAX_CERT_SIZE + 8, and the inline add only when
MAX_CERT_SIZE < COMM_DATA_LEN - 36. Those are mutually exclusive, so no single config can
exercise both and any version of this test carries a dead arm.
Removed the client-side test as you suggested.
7036dda to
f7aa8b0
Compare
Problem
The non-DMA
READTRUSTEDhandler sized its response from a length the callee neverresolved. When
wh_Server_CertReadTrusted()failed, no certificate data was staged, butthe handler still ran
resp.cert_len = cert_lenand*out_resp_size = sizeof(resp) + resp.cert_len, so the server transmitted response-packetbytes it had never written.
The handler's return code does not prevent this:
wh_Server_HandleRequestMessage()sendsthe response regardless (
src/wh_server.c:720-726), and the response packet is the samebuffer that carried the request, so the untouched region holds prior comm-buffer contents.
Measured before the fix: a stored 8185-byte certificate produced
resp_size = 8192(thewhole comm buffer), of which 8184 bytes were never written. The correct response is the
8-byte header alone.
Addressed by f_6020.
Root cause
wh_Server_CertReadTrusted()returnedWH_ERROR_BUFFER_SIZEbefore assigning*inout_cert_len, contradicting its contract inwolfhsm/wh_server_cert.h:70-72, so thehandler had only the staging clamp to report. The handler then trusted that value
unconditionally, which also leaked on internal
wh_Nvm_GetMetadata()andwh_Nvm_Read()failures.
Fix (
src/wh_server_cert.c)wh_Server_CertReadTrusted()reportsmeta.lenon both the success andBUFFER_SIZEpaths, honouring its documented contract. The caller's buffer size iscaptured first so the size check is unaffected.
resp.cert_lenonly for outcomes that resolve a length (OKorBUFFER_SIZE), and adds the payload to*out_resp_sizeonly on success. Every otheroutcome yields a header-only response.
Other callers (
CertVerifyMultiRoot,CertVerifyAcert, the DMA handler) never read thelength on a failure return, so the contract change is safe for them.
Tests
New
test-refactor/server/wh_test_cert_readtrusted.cdrives the handler directly against apoisoned response packet, pinning the clamp from both sides:
STAGED_LENmust read backwhole,
STAGED_LEN + 1must report the stored size with a header-only response and no bytepast the header touched. A second sub-test locks the
WH_ERROR_ACCESSpath to no length andno payload. It is gated only on
WOLFHSM_CFG_CERTIFICATE_MANAGER, so it runs in everyconfig.
The oversized case was confirmed to fail against the unfixed server, so it is a negative
control. The
ACCESScase passes either way and is kept as a forward-looking guard.Verification
test-refactordefault: 44 passed, 26 skipped, 0 failed of 70.test-refactorDMA=1 ASAN=1: 48 passed, 22 skipped, 0 failed of 70.test/DMA=1 ASAN=1: passing, unchanged by this PR.Not in this PR
the client requires either the inline add or the DMA add depending on
COMM_DATA_LENvsWOLFHSM_CFG_MAX_CERT_SIZE, and those two conditions are mutually exclusive — no singleconfig can exercise both, so the test necessarily carried an arm that no build compiles.
The server-side test covers the same contract in every config.