Skip to content

Commit 64a1274

Browse files
Zero DMA destination buffer on server WRITE_PRE
1 parent a288dc4 commit 64a1274

8 files changed

Lines changed: 383 additions & 31 deletions

File tree

docs/src/5-Features.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -630,6 +630,8 @@ It is called twice per access — once before, once after — with the `oper` ar
630630

631631
If no callback is registered, the server uses the client address directly as `*serverPtr`, which is the right behavior for a system with a flat shared address space and coherent caches. Ports that need either address translation or cache maintenance supply a callback that handles both; the callback is the single extension point for both concerns.
632632

633+
**The callback must not call into any wolfHSM NVM API.** The NVM and certificate DMA *read* handlers invoke the `WH_DMA_OPER_CLIENT_WRITE_PRE` phase while holding the server's NVM lock, so that a request for an object the client is not permitted to read is refused before any client memory is mapped. That lock is not recursive: a callback that re-enters `wh_Nvm_*` deadlocks under `WOLFHSM_CFG_THREADSAFE`. A port that needs NVM-resident data for address translation — an allowlist or mapping table stored as an NVM object, for example — must read it once at initialization and cache it, rather than looking it up inside the callback. Treat the restriction as applying to every phase: which phases run under the lock is an internal detail and may change.
634+
633635
For platforms where the client buffer is not directly memcpy-able even after address translation — for example, when the only path to client memory is through a hardware FIFO or register window — wolfHSM additionally exposes a `whServerDmaMemCopyCb` callback under `WOLFHSM_CFG_DMA_CUSTOM_CLIENT_COPY`. When registered (via `wh_Server_DmaRegisterMemCopyCb`), this callback replaces the internal `memcpy` between server and client memory entirely, and is the only operation that touches the client side of the transfer.
634636

635637
The same PRE/POST callback model is also available on the **client side** through `wh_Client_DmaRegisterCb`, with an identical `whClientDmaClientMemCb` signature. The client callback is invoked before the request is sent and after the response is received, and is the right place for any work that has to happen in the client's address space before the server is ever told about a buffer — pinning pages, flushing the client's view of a cache line, or substituting the application's pointer with one that lives in a region the server can actually reach. The POSIX shared-memory transport illustrates the last case: an application buffer allocated from the process's ordinary heap is not visible to the server because it lies outside the mapped shared-memory segment, so the transport's client callback (`posixTransportShm_ClientStaticMemDmaCallback`) detects that the supplied address falls outside the DMA region, allocates a bounce buffer inside the shared segment on `*_READ_PRE`/`*_WRITE_PRE`, copies the application data into it for the read direction, and reports the in-segment offset as the address the server should use. The matching POST phase copies any server-written bytes back to the original application buffer and frees the bounce buffer. From the application's perspective the original wolfCrypt call is unchanged; the client callback transparently bridges the gap between the application's address space and the address space the server can address. Client-side and server-side callbacks are independent — a port may register either, both, or neither, depending on which side needs the translation.

src/wh_server_cert.c

