|WARNING| examples: add Wycheproof validation app

dpdklab at iol.unh.edu dpdklab at iol.unh.edu
Tue Sep 15 18:52:03 CEST 2026


Test-Label: ai-code-review
Test-Status: WARNING
https://dpdk.org/patch/169628

_AI Code Review_

=== Patch Review: series.patch (via Claude) ===
Review date: 2026-09-15

# DPDK Patch Review: examples/wycheproof_validation

## Summary

This patch adds a new example application that validates DPDK cryptodev PMDs against Google Wycheproof JSON test vectors. The implementation includes support for AEAD algorithms (AES-GCM, AES-CCM, SM4-GCM, ChaCha20-Poly1305), MAC algorithms (HMAC variants, AES-CMAC, AES-GMAC), and asymmetric algorithms (DSA, ECDSA, ECDH).

## Errors

### Resource Leak on Error Path

**Location:** `app_init()` (lines 259-282)

The function creates multiple mempools but does not clean up all resources when a later step fails. For example:

```c
env.asym_session_pool = rte_cryptodev_asym_session_pool_create(...);
env.asym_op_pool = rte_crypto_op_pool_create(...);

queue_pair.mp_session = env.session_pool;
ret = rte_cryptodev_queue_pair_setup(env.dev_id, 0, &queue_pair, ...);
if (ret < 0)
    goto error;  /* asym pools not freed */

ret = rte_cryptodev_start(env.dev_id);
if (ret < 0)
    goto error;  /* asym pools still not freed */
```

The `error:` label at line 318 only frees the pools that were allocated before the `asym_session_pool` was added. The asymmetric pools (lines 273-277) are leaked if any step after their creation fails.

**Fix:** Adjust the error cleanup to include the asymmetric pools:

```c
error:
    rte_mempool_free(env.asym_op_pool);
    rte_mempool_free(env.asym_session_pool);
    rte_mempool_free(env.op_pool);
    /* ... existing cleanup ... */
```

---

### Resource Leak in Hash Device Setup

**Location:** `app_init()` secondary hash device loop (lines 286-313)

When setting up a separate hash device, if `rte_cryptodev_queue_pair_setup()` or `rte_cryptodev_start()` fails, the function calls `rte_cryptodev_close(d)` and continues searching. However, if `rte_cryptodev_configure()` succeeds but the subsequent queue setup fails, the device remains configured but not started, and the continue skips cleanup of any intermediate state.

While `rte_cryptodev_close()` is called on the queue-setup-or-start failure path, the `continue` inside the loop means that if *all* devices fail this way, we exit the loop without recording a usable hash device. Later code that calls `compute_hash()` will use `env.hash_dev_id`, which may point to a device that was configured but not successfully started.

**Fix:** After the loop completes, verify that `env.hash_dev_own` is true or that `env.hash_dev_id` points to a device with the SHA256 capability. If no suitable device was found, document that DSA/ECDSA paths will fail at runtime (which is acceptable given the comment "Digest computation for DSA/ECDSA needs..."), or return an error if DSA/ECDSA support is required.

Alternatively, ensure that if a device is partially configured (configure succeeded, queue setup failed), the `rte_cryptodev_close()` call properly cleans it up. The current code assumes `rte_cryptodev_close()` is sufficient, which is likely correct, but the logic should be verified or commented.

---

### Incorrectly Split P1363 DSA Signature

**Location:** `process_dsa_p1363()` (lines 1351-1353)

```c
ret = run_dsa_verify(&dsa, vector.msg, vector.msg_len, sig, sig_len / 2,
    sig + sig_len / 2, sig_len / 2, &status);
```

The comment at line 1341 checks for even signature length: `if ((sig_len & 1) != 0 || sig_len == 0)`. However, the P1363 DSA signature format is `r || s`, where each component is a fixed-width big-endian integer. The code assumes that both `r` and `s` are `sig_len / 2` bytes. This is correct for standard P1363 encoding where `sig_len = 2 * len(q)`.

However, if the signature length is not exactly twice the expected component length (e.g., due to malformed test data that passed the even-length check), this could result in passing incorrect component lengths to `run_dsa_verify()`.

**Analysis:** The code does check for even length and non-zero length at line 1341. For a well-formed P1363 signature, `sig_len = 2 * component_length`, so `sig_len / 2` is correct for each component. The check is adequate given the P1363 format constraint.

**Correction:** The code is actually correct. The even-length check ensures `sig_len / 2` is an integer, and the P1363 format guarantees equal-length `r` and `s`. No error here. (Removed per final check.)

---

### ECDH Shared Secret Memory Not Zeroed Before Free

