|WARNING| [v8] examples: add Wycheproof validation app
dpdklab at iol.unh.edu
dpdklab at iol.unh.edu
Tue Oct 13 18:04:04 CEST 2026
Test-Label: ai-code-review
Test-Status: WARNING
https://dpdk.org/patch/171197
_AI Code Review_
=== Patch Review: series.patch (via Claude) ===
Review date: 2026-10-13
# Wycheproof Validation Example - DPDK Code Review
## ERRORS
**1. Missing error check on `rte_cryptodev_asym_session_create()` (line 1414, 1531, 1803)**
The code checks only if the return value is `< 0` or if `session == NULL`, then treats it as `-ENOTSUP`. However, `rte_cryptodev_asym_session_create()` returns an `int` status code, not a pointer. The `session` parameter is an output pointer that is written to. The pattern should check the return value for error, not compare `session` to NULL as a separate condition.
```c
/* Current (incorrect) */
if (rte_cryptodev_asym_session_create(env.dev_id, &xform, env.asym_session_pool,
&session) < 0 || session == NULL) {
ret = -ENOTSUP;
goto out;
}
/* Correct */
ret = rte_cryptodev_asym_session_create(env.dev_id, &xform, env.asym_session_pool,
&session);
if (ret < 0 || session == NULL) {
ret = -ENOTSUP;
goto out;
}
```
The current pattern discards the return value in the conditional, making `ret = -ENOTSUP;` always execute regardless of what `rte_cryptodev_asym_session_create()` actually returned.
**2. Device selection for hash_dev_id modifies global state but never restores it on error (lines 261-292)**
When searching for a hash-capable device distinct from `env.dev_id`, the code configures, sets up a queue pair, and starts the device at index `d`. If any of these operations fail, the code calls `rte_cryptodev_close(d)` and continues searching. However:
- `rte_cryptodev_configure(d, &config)` was called and may have partially initialized device `d`.
- If `rte_cryptodev_queue_pair_setup()` fails, the device is in a configured-but-not-setup state.
- Calling `rte_cryptodev_close()` without first calling `rte_cryptodev_stop()` if the device was started is incorrect cleanup order.
The cleanup should be:
```c
if (rte_cryptodev_start(d) < 0) {
rte_cryptodev_queue_pair_setup(...); /* may have succeeded */
rte_cryptodev_close(d);
continue;
}
```
should be:
```c
if (rte_cryptodev_start(d) < 0) {
rte_cryptodev_stop(d); /* in case partial start */
rte_cryptodev_close(d);
continue;
}
```
But more importantly: if `rte_cryptodev_configure(d, ...)` succeeds but later steps fail, the device is left in an inconsistent state. The code should call `rte_cryptodev_stop()` before `rte_cryptodev_close()` in the error path.
**3. Potential buffer overflow in CCM AAD setup (line 654)**
```c
aad = rte_zmalloc(NULL,
RTE_ALIGN_CEIL(vector->aad_len + AES_CCM_AAD_OFFSET, 16), 0);
if (aad == NULL)
goto out;
if (vector->aad_len != 0)
memcpy(aad + AES_CCM_AAD_OFFSET, vector->aad, vector->aad_len);
```
The allocation size is `RTE_ALIGN_CEIL(vector->aad_len + 18, 16)`. If `vector->aad_len` is `UINT32_MAX - 17`, then `vector->aad_len + 18` wraps to 1, `RTE_ALIGN_CEIL(1, 16)` becomes 16, but then `memcpy(aad + 18, vector->aad, UINT32_MAX - 17)` writes far beyond the 16-byte allocation.
While `vector->aad_len` is parsed from JSON and unlikely to be that large in practice, there is no explicit bounds check before the addition. Add a check:
```c
if (vector->aad_len > UINT32_MAX - AES_CCM_AAD_OFFSET - 16)
goto unsupported;
```
**4. `normalize_operand()` can be called with overlapping source and destination (lines 1475, 1795)**
In `run_ecdh_ecpoint()` at line 1475:
```c
if (normalize_operand(priv, priv_len, priv_buf, bytesize) != 0)
return -EINVAL;
```
and in `run_ecdsa_verify()` at lines 1795-1796:
```c
if (normalize_operand(group->wx, group->wx_len, wx_buf, group->bytesize) != 0 ||
normalize_operand(group->wy, group->wy_len, wy_buf, group->bytesize) != 0)
```
The function `normalize_operand()` at line 1368 does:
```c
while (value_len > 0 && value[0] == 0) {
value++;
value_len--;
}
...
memcpy(out + bytesize - value_len, value, value_len);
```
If `value` and `out` overlap (e.g., caller passes the same buffer as source and destination), the `memcpy()` has undefined behavior. While the current callers do not pass overlapping buffers, the function does not document or enforce this precondition. Either document the requirement or use `memmove()`.
**5. Wedged device comment claims "op still owned by the PMD" but code returns without freeing session (multiple locations)**
At lines 704, 817, 925, 1056, 1423, 1540, and 1812, the code has:
```c
completed = dequeue_one(env.dev_id);
if (completed == NULL)
/* Device wedged: op still owned by the PMD. Abort without freeing
* in-flight state; teardown stops the device.
*/
return -ETIMEDOUT;
```
However, the `session` was allocated and is never freed on this path. The comment says "without freeing in-flight state" but that applies only to the `op` and `mbuf`, not to the session which is not in-flight. The session should be freed:
```c
if (completed == NULL) {
ret = -ETIMEDOUT;
goto out; /* will free session */
}
```
As written, every wedged-device timeout leaks the session.
---
## WARNINGS
**1. `hash_dev_id` device selected but not validated as capable of all required hash algorithms (lines 261-292)**
The code searches for a device that advertises `RTE_CRYPTO_AUTH_SHA256`, assigns it to `env.hash_dev_id`, and later uses `env.hash_dev_id` to compute digests for DSA and ECDSA which may require SHA-1, SHA-224, SHA-384, or SHA-512 (lines 1196-1210, 1219). The device selected for SHA256 capability may not support all hash algorithms needed by the vectors. The code will fail later with `-ENOTSUP` when attempting to create a session for an unsupported hash, but the device selection logic should either:
- Check for a broader set of hash algorithms, or
- Document that the hash device is selected opportunistically and may not support all vectors.
**2. Release notes claim "covering AEAD, MAC, DSA, ECDH and ECDSA" but the example skips many algorithm variants (doc/guides/rel_notes/release_26_11.rst line 5)**
The release notes say the example "covers" these families, but the code explicitly skips AES-CBC, AES-CTR, SEED, SM4-CCM, RSA, and many DSA/ECDH/ECDSA encoding formats (process_file() lines 2046-2050). "Covers" implies comprehensive support; "validates selected algorithms from" would be more accurate. This is a documentation clarity issue, not a code bug.
**3. `dequeue_one()` timeout is 1 second, may be too short for slow software crypto (line 550)**
```c
uint64_t deadline = rte_get_timer_cycles() + rte_get_timer_hz();
```
On systems with `rte_get_timer_hz()` returning cycles-per-second, this is a 1-second timeout. For a software cryptodev on a heavily loaded system, or for large asymmetric operations (RSA/DSA), 1 second may be insufficient. Consider making the timeout configurable or increasing it to 5-10 seconds for asymmetric operations.
**4. `env.mbuf_data_room` defaults to 16384 but vectors are skipped if message exceeds `env.mbuf_data_room - RTE_PKTMBUF_HEADROOM` (lines 887, 1192)**
The default 16KB data room is reasonable, but the skip condition `vector->msg_len > env.mbuf_data_room - RTE_PKTMBUF_HEADROOM` means messages slightly under 16KB (accounting for headroom) will be skipped even though they are valid test cases. The skip message says "message length exceeds mbuf" but this is a limitation of the tool's default configuration, not the vector. Consider either increasing the default or making the skip debug message say "message length exceeds configured mbuf data room".
**5. Application does not set `enable_drivers=crypto/openssl` in documented meson command but then requires it (doc/guides/sample_app_ug/wycheproof_validation.rst line 25)**
The build command shows:
```
meson setup build -Dexamples=crypto_validation/wycheproof_validation \
-Denable_drivers=crypto/openssl
```
but then the run command also uses `--vdev crypto_openssl`. If `enable_drivers` is not set during build, the `crypto_openssl` PMD will not be built, and the vdev will fail at runtime. The documentation should clarify that `-Denable_drivers` is required to build the PMD, or omit it and explain how to ensure a crypto PMD is available.
**6. `parse_uint32()` does not distinguish "0" from invalid input when `errno` is already non-zero (line 100)**
```c
errno = 0;
parsed = strtoul(text, &end, 10);
if (errno != 0 || *text == '\0' || *end != '\0' || parsed > UINT32_MAX)
return -EINVAL;
```
If `errno` is non-zero before the call (leftover from a previous operation), `strtoul()` does not clear it, and valid input "0" will be rejected. The pattern is correct (setting `errno = 0` before the call), but the comment or a more defensive check would clarify this. This is borderline; the code is correct as written.
---
## INFO
**1. `struct app_env` field `hash_dev_id` and `hash_dev_own` are not freed if `app_init()` fails after hash device allocation (pre-existing pattern, not introduced by this patch)**
If `app_init()` fails after the hash device is selected and started (line 292), but before the function completes, `app_uninit()` will not be called, and the hash device will not be stopped or closed. This is a pre-existing pattern in DPDK examples (setup functions that fail partway through), but it means a failure in the main body of `app_init()` after line 292 will leave the hash device running.
The fix would be to add cleanup in the `error:` label:
```c
error:
if (env.hash_dev_own) {
rte_cryptodev_stop(env.hash_dev_id);
rte_cryptodev_close(env.hash_dev_id);
}
rte_mempool_free(env.asym_op_pool);
/* ... rest of cleanup ... */
```
**2. `decode_hex()` allocates with `rte_malloc()` but no alignment requirement is specified (line 381)**
```c
decoded = rte_malloc(NULL, *length, 0);
```
The third parameter is alignment; `0` means default (which is cache-line on most platforms). For buffers that will be DMA'd to hardware (keys, IVs, messages), cache-line alignment is appropriate. For temporary buffers that are only CPU-accessed, `malloc()` would suffice and consume less hugepage memory. See the "Appropriate Use of rte_malloc()" section in AGENTS.md. This is an Info note because the current usage is not incorrect, just heavier than necessary.
**3. Pre-existing: MAINTAINERS diff shows FIPS validation files moved but the MAINTAINERS entry for "FIPS validation example" still points to `examples/fips_validation/` (MAINTAINERS line 2029)**
The files were moved to `examples/crypto_validation/fips_validation/` but the MAINTAINERS entry was not updated to match. The correct path after this patch should be:
```
F: examples/crypto_validation/fips_validation/
```
This is a minor consistency issue, not a functional bug.
---
Review-Result: ERROR
More information about the test-report
mailing list