Lines changed: 45 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1286,18 +1286,8 @@ int wh_Server_HandleCertRequest(whServerContext* server, uint16_t magic,
12861286
wh_MessageCert_TranslateReadTrustedDmaRequest(
12871287
magic, (whMessageCert_ReadTrustedDmaRequest*)req_packet,
12881288
&req);
1289-
1290-
/* Process client address */
1291-
resp.rc = wh_Server_DmaProcessClientAddress(
1292-
server, req.cert_addr, &cert_data, req.cert_len,
1293-
WH_DMA_OPER_CLIENT_WRITE_PRE, (whServerDmaFlags){0});
1294-
if (resp.rc == WH_ERROR_OK) {
1295-
cert_dma_pre_ok = 1;
1296-
}
12971289
}
12981290
if (resp.rc == WH_ERROR_OK) {
1299-
/* Deny reading non-exportable or server-only (trusted KEK)
1300-
* objects; see the non-DMA path above. */
13011291
resp.rc = WH_SERVER_NVM_LOCK(server);
13021292
if (resp.rc == WH_ERROR_OK) {
13031293
resp.rc = wh_Nvm_GetMetadata(server->nvm, req.id, &meta);
@@ -1306,25 +1296,56 @@ int wh_Server_HandleCertRequest(whServerContext* server, uint16_t magic,
13061296
WH_NVM_FLAGS_SERVER_ONLY)) != 0) {
13071297
resp.rc = WH_ERROR_ACCESS;
13081298
}
1309-
else {
1310-
/* The callee reports the stored length back into
1311-
* cert_len, but SimpleResponse has no field to
1312-
* return it, so the client cannot learn it here */
1313-
cert_len = req.cert_len;
1314-
resp.rc = wh_Server_CertReadTrusted(
1315-
server, req.id, cert_data, &cert_len);
1299+
}
1300+
/* wh_Server_CertReadTrusted() calls the unchecked
1301+
* wh_Nvm_Read(), so the check above is the only gate, and
1302+
* it precedes the bound and the map. */
1303+
if (resp.rc == WH_ERROR_OK &&
1304+
req.cert_len > WOLFHSM_CFG_MAX_CERT_SIZE) {
1305+
resp.rc = WH_ERROR_BADARGS;
1306+
}
1307+
if (resp.rc == WH_ERROR_OK) {
1308+
resp.rc = wh_Server_DmaProcessClientAddress(
1309+
server, req.cert_addr, &cert_data, req.cert_len,
1310+
WH_DMA_OPER_CLIENT_WRITE_PRE,
1311+
(whServerDmaFlags){0});
1312+
/* Zero length is a no-op. A NULL mapping cannot be
1313+
* zeroed, so fail before pairing a POST. */
1314+
if (resp.rc == WH_ERROR_OK && req.cert_len > 0) {
1315+
if (cert_data == NULL) {
1316+
resp.rc = WH_ERROR_BADARGS;
1317+
}
1318+
else {
1319+
cert_dma_pre_ok = 1;
1320+
/* The read fills only meta.len, so zero
1321+
* first. */
1322+
memset(cert_data, 0, req.cert_len);
1323+
}
1324+
}
1325+
else if (resp.rc == WH_ERROR_OK) {
1326+
cert_dma_pre_ok = 1;
13161327
}
13171328
}
1329+
if (resp.rc == WH_ERROR_OK) {
1330+
/* The callee reports the stored length back into
1331+
* cert_len, but SimpleResponse has no field to
1332+
* return it, so the client cannot learn it here */
1333+
cert_len = req.cert_len;
1334+
resp.rc = wh_Server_CertReadTrusted(
1335+
server, req.id, cert_data, &cert_len);
1336+
}
13181337

13191338
(void)WH_SERVER_NVM_UNLOCK(server);
13201339
} /* WH_SERVER_NVM_LOCK() */
1321-
}
1322-
/* Always call POST for successful PRE, regardless of operation
1323-
* result */
1324-
if (cert_dma_pre_ok) {
1325-
(void)wh_Server_DmaProcessClientAddress(
1326-
server, req.cert_addr, &cert_data, req.cert_len,
1327-
WH_DMA_OPER_CLIENT_WRITE_POST, (whServerDmaFlags){0});
1340+
1341+
/* Always call POST for successful PRE, regardless of
1342+
* operation result. Runs outside the lock: only the PRE has
1343+
* to be ordered against the deny check. */
1344+
if (cert_dma_pre_ok) {
1345+
(void)wh_Server_DmaProcessClientAddress(
1346+
server, req.cert_addr, &cert_data, req.cert_len,
1347+
WH_DMA_OPER_CLIENT_WRITE_POST, (whServerDmaFlags){0});
1348+
}
13281349
}
13291350

13301351
/* Convert the response struct */

