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 },