diff --git a/src/wh_client.c b/src/wh_client.c
index 31112e130..a0bc3e07e 100644
--- a/src/wh_client.c
+++ b/src/wh_client.c
@@ -318,7 +318,7 @@ int wh_Client_CommInitResponse(whClientContext* c,
uint32_t *out_serverid)
{
int rc = 0;
- whMessageCommInitResponse msg = {0};
+ whMessageCommInitResponse* msg = NULL;
uint16_t resp_group = 0;
uint16_t resp_action = 0;
uint16_t resp_size = 0;
@@ -327,23 +327,30 @@ int wh_Client_CommInitResponse(whClientContext* c,
return WH_ERROR_BADARGS;
}
+ /* Receive into the comm buffer: the comm layer copies the whole received
+ * payload in without bounding it to the caller's buffer size */
+ msg = (whMessageCommInitResponse*)wh_CommClient_GetDataPtr(c->comm);
+ if (msg == NULL) {
+ return WH_ERROR_BADARGS;
+ }
+
rc = wh_Client_RecvResponse(c,
&resp_group, &resp_action,
- &resp_size, &msg);
+ &resp_size, msg);
if (rc == 0) {
/* Validate response */
if ( (resp_group != WH_MESSAGE_GROUP_COMM) ||
(resp_action != WH_MESSAGE_COMM_ACTION_INIT) ||
- (resp_size != sizeof(msg)) ){
+ (resp_size != sizeof(*msg)) ){
/* Invalid message */
rc = WH_ERROR_ABORTED;
} else {
/* Valid message */
if (out_clientid != NULL) {
- *out_clientid = msg.client_id;
+ *out_clientid = msg->client_id;
}
if (out_serverid != NULL) {
- *out_serverid = msg.server_id;
+ *out_serverid = msg->server_id;
}
}
}
@@ -398,7 +405,7 @@ int wh_Client_CommInfoResponse(whClientContext* c,
uint32_t *out_nvm_state)
{
int rc = 0;
- whMessageCommInfoResponse msg = {0};
+ whMessageCommInfoResponse* msg = NULL;
uint16_t resp_group = 0;
uint16_t resp_action = 0;
uint16_t resp_size = 0;
@@ -407,59 +414,66 @@ int wh_Client_CommInfoResponse(whClientContext* c,
return WH_ERROR_BADARGS;
}
+ /* Receive into the comm buffer: the comm layer copies the whole received
+ * payload in without bounding it to the caller's buffer size */
+ msg = (whMessageCommInfoResponse*)wh_CommClient_GetDataPtr(c->comm);
+ if (msg == NULL) {
+ return WH_ERROR_BADARGS;
+ }
+
rc = wh_Client_RecvResponse(c,
&resp_group, &resp_action,
- &resp_size, &msg);
+ &resp_size, msg);
if (rc == 0) {
/* Validate response */
if ( (resp_group != WH_MESSAGE_GROUP_COMM) ||
(resp_action != WH_MESSAGE_COMM_ACTION_INFO) ||
- (resp_size != sizeof(msg)) ){
+ (resp_size != sizeof(*msg)) ){
/* Invalid message */
rc = WH_ERROR_ABORTED;
} else {
/* Valid message */
if (out_version != NULL) {
- memcpy(out_version, msg.version, sizeof(msg.version));
+ memcpy(out_version, msg->version, sizeof(msg->version));
}
if (out_build != NULL) {
- memcpy(out_build, msg.build, sizeof(msg.build));
+ memcpy(out_build, msg->build, sizeof(msg->build));
}
if (out_cfg_comm_data_len != NULL) {
- *out_cfg_comm_data_len = msg.cfg_comm_data_len;
+ *out_cfg_comm_data_len = msg->cfg_comm_data_len;
}
if (out_cfg_nvm_object_count != NULL) {
- *out_cfg_nvm_object_count = msg.cfg_nvm_object_count;
+ *out_cfg_nvm_object_count = msg->cfg_nvm_object_count;
}
if (out_cfg_keycache_count != NULL) {
- *out_cfg_keycache_count = msg.cfg_server_keycache_count;
+ *out_cfg_keycache_count = msg->cfg_server_keycache_count;
}
if (out_cfg_keycache_bufsize != NULL) {
- *out_cfg_keycache_bufsize = msg.cfg_server_keycache_bufsize;
+ *out_cfg_keycache_bufsize = msg->cfg_server_keycache_bufsize;
}
if (out_cfg_keycache_bigcount != NULL) {
- *out_cfg_keycache_bigcount = msg.cfg_server_keycache_bigcount;
+ *out_cfg_keycache_bigcount = msg->cfg_server_keycache_bigcount;
}
if (out_cfg_keycache_bigbufsize != NULL) {
- *out_cfg_keycache_bigbufsize = msg.cfg_server_keycache_bigbufsize;
+ *out_cfg_keycache_bigbufsize = msg->cfg_server_keycache_bigbufsize;
}
if (out_cfg_customcb_count != NULL) {
- *out_cfg_customcb_count = msg.cfg_server_customcb_count;
+ *out_cfg_customcb_count = msg->cfg_server_customcb_count;
}
if (out_cfg_dmaaddr_count != NULL) {
- *out_cfg_dmaaddr_count = msg.cfg_server_dmaaddr_count;
+ *out_cfg_dmaaddr_count = msg->cfg_server_dmaaddr_count;
}
if (out_debug_state != NULL) {
- *out_debug_state = msg.debug_state;
+ *out_debug_state = msg->debug_state;
}
if (out_boot_state != NULL) {
- *out_boot_state = msg.boot_state;
+ *out_boot_state = msg->boot_state;
}
if (out_lifecycle_state != NULL) {
- *out_lifecycle_state = msg.lifecycle_state;
+ *out_lifecycle_state = msg->lifecycle_state;
}
if (out_nvm_state != NULL) {
- *out_nvm_state = msg.nvm_state;
+ *out_nvm_state = msg->nvm_state;
}
}
}
@@ -591,7 +605,8 @@ int wh_Client_CommCloseResponse(whClientContext* c)
if (rc == 0) {
/* Validate response */
if ( (resp_group != WH_MESSAGE_GROUP_COMM) ||
- (resp_action != WH_MESSAGE_COMM_ACTION_CLOSE) ){
+ (resp_action != WH_MESSAGE_COMM_ACTION_CLOSE) ||
+ (resp_size != 0) ){
/* Invalid message */
rc = WH_ERROR_ABORTED;
} else {
@@ -711,7 +726,7 @@ int wh_Client_CustomCbRequest(whClientContext* c, const whMessageCustomCb_Reques
int wh_Client_CustomCbResponse(whClientContext* c,
whMessageCustomCb_Response* outResp)
{
- whMessageCustomCb_Response resp;
+ whMessageCustomCb_Response* resp = NULL;
uint16_t resp_group = 0;
uint16_t resp_action = 0;
uint16_t resp_size = 0;
@@ -721,18 +736,25 @@ int wh_Client_CustomCbResponse(whClientContext* c,
return WH_ERROR_BADARGS;
}
+ /* Receive into the comm buffer: the comm layer copies the whole received
+ * payload in without bounding it to the caller's buffer size */
+ resp = (whMessageCustomCb_Response*)wh_CommClient_GetDataPtr(c->comm);
+ if (resp == NULL) {
+ return WH_ERROR_BADARGS;
+ }
+
rc =
- wh_Client_RecvResponse(c, &resp_group, &resp_action, &resp_size, &resp);
+ wh_Client_RecvResponse(c, &resp_group, &resp_action, &resp_size, resp);
if (rc != WH_ERROR_OK) {
return rc;
}
- if (resp_size != sizeof(resp) || resp_group != WH_MESSAGE_GROUP_CUSTOM) {
+ if (resp_size != sizeof(*resp) || resp_group != WH_MESSAGE_GROUP_CUSTOM) {
/* message invalid */
return WH_ERROR_ABORTED;
}
- memcpy(outResp, &resp, sizeof(resp));
+ memcpy(outResp, resp, sizeof(*resp));
return WH_ERROR_OK;
}
@@ -874,7 +896,12 @@ int wh_Client_KeyCacheResponse(whClientContext* c, uint16_t* keyId)
ret = wh_Client_RecvResponse(c, &group, &action, &size, (uint8_t*)resp);
if (ret == WH_ERROR_OK) {
- if (resp->rc != 0) {
+ /* Defensive bound: the response fields must fit within the actual
+ * received frame */
+ if (size < sizeof(*resp)) {
+ ret = WH_ERROR_ABORTED;
+ }
+ else if (resp->rc != 0) {
ret = resp->rc;
}
else {
@@ -961,7 +988,12 @@ int wh_Client_KeyCacheRandomResponse(whClientContext* c, uint16_t* outKeyId)
ret = wh_Client_RecvResponse(c, &group, &action, &size, (uint8_t*)resp);
if (ret == WH_ERROR_OK) {
- if (resp->rc != 0) {
+ /* Defensive bound: the response fields must fit within the actual
+ * received frame */
+ if (size < sizeof(*resp)) {
+ ret = WH_ERROR_ABORTED;
+ }
+ else if (resp->rc != 0) {
ret = resp->rc;
}
else {
@@ -1021,17 +1053,29 @@ int wh_Client_KeyEvictResponse(whClientContext* c)
uint16_t action;
uint16_t size;
int ret;
- whMessageKeystore_EvictResponse resp;
+ whMessageKeystore_EvictResponse* resp = NULL;
if (c == NULL) {
return WH_ERROR_BADARGS;
}
- ret = wh_Client_RecvResponse(c, &group, &action, &size, (uint8_t*)&resp);
+ /* Receive into the comm buffer: the comm layer copies the whole received
+ * payload in without bounding it to the caller's buffer size */
+ resp = (whMessageKeystore_EvictResponse*)wh_CommClient_GetDataPtr(c->comm);
+ if (resp == NULL) {
+ return WH_ERROR_BADARGS;
+ }
+
+ ret = wh_Client_RecvResponse(c, &group, &action, &size, (uint8_t*)resp);
if (ret == 0) {
- if (resp.rc != 0) {
- ret = resp.rc;
+ /* Defensive bound: the response fields must fit within the actual
+ * received frame */
+ if (size < sizeof(*resp)) {
+ ret = WH_ERROR_ABORTED;
+ }
+ else if (resp->rc != 0) {
+ ret = resp->rc;
}
}
@@ -1092,9 +1136,17 @@ int wh_Client_KeyExportResponse(whClientContext* c, uint8_t* label,
ret = wh_Client_RecvResponse(c, &group, &action, &size, (uint8_t*)resp);
if (ret == WH_ERROR_OK) {
- if (resp->rc != 0) {
+ /* Defensive bounds: the fixed fields, then the key material, must fit
+ * within the actual received frame */
+ if (size < sizeof(*resp)) {
+ ret = WH_ERROR_ABORTED;
+ }
+ else if (resp->rc != 0) {
ret = resp->rc;
}
+ else if (resp->len > (size - sizeof(*resp))) {
+ ret = WH_ERROR_ABORTED;
+ }
else {
if (out == NULL) {
*outSz = resp->len;
@@ -1177,9 +1229,17 @@ int wh_Client_KeyExportPublicResponse(whClientContext* c, uint8_t* label,
ret = wh_Client_RecvResponse(c, &group, &action, &size, (uint8_t*)resp);
if (ret == WH_ERROR_OK) {
- if (resp->rc != 0) {
+ /* Defensive bounds: the fixed fields, then the key material, must fit
+ * within the actual received frame */
+ if (size < sizeof(*resp)) {
+ ret = WH_ERROR_ABORTED;
+ }
+ else if (resp->rc != 0) {
ret = resp->rc;
}
+ else if (resp->len > (size - sizeof(*resp))) {
+ ret = WH_ERROR_ABORTED;
+ }
else {
if (out == NULL) {
*outSz = resp->len;
@@ -1254,7 +1314,12 @@ int wh_Client_KeyCommitResponse(whClientContext* c)
ret = wh_Client_RecvResponse(c, &group, &action, &size, (uint8_t*)resp);
if (ret == WH_ERROR_OK) {
- if (resp->rc != 0) {
+ /* Defensive bound: the response fields must fit within the actual
+ * received frame */
+ if (size < sizeof(*resp)) {
+ ret = WH_ERROR_ABORTED;
+ }
+ else if (resp->rc != 0) {
ret = resp->rc;
}
}
@@ -1310,7 +1375,12 @@ int wh_Client_KeyEraseResponse(whClientContext* c)
ret = wh_Client_RecvResponse(c, &group, &action, &size, (uint8_t*)resp);
if (ret == 0) {
- if (resp->rc != 0) {
+ /* Defensive bound: the response fields must fit within the actual
+ * received frame */
+ if (size < sizeof(*resp)) {
+ ret = WH_ERROR_ABORTED;
+ }
+ else if (resp->rc != 0) {
ret = resp->rc;
}
}
@@ -1366,7 +1436,12 @@ int wh_Client_KeyRevokeResponse(whClientContext* c)
ret = wh_Client_RecvResponse(c, &group, &action, &size, (uint8_t*)resp);
if (ret == 0) {
- if (resp->rc != 0) {
+ /* Defensive bound: the response fields must fit within the actual
+ * received frame */
+ if (size < sizeof(*resp)) {
+ ret = WH_ERROR_ABORTED;
+ }
+ else if (resp->rc != 0) {
ret = resp->rc;
}
}
@@ -1424,7 +1499,12 @@ int wh_Client_CounterInitResponse(whClientContext* c, uint32_t* counter)
ret = wh_Client_RecvResponse(c, &group, &action, &size, (uint8_t*)resp);
if (ret == WH_ERROR_OK) {
- if (resp->rc != 0) {
+ /* Defensive bound: the response fields must fit within the actual
+ * received frame */
+ if (size < sizeof(*resp)) {
+ ret = WH_ERROR_ABORTED;
+ }
+ else if (resp->rc != 0) {
ret = resp->rc;
}
else if (counter != NULL) {
@@ -1502,7 +1582,12 @@ int wh_Client_CounterIncrementResponse(whClientContext* c, uint32_t* counter)
ret = wh_Client_RecvResponse(c, &group, &action, &size, (uint8_t*)resp);
if (ret == WH_ERROR_OK) {
- if (resp->rc != 0) {
+ /* Defensive bound: the response fields must fit within the actual
+ * received frame */
+ if (size < sizeof(*resp)) {
+ ret = WH_ERROR_ABORTED;
+ }
+ else if (resp->rc != 0) {
ret = resp->rc;
}
else if (counter != NULL) {
@@ -1562,7 +1647,12 @@ int wh_Client_CounterReadResponse(whClientContext* c, uint32_t* counter)
ret = wh_Client_RecvResponse(c, &group, &action, &size, (uint8_t*)resp);
if (ret == WH_ERROR_OK) {
- if (resp->rc != 0) {
+ /* Defensive bound: the response fields must fit within the actual
+ * received frame */
+ if (size < sizeof(*resp)) {
+ ret = WH_ERROR_ABORTED;
+ }
+ else if (resp->rc != 0) {
ret = resp->rc;
}
else {
@@ -1622,7 +1712,12 @@ int wh_Client_CounterDestroyResponse(whClientContext* c)
ret = wh_Client_RecvResponse(c, &group, &action, &size, (uint8_t*)resp);
if (ret == WH_ERROR_OK) {
- if (resp->rc != 0) {
+ /* Defensive bound: the response fields must fit within the actual
+ * received frame */
+ if (size < sizeof(*resp)) {
+ ret = WH_ERROR_ABORTED;
+ }
+ else if (resp->rc != 0) {
ret = resp->rc;
}
}
diff --git a/test-refactor/misc/wh_test_client_malformed_resp.c b/test-refactor/misc/wh_test_client_malformed_resp.c
new file mode 100644
index 000000000..5ed9571db
--- /dev/null
+++ b/test-refactor/misc/wh_test_client_malformed_resp.c
@@ -0,0 +1,503 @@
+/*
+ * Copyright (C) 2026 wolfSSL Inc.
+ *
+ * This file is part of wolfHSM.
+ *
+ * wolfHSM is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * wolfHSM is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with wolfHSM. If not, see .
+ */
+/*
+ * test-refactor/misc/wh_test_client_malformed_resp.c
+ *
+ * Client-side hardening against malformed server responses. Driven by a raw
+ * comm server, so a reply can carry the correct kind and sequence while
+ * declaring a bogus payload size.
+ */
+
+#include "wolfhsm/wh_settings.h"
+
+#if defined(WOLFHSM_CFG_ENABLE_CLIENT) && defined(WOLFHSM_CFG_ENABLE_SERVER)
+
+#include
+#include
+
+#include "wolfhsm/wh_error.h"
+#include "wolfhsm/wh_comm.h"
+#include "wolfhsm/wh_message.h"
+#include "wolfhsm/wh_message_comm.h"
+#include "wolfhsm/wh_message_counter.h"
+#include "wolfhsm/wh_message_customcb.h"
+#include "wolfhsm/wh_message_keystore.h"
+#include "wolfhsm/wh_client.h"
+#include "wolfhsm/wh_transport_mem.h"
+#include "wolfhsm/wh_utils.h"
+
+#include "wh_test_common.h"
+#include "wh_test_list.h"
+
+#define WH_TEST_MR_BUFFER_SIZE 4096
+/* Largest frame both ends carry: the transport buffer less its CSR and the
+ * comm header, capped by the comm payload limit */
+#define WH_TEST_MR_TRANSPORT_MAX \
+ (WH_TEST_MR_BUFFER_SIZE - sizeof(whTransportMemCsr) - \
+ sizeof(whCommHeader))
+#define WH_TEST_MR_OVERSIZED_SZ \
+ ((WOLFHSM_CFG_COMM_DATA_LEN < WH_TEST_MR_TRANSPORT_MAX) \
+ ? (size_t)WOLFHSM_CFG_COMM_DATA_LEN \
+ : WH_TEST_MR_TRANSPORT_MAX)
+#define WH_TEST_MR_KEY_SZ 32
+#define WH_TEST_MR_POISON 0xA5
+#define WH_TEST_MR_KEY_ID 1
+#define WH_TEST_MR_COUNTER_ID 1
+
+/* The oversized frame must outgrow every response struct it is sent to, or
+ * those handlers never see an over-long reply */
+WH_UTILS_STATIC_ASSERT(WH_TEST_MR_OVERSIZED_SZ >
+ sizeof(whMessageCustomCb_Response),
+ "oversized frame too small to overrun the responses");
+WH_UTILS_STATIC_ASSERT(WH_TEST_MR_OVERSIZED_SZ >
+ sizeof(whMessageCommInfoResponse),
+ "oversized frame too small to overrun the responses");
+
+/* The fixed-size response handlers, dispatched by index */
+enum {
+ WH_TEST_MR_KEY_CACHE = 0,
+ WH_TEST_MR_KEY_CACHE_RANDOM,
+ WH_TEST_MR_KEY_EVICT,
+ WH_TEST_MR_KEY_COMMIT,
+ WH_TEST_MR_KEY_ERASE,
+ WH_TEST_MR_KEY_REVOKE,
+ WH_TEST_MR_COUNTER_INIT,
+ WH_TEST_MR_COUNTER_INCREMENT,
+ WH_TEST_MR_COUNTER_READ,
+ WH_TEST_MR_COUNTER_DESTROY,
+ WH_TEST_MR_FIXED_COUNT
+};
+
+typedef struct {
+ const char* name;
+ uint16_t respSz;
+} whFixedCase;
+
+static const whFixedCase _fixedCase[] = {
+ {"KeyCache", (uint16_t)sizeof(whMessageKeystore_CacheResponse)},
+ {"KeyCacheRandom", (uint16_t)sizeof(whMessageKeystore_CacheRandomResponse)},
+ {"KeyEvict", (uint16_t)sizeof(whMessageKeystore_EvictResponse)},
+ {"KeyCommit", (uint16_t)sizeof(whMessageKeystore_CommitResponse)},
+ {"KeyErase", (uint16_t)sizeof(whMessageKeystore_EraseResponse)},
+ {"KeyRevoke", (uint16_t)sizeof(whMessageKeystore_RevokeResponse)},
+ {"CounterInit", (uint16_t)sizeof(whMessageCounter_InitResponse)},
+ {"CounterIncrement",
+ (uint16_t)sizeof(whMessageCounter_IncrementResponse)},
+ {"CounterRead", (uint16_t)sizeof(whMessageCounter_ReadResponse)},
+ {"CounterDestroy", (uint16_t)sizeof(whMessageCounter_DestroyResponse)},
+};
+
+typedef struct {
+ whClientContext client[1];
+ whCommServer server[1];
+ /* Mem transport shared by the client and the raw comm server */
+ uint8_t reqBuf[WH_TEST_MR_BUFFER_SIZE];
+ uint8_t respBuf[WH_TEST_MR_BUFFER_SIZE];
+ whTransportMemConfig tmcf[1];
+ whTransportClientCb tccb[1];
+ whTransportMemClientContext tmcc[1];
+ whCommClientConfig cc_conf[1];
+ whClientConfig c_conf[1];
+ whTransportServerCb tscb[1];
+ whTransportMemServerContext tmsc[1];
+ whCommServerConfig cs_conf[1];
+} TestCtx;
+
+/* Frame buffers are static to keep WOLFHSM_CFG_COMM_DATA_LEN off the stack */
+static uint8_t _rxReq[WOLFHSM_CFG_COMM_DATA_LEN];
+static uint8_t _txResp[WOLFHSM_CFG_COMM_DATA_LEN];
+
+static int _SetupClientServer(TestCtx* t)
+{
+ memset(t, 0, sizeof(*t));
+
+ t->tmcf[0] = (whTransportMemConfig){
+ .req = (whTransportMemCsr*)t->reqBuf,
+ .req_size = sizeof(t->reqBuf),
+ .resp = (whTransportMemCsr*)t->respBuf,
+ .resp_size = sizeof(t->respBuf),
+ };
+
+ t->tccb[0] = (whTransportClientCb)WH_TRANSPORT_MEM_CLIENT_CB;
+ t->cc_conf[0] = (whCommClientConfig){
+ .transport_cb = t->tccb,
+ .transport_context = (void*)t->tmcc,
+ .transport_config = (void*)t->tmcf,
+ .client_id = WH_TEST_DEFAULT_CLIENT_ID,
+ };
+ t->c_conf[0] = (whClientConfig){
+ .comm = t->cc_conf,
+ };
+
+ t->tscb[0] = (whTransportServerCb)WH_TRANSPORT_MEM_SERVER_CB;
+ t->cs_conf[0] = (whCommServerConfig){
+ .transport_cb = t->tscb,
+ .transport_context = (void*)t->tmsc,
+ .transport_config = (void*)t->tmcf,
+ .server_id = 124,
+ };
+
+ WH_TEST_RETURN_ON_FAIL(wh_Client_Init(t->client, t->c_conf));
+ WH_TEST_RETURN_ON_FAIL(
+ wh_CommServer_Init(t->server, t->cs_conf, NULL, NULL));
+ return WH_ERROR_OK;
+}
+
+static void _CleanupClientServer(TestCtx* t)
+{
+ (void)wh_CommServer_Cleanup(t->server);
+ (void)wh_Client_Cleanup(t->client);
+}
+
+/* Consume the pending request and answer it from _txResp, declaring frameLen
+ * bytes regardless of what the message actually needs */
+static int _ReplyWith(TestCtx* t, uint16_t frameLen)
+{
+ int ret = 0;
+ uint16_t magic = 0;
+ uint16_t kind = 0;
+ uint16_t seq = 0;
+ uint16_t len = 0;
+
+ ret = wh_CommServer_RecvRequest(t->server, &magic, &kind, &seq, &len,
+ sizeof(_rxReq), _rxReq);
+ if (ret == WH_ERROR_OK) {
+ ret = wh_CommServer_SendResponse(t->server, magic, kind, seq, frameLen,
+ _txResp);
+ }
+ return ret;
+}
+
+/* Issue the request for a fixed-size case so the reply's kind and seq line up */
+static int _FixedRequest(TestCtx* t, int idx)
+{
+ int ret = WH_ERROR_BADARGS;
+ uint8_t key[WH_TEST_MR_KEY_SZ];
+
+ memset(key, WH_TEST_MR_POISON, sizeof(key));
+
+ switch (idx) {
+ case WH_TEST_MR_KEY_CACHE:
+ ret = wh_Client_KeyCacheRequest(t->client, 0, NULL, 0, key,
+ sizeof(key));
+ break;
+ case WH_TEST_MR_KEY_CACHE_RANDOM:
+ ret = wh_Client_KeyCacheRandomRequest(t->client, 0, NULL, 0,
+ sizeof(key),
+ WH_TEST_MR_KEY_ID);
+ break;
+ case WH_TEST_MR_KEY_EVICT:
+ ret = wh_Client_KeyEvictRequest(t->client, WH_TEST_MR_KEY_ID);
+ break;
+ case WH_TEST_MR_KEY_COMMIT:
+ ret = wh_Client_KeyCommitRequest(t->client, WH_TEST_MR_KEY_ID);
+ break;
+ case WH_TEST_MR_KEY_ERASE:
+ ret = wh_Client_KeyEraseRequest(t->client, WH_TEST_MR_KEY_ID);
+ break;
+ case WH_TEST_MR_KEY_REVOKE:
+ ret = wh_Client_KeyRevokeRequest(t->client, WH_TEST_MR_KEY_ID);
+ break;
+ case WH_TEST_MR_COUNTER_INIT:
+ ret = wh_Client_CounterInitRequest(t->client,
+ WH_TEST_MR_COUNTER_ID, 0);
+ break;
+ case WH_TEST_MR_COUNTER_INCREMENT:
+ ret = wh_Client_CounterIncrementRequest(t->client,
+ WH_TEST_MR_COUNTER_ID);
+ break;
+ case WH_TEST_MR_COUNTER_READ:
+ ret = wh_Client_CounterReadRequest(t->client,
+ WH_TEST_MR_COUNTER_ID);
+ break;
+ case WH_TEST_MR_COUNTER_DESTROY:
+ ret = wh_Client_CounterDestroyRequest(t->client,
+ WH_TEST_MR_COUNTER_ID);
+ break;
+ default:
+ break;
+ }
+ return ret;
+}
+
+static int _FixedResponse(TestCtx* t, int idx)
+{
+ int ret = WH_ERROR_BADARGS;
+ uint16_t keyId = 0;
+ uint32_t counter = 0;
+
+ switch (idx) {
+ case WH_TEST_MR_KEY_CACHE:
+ ret = wh_Client_KeyCacheResponse(t->client, &keyId);
+ break;
+ case WH_TEST_MR_KEY_CACHE_RANDOM:
+ ret = wh_Client_KeyCacheRandomResponse(t->client, &keyId);
+ break;
+ case WH_TEST_MR_KEY_EVICT:
+ ret = wh_Client_KeyEvictResponse(t->client);
+ break;
+ case WH_TEST_MR_KEY_COMMIT:
+ ret = wh_Client_KeyCommitResponse(t->client);
+ break;
+ case WH_TEST_MR_KEY_ERASE:
+ ret = wh_Client_KeyEraseResponse(t->client);
+ break;
+ case WH_TEST_MR_KEY_REVOKE:
+ ret = wh_Client_KeyRevokeResponse(t->client);
+ break;
+ case WH_TEST_MR_COUNTER_INIT:
+ ret = wh_Client_CounterInitResponse(t->client, &counter);
+ break;
+ case WH_TEST_MR_COUNTER_INCREMENT:
+ ret = wh_Client_CounterIncrementResponse(t->client, &counter);
+ break;
+ case WH_TEST_MR_COUNTER_READ:
+ ret = wh_Client_CounterReadResponse(t->client, &counter);
+ break;
+ case WH_TEST_MR_COUNTER_DESTROY:
+ ret = wh_Client_CounterDestroyResponse(t->client);
+ break;
+ default:
+ break;
+ }
+ return ret;
+}
+
+/* Every fixed-size handler, at the exact bound: reject a frame one byte short
+ * of the response struct, accept one sitting exactly on it. The short frame is
+ * all-ones so a missing size gate surfaces as a stale nonzero rc. */
+static int _whTest_MalformedRespFixed(TestCtx* t)
+{
+ int ret = 0;
+ int i = 0;
+
+ for (i = 0; i < WH_TEST_MR_FIXED_COUNT; i++) {
+ memset(_txResp, 0xFF, sizeof(_txResp));
+ WH_TEST_RETURN_ON_FAIL(_FixedRequest(t, i));
+ WH_TEST_RETURN_ON_FAIL(
+ _ReplyWith(t, (uint16_t)(_fixedCase[i].respSz - 1)));
+
+ ret = _FixedResponse(t, i);
+ if (ret != WH_ERROR_ABORTED) {
+ WH_ERROR_PRINT("%s accepted a short response: %d\n",
+ _fixedCase[i].name, ret);
+ return WH_ERROR_ABORTED;
+ }
+
+ /* rc of zero, so only the size can decide the outcome */
+ memset(_txResp, 0, sizeof(_txResp));
+ WH_TEST_RETURN_ON_FAIL(_FixedRequest(t, i));
+ WH_TEST_RETURN_ON_FAIL(_ReplyWith(t, _fixedCase[i].respSz));
+
+ ret = _FixedResponse(t, i);
+ if (ret != WH_ERROR_OK) {
+ WH_ERROR_PRINT("%s rejected a well-formed response: %d\n",
+ _fixedCase[i].name, ret);
+ return WH_ERROR_ABORTED;
+ }
+ }
+
+ return WH_ERROR_OK;
+}
+
+/* Craft a key export reply declaring claimedLen bytes of key material while
+ * handing the transport only payloadSz bytes of it */
+static int _ReplyWithExport(TestCtx* t, uint32_t claimedLen, uint16_t payloadSz,
+ uint16_t frameLen)
+{
+ whMessageKeystore_ExportResponse* resp =
+ (whMessageKeystore_ExportResponse*)_txResp;
+
+ memset(_txResp, 0, sizeof(_txResp));
+ resp->rc = 0;
+ resp->len = claimedLen;
+ memset((uint8_t*)(resp + 1), WH_TEST_MR_POISON, payloadSz);
+
+ return _ReplyWith(t, frameLen);
+}
+
+/* A response claiming more key material than the frame carried must be
+ * rejected instead of copying whatever trails the payload - which, on a
+ * request/response buffer, is the client's own outbound request. */
+static int _whTest_MalformedRespKeyExport(TestCtx* t)
+{
+ int ret = 0;
+ int i = 0;
+ uint16_t hdrSz = (uint16_t)sizeof(whMessageKeystore_ExportResponse);
+ uint16_t outSz = 0;
+ uint8_t out[WH_TEST_MR_KEY_SZ * 2];
+ uint8_t label[WH_NVM_LABEL_LEN];
+
+ /* Well-formed exchange first, so the comm buffer holds a known pattern
+ * past the header for the malformed exchanges to expose */
+ memset(out, 0, sizeof(out));
+ WH_TEST_RETURN_ON_FAIL(
+ wh_Client_KeyExportRequest(t->client, WH_TEST_MR_KEY_ID));
+ WH_TEST_RETURN_ON_FAIL(_ReplyWithExport(t, WH_TEST_MR_KEY_SZ,
+ WH_TEST_MR_KEY_SZ,
+ (uint16_t)(hdrSz +
+ WH_TEST_MR_KEY_SZ)));
+ outSz = sizeof(out);
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyExportResponse(
+ t->client, label, sizeof(label), out, &outSz));
+ WH_TEST_ASSERT_RETURN(outSz == WH_TEST_MR_KEY_SZ);
+ for (i = 0; i < WH_TEST_MR_KEY_SZ; i++) {
+ WH_TEST_ASSERT_RETURN(out[i] == WH_TEST_MR_POISON);
+ }
+
+ /* One byte past the payload the frame actually carried */
+ memset(out, 0, sizeof(out));
+ WH_TEST_RETURN_ON_FAIL(
+ wh_Client_KeyExportRequest(t->client, WH_TEST_MR_KEY_ID));
+ WH_TEST_RETURN_ON_FAIL(_ReplyWithExport(
+ t, WH_TEST_MR_KEY_SZ + 1, WH_TEST_MR_KEY_SZ,
+ (uint16_t)(hdrSz + WH_TEST_MR_KEY_SZ)));
+ outSz = sizeof(out);
+ ret = wh_Client_KeyExportResponse(t->client, label, sizeof(label), out,
+ &outSz);
+ WH_TEST_ASSERT_RETURN(ret == WH_ERROR_ABORTED);
+ /* Nothing may have been copied out of the comm buffer */
+ for (i = 0; i < (int)sizeof(out); i++) {
+ WH_TEST_ASSERT_RETURN(out[i] == 0);
+ }
+
+ /* A frame too short for even the fixed fields */
+ memset(out, 0, sizeof(out));
+ WH_TEST_RETURN_ON_FAIL(
+ wh_Client_KeyExportRequest(t->client, WH_TEST_MR_KEY_ID));
+ WH_TEST_RETURN_ON_FAIL(
+ _ReplyWithExport(t, WH_TEST_MR_KEY_SZ, 0, (uint16_t)(hdrSz - 1)));
+ outSz = sizeof(out);
+ ret = wh_Client_KeyExportResponse(t->client, label, sizeof(label), out,
+ &outSz);
+ WH_TEST_ASSERT_RETURN(ret == WH_ERROR_ABORTED);
+ for (i = 0; i < (int)sizeof(out); i++) {
+ WH_TEST_ASSERT_RETURN(out[i] == 0);
+ }
+
+ return WH_ERROR_OK;
+}
+
+/* Same bound, on the public-key export path */
+static int _whTest_MalformedRespKeyExportPublic(TestCtx* t)
+{
+ int ret = 0;
+ int i = 0;
+ uint16_t hdrSz = (uint16_t)sizeof(whMessageKeystore_ExportPublicResponse);
+ uint16_t outSz = 0;
+ uint8_t out[WH_TEST_MR_KEY_SZ * 2];
+ uint8_t label[WH_NVM_LABEL_LEN];
+
+ WH_TEST_ASSERT_RETURN(sizeof(whMessageKeystore_ExportPublicResponse) ==
+ sizeof(whMessageKeystore_ExportResponse));
+
+ /* Declared length one past the payload received */
+ memset(out, 0, sizeof(out));
+ WH_TEST_RETURN_ON_FAIL(
+ wh_Client_KeyExportPublicRequest(t->client, WH_TEST_MR_KEY_ID, 0));
+ WH_TEST_RETURN_ON_FAIL(_ReplyWithExport(
+ t, WH_TEST_MR_KEY_SZ + 1, WH_TEST_MR_KEY_SZ,
+ (uint16_t)(hdrSz + WH_TEST_MR_KEY_SZ)));
+ outSz = sizeof(out);
+ ret = wh_Client_KeyExportPublicResponse(t->client, label, sizeof(label),
+ out, &outSz);
+ WH_TEST_ASSERT_RETURN(ret == WH_ERROR_ABORTED);
+ for (i = 0; i < (int)sizeof(out); i++) {
+ WH_TEST_ASSERT_RETURN(out[i] == 0);
+ }
+
+ /* Exactly on the bound is accepted */
+ memset(out, 0, sizeof(out));
+ WH_TEST_RETURN_ON_FAIL(
+ wh_Client_KeyExportPublicRequest(t->client, WH_TEST_MR_KEY_ID, 0));
+ WH_TEST_RETURN_ON_FAIL(_ReplyWithExport(t, WH_TEST_MR_KEY_SZ,
+ WH_TEST_MR_KEY_SZ,
+ (uint16_t)(hdrSz +
+ WH_TEST_MR_KEY_SZ)));
+ outSz = sizeof(out);
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyExportPublicResponse(
+ t->client, label, sizeof(label), out, &outSz));
+ WH_TEST_ASSERT_RETURN(outSz == WH_TEST_MR_KEY_SZ);
+ for (i = 0; i < WH_TEST_MR_KEY_SZ; i++) {
+ WH_TEST_ASSERT_RETURN(out[i] == WH_TEST_MR_POISON);
+ }
+
+ return WH_ERROR_OK;
+}
+
+/* A reply far larger than the response struct. The comm layer copies the whole
+ * payload into the buffer it is handed, so these handlers must not hand it a
+ * buffer smaller than a maximum-size frame. */
+static int _whTest_MalformedRespOversized(TestCtx* t)
+{
+ int ret = 0;
+ whMessageCustomCb_Response customResp;
+
+ memset(_txResp, 0, sizeof(_txResp));
+
+ /* Fixed-size keystore handler: the frame is oversized but well-formed up
+ * front, so the crafted rc of zero comes back */
+ WH_TEST_RETURN_ON_FAIL(
+ wh_Client_KeyEvictRequest(t->client, WH_TEST_MR_KEY_ID));
+ WH_TEST_RETURN_ON_FAIL(_ReplyWith(t, (uint16_t)WH_TEST_MR_OVERSIZED_SZ));
+ WH_TEST_RETURN_ON_FAIL(wh_Client_KeyEvictResponse(t->client));
+
+ /* Handlers validating an exact size reject the frame outright */
+ WH_TEST_RETURN_ON_FAIL(wh_Client_CommInitRequest(t->client));
+ WH_TEST_RETURN_ON_FAIL(_ReplyWith(t, (uint16_t)WH_TEST_MR_OVERSIZED_SZ));
+ ret = wh_Client_CommInitResponse(t->client, NULL, NULL);
+ WH_TEST_ASSERT_RETURN(ret == WH_ERROR_ABORTED);
+
+ WH_TEST_RETURN_ON_FAIL(wh_Client_CommInfoRequest(t->client));
+ WH_TEST_RETURN_ON_FAIL(_ReplyWith(t, (uint16_t)WH_TEST_MR_OVERSIZED_SZ));
+ ret = wh_Client_CommInfoResponse(t->client, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL);
+ WH_TEST_ASSERT_RETURN(ret == WH_ERROR_ABORTED);
+
+ memset(&customResp, 0, sizeof(customResp));
+ WH_TEST_RETURN_ON_FAIL(wh_Client_CustomCheckRegisteredRequest(
+ t->client, WH_MESSAGE_CUSTOM_CB_TYPE_QUERY));
+ WH_TEST_RETURN_ON_FAIL(_ReplyWith(t, (uint16_t)WH_TEST_MR_OVERSIZED_SZ));
+ ret = wh_Client_CustomCbResponse(t->client, &customResp);
+ WH_TEST_ASSERT_RETURN(ret == WH_ERROR_ABORTED);
+
+ return WH_ERROR_OK;
+}
+
+int whTest_ClientMalformedResp(void* ctx)
+{
+ TestCtx t[1];
+
+ (void)ctx;
+
+ WH_TEST_RETURN_ON_FAIL(_SetupClientServer(t));
+
+ WH_TEST_PRINT("Testing client handling of malformed responses...\n");
+ WH_TEST_RETURN_ON_FAIL(_whTest_MalformedRespFixed(t));
+ WH_TEST_RETURN_ON_FAIL(_whTest_MalformedRespKeyExport(t));
+ WH_TEST_RETURN_ON_FAIL(_whTest_MalformedRespKeyExportPublic(t));
+ WH_TEST_RETURN_ON_FAIL(_whTest_MalformedRespOversized(t));
+
+ _CleanupClientServer(t);
+
+ return WH_ERROR_OK;
+}
+
+#endif /* WOLFHSM_CFG_ENABLE_CLIENT && WOLFHSM_CFG_ENABLE_SERVER */
diff --git a/test-refactor/wh_test_list.c b/test-refactor/wh_test_list.c
index 70f7d0396..6f81042cb 100644
--- a/test-refactor/wh_test_list.c
+++ b/test-refactor/wh_test_list.c
@@ -41,6 +41,7 @@
/* Test declarations and weak skip implementations. */
WH_TEST_DECL(whTest_ClientDevId);
+WH_TEST_DECL(whTest_ClientMalformedResp);
WH_TEST_DECL(whTest_Comm);
WH_TEST_DECL(whTest_Dma);
WH_TEST_DECL(whTest_HwKeystore);
@@ -99,6 +100,7 @@ WH_TEST_DECL(whTest_AuthRequestAuthorization);
const whTestCase whTestsMisc[] = {
{ "whTest_ClientDevId", whTest_ClientDevId },
+ { "whTest_ClientMalformedResp", whTest_ClientMalformedResp },
{ "whTest_Comm", whTest_Comm },
{ "whTest_Dma", whTest_Dma },
{ "whTest_KeystoreReqSize", whTest_KeystoreReqSize },