|WARNING| [v4] examples: add Wycheproof validation app
dpdklab at iol.unh.edu
dpdklab at iol.unh.edu
Tue Sep 22 17:33:22 CEST 2026
Test-Label: ai-code-review
Test-Status: WARNING
https://dpdk.org/patch/169932
_AI Code Review_
=== Patch Review: series.patch (via Claude) ===
Review date: 2026-09-22
# DPDK Patch Review: Wycheproof Validation Example
## Summary
This patch adds a new example application for validating cryptodev PMDs against Google Wycheproof test vectors. The review identifies several **correctness bugs** related to resource management, error handling, and memory operations, along with style and documentation issues.
---
## ERRORS (Must Fix)
### Correctness Bugs
1. **Resource leak on error path in `run_aead()`** (line 595-653)
- When `rte_cryptodev_enqueue_burst()` fails, the function sets `ret = -EIO` and jumps to `out:`, but `operation_op` is still owned by the failed enqueue and should not be freed.
- However, the code frees it anyway at `out:` via `rte_crypto_op_free(operation_op)`.
- **Fix**: Set `operation_op = NULL` after a failed enqueue so the cleanup path skips the free:
```c
if (rte_cryptodev_enqueue_burst(env.dev_id, 0, &operation_op, 1) != 1) {
ret = -EIO;
operation_op = NULL; /* op ownership unclear after enqueue failure */
goto out;
}
```
- Same pattern in `run_hmac()`, `run_gmac()`, `compute_hash()`, `run_dsa_verify()`, `run_ecdh_ecpoint()`, `run_ecdsa_verify()`.
2. **Missing error check on `rte_pktmbuf_append()` in multiple functions**
- `rte_pktmbuf_append()` returns `NULL` if there is insufficient tailroom, but the code unconditionally dereferences the result with `memcpy()`.
- Locations: `run_aead()` line 621, `run_hmac()` line 713, `run_gmac()` line 774, `compute_hash()` line 1128.
- **Fix**: Check the return value before `memcpy()`:
```c
uint8_t *data = rte_pktmbuf_append(mbuf, input_len);
if (data == NULL) {
ret = -EMSGSIZE;
goto out;
}
memcpy(data, input, input_len);
```
3. **Memory allocation not checked for alignment requirement in `run_aead()`** (line 633)
- `*digest = rte_malloc(NULL, vector->tag_len, RTE_CACHE_LINE_SIZE);`
- `rte_malloc()` can return `NULL` on failure, but the code only checks `*digest == NULL && vector->tag_len != 0`.
- When `tag_len == 0`, the code proceeds without allocating, then uses `*digest` at line 651 (`sym_op->aead.digest.data = *digest;`), passing uninitialized pointer to PMD.
- **Fix**: Initialize `*digest = NULL` at declaration and verify it is either `NULL` (when `tag_len == 0`) or valid allocated memory before use.
4. **Use-after-free potential in asymmetric session cleanup**
- In `run_dsa_verify()`, `run_ecdh_ecpoint()`, and `run_ecdsa_verify()`, the code calls `rte_cryptodev_asym_session_free(env.dev_id, session)` at the `out:` label.
- However, if `rte_cryptodev_asym_session_create()` returns `< 0` but `session != NULL` (partial initialization), the free may access stale session data.
- **Fix**: Check `session != NULL` before calling `asym_session_free()`, or ensure the create API guarantees `session` is `NULL` on failure.
5. **Missing `closedir()` on early return in `process_path()`** (line 1948)
- If `snprintf()` fails the length check, the function returns `-ENAMETOOLONG` without closing `directory`.
- **Fix**:
```c
if (snprintf(...) >= (int)sizeof(file_path)) {
ret = -ENAMETOOLONG;
closedir(directory);
return ret;
}
```
6. **Session-create failure treated as PMD failure instead of capability check** (multiple locations)
- In `run_ecdh_ecpoint()` line 1584 and `run_ecdsa_verify()` line 1807, after the capability check passes, the code treats a `session_create()` failure as `-EIO` (PMD failure).
- This causes the tool to report false failures when the PMD legitimately lacks support for specific parameters.
- **Fix**: Return `-ENOTSUP` instead of `-EIO` so the caller can skip the vector appropriately.
7. **Integer overflow in signature length check** (line 1331)
- `if ((sig_len & 1) != 0 || sig_len == 0)` rejects odd-length or zero-length signatures, but does not check for overflow when dividing: `sig_len / 2` is used later without verifying `sig_len <= UINT32_MAX / 2`.
- If `sig_len` is near `UINT32_MAX`, the division could wrap.
- **Fix**: Add explicit check: `if (sig_len == 0 || (sig_len & 1) != 0 || sig_len > UINT32_MAX - 1) { ... }` (though in practice `sig_len` is bounded by decode_hex buffer size, making this low-risk).
8. **`memcmp()` used for digest comparison (timing side channel)** (line 896)
- `memcmp(digest, vector.tag, vector.tag_len)` in `process_hmac()` leaks timing information about how many prefix bytes of the tag match.
- For MAC verification, use `rte_memeq_timingsafe()` to prevent timing attacks.
- **Fix**:
```c
if (!rte_memeq_timingsafe(digest, vector.tag, vector.tag_len)) {
debug_vector("FAIL", name, &vector, "generated tag mismatch");
stats->failed++;
}
```
- Same issue in `validate_aead_vector()` lines 822-823, `process_gmac()` line 953.
---
## WARNINGS (Should Fix)
### Release Notes and Documentation
1. **Release notes entry incomplete**
- The release notes mention "AEAD, MAC, DSA, ECDH and ECDSA algorithm families" but do not list the specific algorithms (AES-GCM, AES-CCM, ChaCha20-Poly1305, HMAC-SHA*, AES-CMAC, AES-GMAC, DSA P1363, ECDH ecpoint, ECDSA P1363).
- **Suggestion**: Expand the list to match the commit message detail, or state "covering commonly-used AEAD, MAC, and asymmetric algorithms."
2. **Documentation does not explain exit code semantics**
- The doc states "exit code is nonzero when a parsed and supported vector fails validation" but does not clarify what happens when all vectors are skipped (e.g., PMD supports none of the algorithms in the file).
- **Suggestion**: Add a note in `wycheproof_validation.rst` explaining that success means all *supported* vectors passed, not that all vectors were executed.
### Style and Formatting
3. **`bool` preferred over `int` for `have_device` flag** (line 132)
- The variable `have_device` is only ever assigned `true` or checked in boolean context, but is declared as `int` in older code style.
- Per the guidelines, this should be `bool` for clarity.
4. **Incorrect use of `rte_malloc()` for general allocations** (line 373, 382, etc.)
- The patch allocates multiple small structures (`vector->key`, `vector->iv`, `decoded`) with `rte_malloc()`, which allocates from hugepage memory.
- Hugepage memory should only be used for DMA-visible buffers or shared memory.
- **Suggestion**: Use `malloc()` for non-DMA vector data (`vector->key`, `vector->iv`, `vector->aad`, `vector->msg`, `vector->ct`, `vector->tag`). Only use `rte_malloc()` for PMD-visible buffers (`*digest`, `aad` in CCM, asymmetric operation fields).
5. **`volatile` would be incorrect if inter-thread access were added**
- The current code has no inter-thread shared state, but if future changes add polling threads, developers might incorrectly use `volatile` for `stats` counters.
- This is not a current error, but the review notes that atomics would be required if threading were introduced.
6. **Missing session pool sizing comment is unclear** (line 207)
- The comment "The hashing device may differ from the target device, so size the session pool for the largest sym session across all devices" is placed inside a block scope with a loop variable `d`.
- The scope is unnecessary and the comment could be clearer.
- **Suggestion**: Move comment outside the block and clarify: "Session pool must accommodate the largest session size across all devices (target device plus optional separate hash device for DSA/ECDSA)."
---
## INFO (Consider)
### Alternative Approaches
1. **`dequeue_one()` timeout could be configurable**
- The 1-second timeout in `dequeue_one()` is hardcoded. For hardware devices with high latency, this might be too short; for software devices, it might be too long.
- **Consideration**: Add a `--timeout` option or document the timeout value in the usage guide.
2. **Asymmetric session creation error messages**
- When `rte_cryptodev_asym_session_create()` fails after the capability check passes, the error is silent (returned as `-EIO` or `-ENOMEM`).
- **Consideration**: Add a debug message explaining the failure to aid PMD developers.
3. **Path traversal in `process_path()` is basic**
- The code only processes `.json` files in the top-level directory; it does not recurse into subdirectories.
- Wycheproof vectors are typically organized in subdirectories (e.g., `aes_gcm_test.json` in `testvectors/`).
- **Consideration**: Document this limitation or add recursive directory traversal.
4. **`memset(vector, 0, sizeof(*vector))` in `parse_vector()`**
- The vector is zeroed before parsing, but if parsing fails partway through (e.g., `decode_hex()` fails on the 3rd field), the cleanup at `error:` calls `free_vector()` which attempts to free uninitialized pointers.
- This is actually correct because `rte_free(NULL)` is safe, but a comment explaining this would improve readability.
---
## Summary of Key Findings
**Correctness bugs (highest priority):**
- Resource leaks on enqueue failure (multiple functions)
- Missing NULL checks on `rte_pktmbuf_append()`
- Timing side channel in digest comparison (use `rte_memeq_timingsafe()`)
- Missing `closedir()` on error path
- Session-create failure mishandled as PMD error instead of capability skip
**Style issues:**
- Inappropriate use of `rte_malloc()` for non-DMA data
- `bool` preferred over `int` for boolean flags
**Documentation:**
- Release notes could be more specific
- Exit code semantics unclear for all-skipped scenarios
The patch is well-structured overall, but the resource management bugs must be fixed before merging, and the timing side channel in MAC comparison is a security concern.
More information about the test-report
mailing list