**Location:** `run_ecdh_ecpoint()` (lines 1411-1472)

The function allocates `out_x` and `out_y` (line 1442-1443) to receive the computed shared secret point. On success, `out_x` is returned to the caller via `*shared_x`, but on error or when the operation fails, `out_x` and `out_y` are freed without zeroing (lines 1467-1468).

**Cryptographic secrets must be wiped before free.** The shared secret `x` coordinate is sensitive material. Even on an operation failure, the buffers may contain partial results or uninitialized data that could leak information.

**Fix:** Use `rte_free_sensitive()` instead of `rte_free()` for `out_x` and `out_y`:

```c
out:
    rte_free_sensitive(out_x);
    rte_free_sensitive(out_y);
```

Note: The caller also needs to use `rte_free_sensitive()` when freeing `result_x` after comparison (line 1558 in `process_ecdh_ecpoint()`).

---

### ECDH and ECDSA Session Leaks on Early Return

**Location:** `run_ecdh_ecpoint()` and `run_ecdsa_verify()` (lines 1447-1450, 1645-1648)

Both functions have this pattern:

```c
if (rte_cryptodev_asym_session_create(..., &session) < 0 || session == NULL) {
    ret = -ENOTSUP;
    goto out;
}
```

If `rte_cryptodev_asym_session_create()` returns `< 0`, the session pointer's value is undefined (it may not have been written to). The code then jumps to `out:`, which calls `rte_cryptodev_asym_session_free(env.dev_id, session)` unconditionally if `session != NULL`.

If the session pointer was not initialized to NULL before the create call, and the create fails, the code may pass an uninitialized pointer to `rte_cryptodev_asym_session_free()`.

**However:** Both functions initialize `session = NULL` in their local variable declarations (lines 1405, 1602). So the pointer is always initialized to NULL, and the `if (session != NULL)` check in the `out:` label (lines 1471, 1678) protects against freeing an invalid pointer.

**Correction:** The code is actually correct. The session pointers are initialized to NULL, so the check at the cleanup label is safe. No error here. (Removed per final check.)

---

### Use-After-Free Risk in Digest Computation

**Location:** `compute_hash()` (lines 1243-1302)

The function allocates `op_digest` (line 1277), attaches it to the crypto op, and enqueues the op. After dequeue, it copies the digest to the caller's buffer (line 1293) and then frees `op_digest` (line 1297).

**However:** The `rte_crypto_op_free(op)` at line 1299 may internally reference the digest buffer if the op still holds a pointer to it. While the DPDK crypto op API typically does not retain pointers after dequeue, the safest pattern is to free the op *before* freeing any attached buffers.

**Analysis:** Reviewing the DPDK crypto op lifecycle: after `rte_cryptodev_dequeue_burst()`, the op is detached from the device and its buffers should no longer be accessed by hardware. Freeing `op_digest` before freeing the op is safe as long as the op itself does not access the digest in its destructor. Given that `rte_crypto_op_free()` is a simple pool return, this is likely safe.

**Correction:** The code is acceptable. The op is dequeued and the digest data is copied out before freeing the digest buffer. The op free is a pool return and does not access the digest. No error here. (Removed per final check.)

---

## Warnings

### Missing Release Notes

**Location:** Documentation (patch header)

The patch adds a new example application, which is a significant user-facing change. Per the guidelines, "New drivers or subsystems must have release notes." A new example under `examples/` qualifies as a user-visible addition that should be documented in the current release notes.

**Fix:** Add an entry to `doc/guides/rel_notes/release_*.rst` (the current release file) under a "New Features" or "Examples" section:

```rst
* **Added Wycheproof cryptodev validation example.**

  Added ``dpdk-wycheproof_validation`` example application that validates
  cryptodev PMDs against Google Wycheproof JSON test vectors. Supports AEAD
  (AES-GCM, AES-CCM, SM4-GCM, ChaCha20-Poly1305), MAC (HMAC, AES-CMAC,
  AES-GMAC), and asymmetric algorithms (DSA, ECDSA, ECDH).
```

---

### Hardcoded DPDK Release Date in Future

**Location:** Patch header (Date: Tue, 15 Sep 2026)

The patch date is "2026-09-15", which is in the future relative to the current date (2026-09-15 per the system prompt). This suggests the patch is either misdated or is part of a forward-looking test corpus. Future dates in patches can confuse tooling and reviewers.

**Fix:** Ensure the patch date reflects the actual submission date. If this is a forward-looking example for testing purposes, document that in the commit message or cover letter.

---

### Asymmetric Pools Best-Effort Comment

**Location:** `app_init()` lines 279-281