src/wh_server_nvm.c

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -464,6 +464,16 @@ int wh_Server_HandleNvmRequest(whServerContext* server,
464464
if (rc == WH_ERROR_OK) {
465465
rc = wh_Nvm_GetMetadata(server->nvm, req.id, &meta);
466466

467+
if (rc == 0) {
468+
/* Refuse before the bound and the map: ACCESS whatever the
469+
* offset, so the length cannot be probed. The non-DMA READ
470+
* deliberately keeps its original ordering. */
471+
if ((meta.flags & (WH_NVM_FLAGS_NONEXPORTABLE |
472+
WH_NVM_FLAGS_SERVER_ONLY)) != 0) {
473+
rc = WH_ERROR_ACCESS;
474+
}
475+
}
476+
467477
if (rc == 0) {
468478
if (req.offset >= meta.len) {
469479
rc = WH_ERROR_BADARGS;
@@ -479,14 +489,25 @@ int wh_Server_HandleNvmRequest(whServerContext* server,
479489
}
480490
}
481491

482-
/* use unclamped length for DMA address processing in case DMA
483-
* callbacks are sensible to alignment and/or size */
492+
/* Map the full requested length (callbacks may depend on it);
493+
* the allowlist, not the object size, bounds the extent. */
484494
if (rc == 0) {
485-
/* perform platform-specific host address processing */
486495
rc = wh_Server_DmaProcessClientAddress(
487496
server, req.data_hostaddr, &data, req.data_len,
488497
WH_DMA_OPER_CLIENT_WRITE_PRE, (whServerDmaFlags){0});
489-
if (rc == 0) {
498+
/* Zero it since the read fills only read_len. A NULL
499+
* mapping cannot be zeroed, so fail rather than POST
500+
* un-zeroed bytes; zero length is a no-op. */
501+
if (rc == 0 && req.data_len > 0) {
502+
if (data == NULL) {
503+
rc = WH_ERROR_BADARGS;
504+
}
505+
else {
506+
data_dma_pre_ok = 1;
507+
memset(data, 0, req.data_len);
508+
}
509+
}
510+
else if (rc == 0) {
490511
data_dma_pre_ok = 1;
491512
}
492513
}
@@ -495,8 +516,8 @@ int wh_Server_HandleNvmRequest(whServerContext* server,
495516
rc = wh_Nvm_ReadChecked(server->nvm, req.id, req.offset,
496517
read_len, (uint8_t*)data);
497518
}
498-
/* Always call POST for successful PRE, regardless of read
499-
* result */
519+
/* POST inside the lock, matching this handler's original
520+
* bracket. */
500521
if (data_dma_pre_ok) {
501522
(void)wh_Server_DmaProcessClientAddress(
502523
server, req.data_hostaddr, &data, req.data_len,

test-refactor/client-server/wh_test_client_certs.c

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929
&& !defined(WOLFHSM_CFG_NO_CRYPTO)
3030

3131
#include <stdint.h>
32+
#include <stddef.h>
33+
#include <string.h>
3234

3335
#include "wolfhsm/wh_error.h"
3436
#include "wolfhsm/wh_client.h"
@@ -83,9 +85,67 @@ static int _whTest_CertReadTrustedSmallBuffer(whClientContext* ctx)
8385
}
8486

8587

88+
#ifdef WOLFHSM_CFG_DMA
89+
90+
#define CERT_DMA_POISON ((uint8_t)0xA7)
91+
92+
/* File-scope so an oversized request that the server wrongly accepts writes
93+
* into this instead of smashing the sub-test's frame. */
94+
static uint8_t _certDmaOversize[WOLFHSM_CFG_MAX_CERT_SIZE + 1];
95+
96+
97+
/* The server writes the whole requested length once it maps the buffer, so
98+
* the tail past a short certificate must be zeros. A length beyond
99+
* WOLFHSM_CFG_MAX_CERT_SIZE is refused before any mapping happens. */
100+
static int _whTest_CertReadTrustedDmaTailZeroed(whClientContext* ctx)
101+
{
102+
int32_t out_rc = 0;
103+
const whNvmId cert_id = 104;
104+
uint8_t dma_buf[2048];
105+
size_t i;
106+
107+
WH_TEST_ASSERT_RETURN(ROOT_A_CERT_len < sizeof(dma_buf));
108+
109+
WH_TEST_RETURN_ON_FAIL(wh_Client_CertAddTrusted(
110+
ctx, cert_id, WH_NVM_ACCESS_ANY, WH_NVM_FLAGS_NONMODIFIABLE,
111+
NULL, 0, ROOT_A_CERT, ROOT_A_CERT_len, &out_rc));
112+
WH_TEST_ASSERT_RETURN(out_rc == WH_ERROR_OK);
113+
114+
memset(dma_buf, CERT_DMA_POISON, sizeof(dma_buf));
115+
WH_TEST_RETURN_ON_FAIL(wh_Client_CertReadTrustedDma(
116+
ctx, cert_id, dma_buf, sizeof(dma_buf), &out_rc));
117+
WH_TEST_ASSERT_RETURN(out_rc == WH_ERROR_OK);
118+
119+
WH_TEST_ASSERT_RETURN(0 == memcmp(dma_buf, ROOT_A_CERT, ROOT_A_CERT_len));
120+
for (i = ROOT_A_CERT_len; i < sizeof(dma_buf); i++) {
121+
WH_TEST_ASSERT_RETURN(dma_buf[i] == 0);
122+
}
123+
124+
/* Past the cap: refused before the map, so the buffer keeps its fill. */
125+
memset(_certDmaOversize, CERT_DMA_POISON, sizeof(_certDmaOversize));
126+
WH_TEST_RETURN_ON_FAIL(wh_Client_CertReadTrustedDma(
127+
ctx, cert_id, _certDmaOversize, sizeof(_certDmaOversize), &out_rc));
128+
WH_TEST_ASSERT_RETURN(out_rc == WH_ERROR_BADARGS);
129+
for (i = 0; i < sizeof(_certDmaOversize); i++) {
130+
WH_TEST_ASSERT_RETURN(_certDmaOversize[i] == CERT_DMA_POISON);
131+
}
132+
133+
WH_TEST_RETURN_ON_FAIL(
134+
wh_Client_CertEraseTrusted(ctx, cert_id, &out_rc));
135+
WH_TEST_ASSERT_RETURN(out_rc == WH_ERROR_OK);
136+
137+
return WH_ERROR_OK;
138+
}
139+
140+
#endif /* WOLFHSM_CFG_DMA */
141+
142+
86143
int whTest_ClientCerts(whClientContext* ctx)
87144
{
88145
WH_TEST_RETURN_ON_FAIL(_whTest_CertReadTrustedSmallBuffer(ctx));
146+
#ifdef WOLFHSM_CFG_DMA
147+
WH_TEST_RETURN_ON_FAIL(_whTest_CertReadTrustedDmaTailZeroed(ctx));
148+
#endif
89149

90150
return WH_ERROR_OK;
91151
}

test-refactor/client-server/wh_test_crypto_aes.c

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1102,6 +1102,148 @@ int whTest_CryptoAesKeyUsagePolicies(whClientContext* ctx)
11021102
return ret;
11031103
}
11041104

1105+
#if defined(WOLFHSM_CFG_DMA) && \
1106+
(defined(HAVE_AES_CBC) || defined(WOLFSSL_AES_COUNTER) || \
1107+
defined(HAVE_AES_ECB))
1108+
/* One buffer as both input and output must match out-of-place. Also fails if
1109+
* a zero-fill moves into wh_Server_DmaProcessClientAddress(): in and out map
1110+
* to one address here, so a central memset would erase the plaintext. */
1111+
static int whTest_CryptoAesDmaInPlace(whClientContext* ctx)
1112+
{
1113+
/* The wh_Client_Aes*Dma entry points are called directly, so the devId
1114+
* only has to be a valid one -- it does not select the DMA path here. */
1115+
int devId = WH_CLIENT_DEVID(ctx);
1116+
int ret = 0;
1117+
Aes aes[1];
1118+
uint8_t inplace[AES_BLOCK_SIZE * 2];
1119+
uint8_t refcipher[AES_BLOCK_SIZE * 2];
1120+
const uint8_t key[] = {0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6,
1121+
0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c};
1122+
#if defined(HAVE_AES_CBC) || defined(WOLFSSL_AES_COUNTER)
1123+
const uint8_t iv[AES_BLOCK_SIZE] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05,
1124+
0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b,
1125+
0x0c, 0x0d, 0x0e, 0x0f};
1126+
#endif
1127+
const uint8_t plainIn[AES_BLOCK_SIZE * 2] = {
1128+
0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96, 0xe9, 0x3d, 0x7e,
1129+
0x11, 0x73, 0x93, 0x17, 0x2a, 0xae, 0x2d, 0x8a, 0x57, 0x1e, 0x03,
1130+
0xac, 0x9c, 0x9e, 0xb7, 0x6f, 0xac, 0x45, 0xaf, 0x8e, 0x51};
1131+
1132+
#ifdef HAVE_AES_CBC
1133+
if (ret == 0) {
1134+
ret = wc_AesInit(aes, NULL, devId);
1135+
if (ret == 0) {
1136+
ret = wc_AesSetKey(aes, key, sizeof(key), iv, AES_ENCRYPTION);
1137+
if (ret == 0) {
1138+
ret = wh_Client_AesCbcDma(ctx, aes, 1, plainIn, sizeof(plainIn),
1139+
refcipher);
1140+
}
1141+
(void)wc_AesFree(aes);
1142+
}
1143+
}
1144+
if (ret == 0) {
1145+
ret = wc_AesInit(aes, NULL, devId);
1146+
if (ret == 0) {
1147+
ret = wc_AesSetKey(aes, key, sizeof(key), iv, AES_ENCRYPTION);
1148+
if (ret == 0) {
1149+
memcpy(inplace, plainIn, sizeof(plainIn));
1150+
ret = wh_Client_AesCbcDma(ctx, aes, 1, inplace, sizeof(inplace),
1151+
inplace);
1152+
}
1153+
(void)wc_AesFree(aes);
1154+
}
1155+
}
1156+
if (ret == 0 && memcmp(inplace, refcipher, sizeof(refcipher)) != 0) {
1157+
WH_ERROR_PRINT("AES-CBC DMA in-place != out-of-place\n");
1158+
ret = -1;
1159+
}
1160+
/* Decrypt is the harder in-place direction: the recovered plaintext below
1161+
* proves each ciphertext block survived to serve as the next block's IV. */
1162+
if (ret == 0) {
1163+
ret = wc_AesInit(aes, NULL, devId);
1164+
if (ret == 0) {
1165+
ret = wc_AesSetKey(aes, key, sizeof(key), iv, AES_DECRYPTION);
1166+
if (ret == 0) {
1167+
memcpy(inplace, refcipher, sizeof(refcipher));
1168+
ret = wh_Client_AesCbcDma(ctx, aes, 0, inplace, sizeof(inplace),
1169+
inplace);
1170+
}
1171+
(void)wc_AesFree(aes);
1172+
}
1173+
}
1174+
if (ret == 0 && memcmp(inplace, plainIn, sizeof(plainIn)) != 0) {
1175+
WH_ERROR_PRINT("AES-CBC DMA in-place decrypt != plaintext\n");
1176+
ret = -1;
1177+
}
1178+
#endif /* HAVE_AES_CBC */
1179+
1180+
#ifdef WOLFSSL_AES_COUNTER
1181+
if (ret == 0) {
1182+
ret = wc_AesInit(aes, NULL, devId);
1183+
if (ret == 0) {
1184+
ret = wc_AesSetKeyDirect(aes, key, sizeof(key), iv, AES_ENCRYPTION);
1185+
if (ret == 0) {
1186+
ret = wh_Client_AesCtrDma(ctx, aes, 1, plainIn, sizeof(plainIn),
1187+
refcipher);
1188+
}
1189+
(void)wc_AesFree(aes);
1190+
}
1191+
}
1192+
if (ret == 0) {
1193+
ret = wc_AesInit(aes, NULL, devId);
1194+
if (ret == 0) {
1195+
ret = wc_AesSetKeyDirect(aes, key, sizeof(key), iv, AES_ENCRYPTION);
1196+
if (ret == 0) {
1197+
memcpy(inplace, plainIn, sizeof(plainIn));
1198+
ret = wh_Client_AesCtrDma(ctx, aes, 1, inplace, sizeof(inplace),
1199+
inplace);
1200+
}
1201+
(void)wc_AesFree(aes);
1202+
}
1203+
}
1204+
if (ret == 0 && memcmp(inplace, refcipher, sizeof(refcipher)) != 0) {
1205+
WH_ERROR_PRINT("AES-CTR DMA in-place != out-of-place\n");
1206+
ret = -1;
1207+
}
1208+
#endif /* WOLFSSL_AES_COUNTER */
1209+
1210+
#ifdef HAVE_AES_ECB
1211+
if (ret == 0) {
1212+
ret = wc_AesInit(aes, NULL, devId);
1213+
if (ret == 0) {
1214+
ret = wc_AesSetKey(aes, key, sizeof(key), NULL, AES_ENCRYPTION);
1215+
if (ret == 0) {
1216+
ret = wh_Client_AesEcbDma(ctx, aes, 1, plainIn, sizeof(plainIn),
1217+
refcipher);
1218+
}
1219+
(void)wc_AesFree(aes);
1220+
}
1221+
}
1222+
if (ret == 0) {
1223+
ret = wc_AesInit(aes, NULL, devId);
1224+
if (ret == 0) {
1225+
ret = wc_AesSetKey(aes, key, sizeof(key), NULL, AES_ENCRYPTION);
1226+
if (ret == 0) {
1227+
memcpy(inplace, plainIn, sizeof(plainIn));
1228+
ret = wh_Client_AesEcbDma(ctx, aes, 1, inplace, sizeof(inplace),
1229+
inplace);
1230+
}
1231+
(void)wc_AesFree(aes);
1232+
}
1233+
}
1234+
if (ret == 0 && memcmp(inplace, refcipher, sizeof(refcipher)) != 0) {
1235+
WH_ERROR_PRINT("AES-ECB DMA in-place != out-of-place\n");
1236+
ret = -1;
1237+
}
1238+
#endif /* HAVE_AES_ECB */
1239+
1240+
if (ret == 0) {
1241+
WH_TEST_PRINT("AES DMA in-place aliasing DEVID=0x%X SUCCESS\n", devId);
1242+
}
1243+
return ret;
1244+
}
1245+
#endif /* WOLFHSM_CFG_DMA && AES (CBC|CTR|ECB) */
1246+
11051247
int whTest_Crypto_Aes(whClientContext* ctx)
11061248
{
11071249
int i;
@@ -1123,6 +1265,11 @@ int whTest_Crypto_Aes(whClientContext* ctx)
11231265
#endif
11241266
#ifdef WOLFSSL_AES_COUNTER
11251267
WH_TEST_RETURN_ON_FAIL(whTest_CryptoAesCtrLeftOob(ctx));
1268+
#endif
1269+
#if defined(WOLFHSM_CFG_DMA) && \
1270+
(defined(HAVE_AES_CBC) || defined(WOLFSSL_AES_COUNTER) || \
1271+
defined(HAVE_AES_ECB))
1272+
WH_TEST_RETURN_ON_FAIL(whTest_CryptoAesDmaInPlace(ctx));
11261273
#endif
11271274
/* TODO: port legacy AES async + DMA-async coverage (comm-buffer & DMA, round-trip & KAT) -- the only remaining legacy crypto parity gap; deferred to follow-up PR. */
11281275
return 0;

0 commit comments

Comments
 (0)