Skip to content

Commit 9b926c8

Browse files
Zero DMA destination buffer on server WRITE_PRE
1 parent 2d1b25a commit 9b926c8

8 files changed

Lines changed: 1013 additions & 29 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: 42 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1273,18 +1273,8 @@ int wh_Server_HandleCertRequest(whServerContext* server, uint16_t magic,
12731273
wh_MessageCert_TranslateReadTrustedDmaRequest(
12741274
magic, (whMessageCert_ReadTrustedDmaRequest*)req_packet,
12751275
&req);
1276-
1277-
/* Process client address */
1278-
resp.rc = wh_Server_DmaProcessClientAddress(
1279-
server, req.cert_addr, &cert_data, req.cert_len,
1280-
WH_DMA_OPER_CLIENT_WRITE_PRE, (whServerDmaFlags){0});
1281-
if (resp.rc == WH_ERROR_OK) {
1282-
cert_dma_pre_ok = 1;
1283-
}
12841276
}
12851277
if (resp.rc == WH_ERROR_OK) {
1286-
/* Deny reading non-exportable or server-only (trusted KEK)
1287-
* objects; see the non-DMA path above. */
12881278
resp.rc = WH_SERVER_NVM_LOCK(server);
12891279
if (resp.rc == WH_ERROR_OK) {
12901280
resp.rc = wh_Nvm_GetMetadata(server->nvm, req.id, &meta);
@@ -1293,23 +1283,53 @@ int wh_Server_HandleCertRequest(whServerContext* server, uint16_t magic,
12931283
WH_NVM_FLAGS_SERVER_ONLY)) != 0) {
12941284
resp.rc = WH_ERROR_ACCESS;
12951285
}
1296-
else {
1297-
/* Clamp cert_len to actual stored length */
1298-
cert_len = req.cert_len;
1299-
resp.rc = wh_Server_CertReadTrusted(
1300-
server, req.id, cert_data, &cert_len);
1286+
}
1287+
/* wh_Server_CertReadTrusted() calls the unchecked
1288+
* wh_Nvm_Read(), so the check above is the only gate, and
1289+
* it precedes the bound and the map. */
1290+
if (resp.rc == WH_ERROR_OK &&
1291+
req.cert_len > WOLFHSM_CFG_MAX_CERT_SIZE) {
1292+
resp.rc = WH_ERROR_BADARGS;
1293+
}
1294+
if (resp.rc == WH_ERROR_OK) {
1295+
resp.rc = wh_Server_DmaProcessClientAddress(
1296+
server, req.cert_addr, &cert_data, req.cert_len,
1297+
WH_DMA_OPER_CLIENT_WRITE_PRE,
1298+
(whServerDmaFlags){0});
1299+
/* Zero length is a no-op. A NULL mapping cannot be
1300+
* zeroed, so fail before pairing a POST. */
1301+
if (resp.rc == WH_ERROR_OK && req.cert_len > 0) {
1302+
if (cert_data == NULL) {
1303+
resp.rc = WH_ERROR_BADARGS;
1304+
}
1305+
else {
1306+
cert_dma_pre_ok = 1;
1307+
/* The read fills only meta.len, so zero
1308+
* first. */
1309+
memset(cert_data, 0, req.cert_len);
1310+
}
1311+
}
1312+
else if (resp.rc == WH_ERROR_OK) {
1313+
cert_dma_pre_ok = 1;
13011314
}
13021315
}
1316+
if (resp.rc == WH_ERROR_OK) {
1317+
cert_len = req.cert_len;
1318+
resp.rc = wh_Server_CertReadTrusted(
1319+
server, req.id, cert_data, &cert_len);
1320+
}
13031321

13041322
(void)WH_SERVER_NVM_UNLOCK(server);
13051323
} /* WH_SERVER_NVM_LOCK() */
1306-
}
1307-
/* Always call POST for successful PRE, regardless of operation
1308-
* result */
1309-
if (cert_dma_pre_ok) {
1310-
(void)wh_Server_DmaProcessClientAddress(
1311-
server, req.cert_addr, &cert_data, req.cert_len,
1312-
WH_DMA_OPER_CLIENT_WRITE_POST, (whServerDmaFlags){0});
1324+
1325+
/* Always call POST for successful PRE, regardless of
1326+
* operation result. Runs outside the lock: only the PRE has
1327+
* to be ordered against the deny check. */
1328+
if (cert_dma_pre_ok) {
1329+
(void)wh_Server_DmaProcessClientAddress(
1330+
server, req.cert_addr, &cert_data, req.cert_len,
1331+
WH_DMA_OPER_CLIENT_WRITE_POST, (whServerDmaFlags){0});
1332+
}
13131333
}
13141334

13151335
/* Convert the response struct */

src/wh_server_nvm.c

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

460+
if (rc == 0) {
461+
/* Refuse before the bound and the map: ACCESS whatever the
462+
* offset, so the length cannot be probed. The non-DMA READ
463+
* deliberately keeps its original ordering. */
464+
if ((meta.flags & (WH_NVM_FLAGS_NONEXPORTABLE |
465+
WH_NVM_FLAGS_SERVER_ONLY)) != 0) {
466+
rc = WH_ERROR_ACCESS;
467+
}
468+
}
469+
460470
if (rc == 0) {
461471
if (req.offset >= meta.len) {
462472
rc = WH_ERROR_BADARGS;
@@ -472,14 +482,25 @@ int wh_Server_HandleNvmRequest(whServerContext* server,
472482
}
473483
}
474484

475-
/* use unclamped length for DMA address processing in case DMA
476-
* callbacks are sensible to alignment and/or size */
485+
/* Map the full requested length (callbacks may depend on it);
486+
* the allowlist, not the object size, bounds the extent. */
477487
if (rc == 0) {
478-
/* perform platform-specific host address processing */
479488
rc = wh_Server_DmaProcessClientAddress(
480489
server, req.data_hostaddr, &data, req.data_len,
481490
WH_DMA_OPER_CLIENT_WRITE_PRE, (whServerDmaFlags){0});
482-
if (rc == 0) {
491+
/* Zero it since the read fills only read_len. A NULL
492+
* mapping cannot be zeroed, so fail rather than POST
493+
* un-zeroed bytes; zero length is a no-op. */
494+
if (rc == 0 && req.data_len > 0) {
495+
if (data == NULL) {
496+
rc = WH_ERROR_BADARGS;
497+
}
498+
else {
499+
data_dma_pre_ok = 1;
500+
memset(data, 0, req.data_len);
501+
}
502+
}
503+
else if (rc == 0) {
483504
data_dma_pre_ok = 1;
484505
}
485506
}
@@ -488,8 +509,8 @@ int wh_Server_HandleNvmRequest(whServerContext* server,
488509
rc = wh_Nvm_ReadChecked(server->nvm, req.id, req.offset,
489510
read_len, (uint8_t*)data);
490511
}
491-
/* Always call POST for successful PRE, regardless of read
492-
* result */
512+
/* POST inside the lock, matching this handler's original
513+
* bracket. */
493514
if (data_dma_pre_ok) {
494515
(void)wh_Server_DmaProcessClientAddress(
495516
server, req.data_hostaddr, &data, req.data_len,

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)