The comment states "Asymmetric pools are best-effort; DSA/ECDSA paths gate on the device asymmetric feature flag at runtime." This is a reasonable approach for an example, but the code should ensure that if the pools fail to allocate, the application does not crash when a user tries to run DSA/ECDSA vectors.

**Current behavior:** The code checks for `env.asym_session_pool == NULL` and `env.asym_op_pool == NULL` in `run_dsa_verify()`, `run_ecdh_ecpoint()`, and `run_ecdsa_verify()`, returning `-ENOTSUP`. This is correct.

**Recommendation:** Add a user-facing message when the asymmetric pools fail to allocate, so users understand why DSA/ECDSA vectors are being skipped. For example:

```c
if (env.asym_session_pool == NULL || env.asym_op_pool == NULL) {
    if (env.debug)
        printf("Asymmetric session/op pools not available; "
               "DSA/ECDSA/ECDH vectors will be skipped\n");
}
```

---

### Makefile Missing Newline at EOF

**Location:** `examples/wycheproof_validation/Makefile` (line 40)

The Makefile ends with `test -d build && rmdir -p build || true` without a trailing newline. POSIX text files should end with a newline.

**Fix:** Add a newline after the last line of the Makefile.

---

### Debug Output for Empty Shared Secret

**Location:** `process_ecdh_ecpoint()` (lines 1540-1544)

```c
if (shared_len == 0) {
    debug_vector("SKIP", name, &vector,
        "no expected shared secret (point validation out of scope)");
    stats->skipped_unsupported++;
    goto next;
}
```

The debug message is clear, but the logic implies that vectors with `shared_len == 0` are silently skipped even when `--debug` is not specified. Users running without `--debug` will see these vectors counted in `skipped_unsupported` but won't know why.

**Recommendation:** This is acceptable for an example, but consider logging a summary at the end: "X vectors skipped due to out-of-scope operations (invalid-curve ECDH, etc.)." Alternatively, document this behavior in the README.

**Current README coverage:** The README states "Invalid-curve vectors (empty `shared`) are skipped because on-curve validation is a separate ECDH `PUB_KEY_VERIFY` op..." This is documented, so the behavior is acceptable.

---

### README: RST Definition Lists

**Location:** `examples/wycheproof_validation/README.md` (lines 10-19)

The README uses a Markdown table for the Wycheproof-to-DPDK mapping. If this documentation is converted to RST for inclusion in the DPDK docs, the table format should use RST syntax. Additionally, the list of supported algorithms (lines 5-9) is formatted as a Markdown list with inline code.

If this file will be processed by Sphinx, consider using RST definition lists for clearer structure:

```rst
The initial implementation supports:

AES-GCM, AES-CCM, CHACHA20-POLY1305, SM4-GCM
   AEAD algorithms using ``aead_test_schema_v1.json``.

HMAC-SHA1/224/256/384/512, HMAC-SHA3-*, HMAC-SM3, AES-CMAC
   MAC algorithms using ``mac_test_schema_v1.json``.
```

**Current status:** The file is `.md`, so Markdown syntax is correct. However, if integrated into DPDK docs (under `doc/guides/sample_app_ug/`), it should be converted to RST with definition lists per the guidelines.

---

## Info

### Makefile Standalone Build

**Location:** `examples/wycheproof_validation/Makefile`

The Makefile checks for `pkg-config` availability of `libdpdk` and `jansson` (lines 8-14) and provides both shared and static build targets. This is a good pattern for standalone examples.

**Observation:** The `PKGCONF ?= pkg-config` on line 6 allows users to override the tool, which is useful for cross-compilation. Well done.

---

### Wycheproof Schema Recognition

**Location:** `process_file()` (lines 1713-1775)

The dispatcher recognizes multiple Wycheproof schemas and algorithms, including those not yet implemented (AES-CBC-PKCS5, AES-EAX, RSA, etc.). The code explicitly logs these as "recognized-but-unsupported" rather than silently folding them into the generic "unsupported" count.

**Comment:** This is excellent design. It helps users understand which algorithms are on the roadmap versus which are unrecognized test files.

---

### Debug Output for Malformed Vectors

**Location:** Various `process_*` functions (e.g., lines 995-998, 1164-1166)

When a vector fails to parse (invalid JSON structure, missing required fields), the code uses `env.debug` to log "SKIP ... malformed vector" and increments `skipped_unsupported`. This prevents the application from crashing on corrupt or unexpected test data.

**Comment:** Good defensive programming. The debug logs provide actionable information when troubleshooting test corpus issues.

---

### ECDSA e Truncation

**Location:** `run_ecdsa_verify()` (lines 1630-1632)

```c
/* ECDSA


More information about the test-report mailing list