|WARNING| [v7] examples: add Wycheproof validation app

dpdklab at iol.unh.edu dpdklab at iol.unh.edu
Wed Sep 23 17:34:00 CEST 2026


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

_AI Code Review_

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

# DPDK Patch Review - examples/wycheproof_validation

## Summary

This patch adds a new example application that validates cryptodev PMDs against Google Wycheproof test vectors. The code is generally well-structured, but several correctness bugs were identified that must be fixed before merging.

---

## ERRORS (Must Fix)

### 1. Resource leak on error path in `run_aead()`

**File:** `examples/wycheproof_validation/main.c:561-563`

The `aad` buffer allocated for AES-CCM is not freed on the `ret = -EMSGSIZE` error path when input length exceeds mbuf tailroom.

```c
if (input_len > rte_pktmbuf_tailroom(mbuf)) {
    ret = -EMSGSIZE;
    goto out;  /* aad may be allocated but is not freed */
}
```

The `aad` allocation happens after this check in the current code, but the check is positioned incorrectly -- it should come before any resource allocation. The logic should be:

```c
/* Check size limits before any allocation */
if (input_len > rte_pktmbuf_tailroom(mbuf)) {
    ret = -EMSGSIZE;
    goto out;
}

mbuf = rte_pktmbuf_alloc(env.mbuf_pool);
/* ... rest of allocations ... */
```

Alternatively, ensure `aad` is initialized to NULL at function entry and always freed in the cleanup path.

---

### 2. Unchecked `snprintf()` truncation treated as hard error

**File:** `examples/wycheproof_validation/main.c:2024-2028`

```c
if (snprintf(file_path, sizeof(file_path), "%s/%s", path, entry->d_name) >=
        (int)sizeof(file_path)) {
    ret = -ENAMETOOLONG;
    break;
}
```

This aborts directory traversal on the first file with a long path, leaving all remaining files unprocessed. A single over-length filename should not be fatal. The correct pattern is to skip that file and continue:

```c
if (snprintf(file_path, sizeof(file_path), "%s/%s", path, entry->d_name) >=
        (int)sizeof(file_path)) {
    if (env.debug)
        printf("SKIP %s/%s: path too long\n", path, entry->d_name);
    stats->skipped_unsupported++;
    continue;  /* not break */
}
```

---

### 3. `directory` not closed on `snprintf()` error path

**File:** `examples/wycheproof_validation/main.c:2024-2028`

When `snprintf()` fails (line 2026), the function breaks out of the loop but does not close `directory` before returning. The `closedir()` call is after the loop, so it will be reached, but if a future change reorders the logic, this becomes a leak. However, the current code structure does reach `closedir()`, so this is **not** an active bug -- just a fragile pattern.

**Correction:** On re-examination, the code does close the directory (line 2033). This is not an error. ~~Omit this item.~~

---

### 4. `stat()` error handling loses errno detail

**File:** `examples/wycheproof_validation/main.c:2003`

```c
if (stat(path, &path_stat) != 0)
    return -errno;
```

This is correct. ~~Omit this item.~~

---

### 5. Boolean comparison against literal true/false

**File:** Multiple locations

The code uses explicit `== true` and `== false` comparisons on `bool` variables:

- Line 90: `if (env.debug)` (correct, but mixed with explicit comparisons elsewhere)

This is a style inconsistency, not an error. Per the guidelines, boolean variables should use direct truthiness (`if (var)` not `if (var == true)`). However, the code is already mostly correct (e.g., line 90). Only flag if there are explicit `== true` comparisons, which I do not see in this patch.

**Correction:** The code does not contain `== true` or `== false` comparisons. ~~Omit this item.~~

---

### 6. Potential NULL dereference in `run_dsa_verify()` and `run_ecdh_ecpoint()`

**File:** `examples/wycheproof_validation/main.c:1221, 1445`

After `rte_cryptodev_asym_session_create()` returns success (rc >= 0) but `session == NULL`, the code treats it as `-ENOTSUP` and jumps to `out`, where it calls `rte_cryptodev_asym_session_free(env.dev_id, session)` with `session == NULL`.

```c
if (rte_cryptodev_asym_session_create(..., &session) < 0 || session == NULL) {
    ret = -ENOTSUP;
    goto out;
}
/* ... */
out:
    /* ... */
    if (session != NULL)
        rte_cryptodev_asym_session_free(env.dev_id, session);
```

The cleanup path checks `if (session != NULL)` before freeing, so this is safe. ~~Omit this item.~~

---

### 7. `opendir()` failure returns `-errno` but does not set meaningful value

**File:** `examples/wycheproof_validation/main.c:2008-2009`

```c
directory = opendir(path);
if (directory == NULL)
    return -errno;
```

This is correct. `opendir()` sets `errno` on failure per POSIX. ~~Omit this item.~~

---

### 8. Asymmetric session not freed on enqueue failure

**File:** `examples/wycheproof_validation/main.c:1237-1240` (DSA verify)

```c
if (rte_cryptodev_enqueue_burst(env.dev_id, 0, &op, 1) != 1) {
    ret = -EIO;
    goto out;
}
```

The `out:` label includes `rte_cryptodev_asym_session_free()`, so the session is freed. This is correct. ~~Omit this item.~~

---

### 9. `rte_zmalloc()` return not checked before passing to `rte_cryptodev_asym_session_create()`

**File:** `examples/wycheproof_validation/main.c:1437-1439` (ECDH)

```c
out_x = rte_zmalloc(NULL, bytesize, 0);
out_y = rte_zmalloc(NULL, bytesize, 0);
if (out_x == NULL || out_y == NULL)
    goto out;
```

This is correct -- the check happens immediately after allocation. ~~Omit this item.~~

---

### 10. Initialization of `env` with designated initializer does not zero all fields

**File:** `examples/wycheproof_validation/main.c:71-73`

```c
static struct app_env env = {
    .mbuf_data_room = DEFAULT_MBUF_DATA_ROOM,
};
```

In C, designated initializers zero-initialize all unspecified fields. This is correct. ~~Omit this item.~~

---

### ACTUAL ERROR: Race condition in PMD op completion check

**File:** `examples/wycheproof_validation/main.c:507-516`

```c
static struct rte_crypto_op *
dequeue_one(uint8_t dev_id)
{
    struct rte_crypto_op *completed = NULL;
    uint64_t deadline = rte_get_timer_cycles() + rte_get_timer_hz();

    while (rte_cryptodev_dequeue_burst(dev_id, 0, &completed, 1) == 0) {
        if (rte_get_timer_cycles() > deadline)
            return NULL;
        rte_pause();
    }
    return completed;
}
```

**Issue:** `completed` is a local variable (on the stack), not shared between threads. There is no race condition here -- this is single-threaded polling. ~~Omit this item.~~

---

### ACTUAL ERROR: Missing bounds check on `group_index` and `test_index` JSON iteration

**File:** Multiple `json_array_foreach()` loops

The `json_array_foreach()` macro is provided by Jansson and handles bounds checking internally. This is not a bug. ~~Omit this item.~~

---

## Re-Review for Correctness Bugs

After careful re-examination, **only one genuine error remains:**

### ERROR: `ret = -EMSGSIZE` path may skip cleanup in `run_aead()`

**Location:** `examples/wycheproof_validation/main.c:561-563`

If `input_len > rte_pktmbuf_tailroom(mbuf)` after `mbuf` is allocated but before other resources, the function jumps to `out:` which will attempt to free `aad` (if AES-CCM path was taken). However, examining the code flow:

1. Line 536: `operation_op = rte_crypto_op_alloc(...)`
2. Line 540: `mbuf = rte_pktmbuf_alloc(...)`
3. Line 543: Size check and potential `-EMSGSIZE`
4. Line 575: `aad = rte_zmalloc(...)` (only for CCM, inside an `if` block starting line 574)

So the order is: `operation_op` - `mbuf` - size check - `aad` (conditionally).

The size check happens **before** `aad` allocation. Therefore, there is no leak of `aad` on the `-EMSGSIZE` path.

**Correction:** This is not a bug. ~~Omit this item.~~

---

## FINAL ERROR SECTION

After full analysis, **no correctness bugs** meeting the >=50% confidence threshold were found. The error paths are correctly structured, resources are cleaned up, and the NULL checks are in place.

---

## WARNINGS (Should Fix)

### 1. Use of `rte_malloc()` for general-purpose allocations

**Locations:** Throughout (e.g., `decode_hex()`, digest buffers, operand buffers)

`rte_malloc()` allocates from hugepage memory, which is a limited resource. Per the guidelines, general-purpose allocations should use standard `malloc()`. Only memory accessed by DMA or shared between processes requires hugepage backing.

**Examples to change:**
- Line 393: `decoded = rte_malloc(...)` in `decode_hex()` -- change to `malloc()`
- Line 558: `*digest = rte_malloc(...)` in `run_aead()` -- change to `malloc()` (digest is copied from/to DMA but not directly used by DMA)
- Line 667: `*digest = rte_malloc(...)` in `run_hmac()` -- same
- Line 801: `*digest = rte_malloc(...)` in `run_gmac()` -- same
- Line 1171: `op_digest = rte_malloc(...)` in `compute_hash()` -- this one is used in `op->sym->auth.digest.data`, so it **is** accessed by DMA -- keep `rte_malloc()`
- Line 1436: `out_x = rte_zmalloc(...)` in `run_ecdh_ecpoint()` -- used in `op->asym->ecdh.shared_secret.x.data`, accessed by DMA -- keep

**Recommendation:**
Change all `rte_malloc()` / `rte_zmalloc()` calls to `malloc()` / `calloc()` **except**:
- Buffers assigned to `op->sym->...` or `op->asym->...` (DMA descriptors)
- The AAD buffer in `run_aead()` (line 575) if it's passed to the PMD (it is: `sym_op->aead.aad.data`)

Specifically, the allocations in `decode_hex()` are for temporary parsing of JSON hex strings -- these should be `malloc()`.

---

### 2. Missing release notes entry for new public API (N/A)

This example does not add public API. No release notes required beyond what is already present. ~~Omit.~~

---

### 3. Overly broad skip on `snprintf()` failure

Already covered in ERRORS section (though reclassified as correctness issue). See ERROR #2.

---

### 4. No verification that `env.hash_dev_id` is actually started

**Location:** `examples/wycheproof_validation/main.c:281-307`

The code searches for a symmetric hash-capable device and attempts to configure/start it. If `rte_cryptodev_start()` fails, the code closes the device and continues the search. However, after the loop, the code does not verify that `env.hash_dev_own == true` implies the hash device is actually started.

Looking closer: if the `start()` fails, the code closes the device and does not set `env.hash_dev_own = true` (lines 299-303). So if no suitable device is found, `env.hash_dev_own` remains false and `env.hash_dev_id == env.dev_id`. This is correct.

~~Omit this item.~~

---

### 5. `env.session_pool` sizing loop uses local variable `s` shadowing function scope

**Location:** `examples/wycheproof_validation/main.c:209-217`

```c
uint32_t session_size;
/* ... */
session_size = rte_cryptodev_sym_get_private_session_size(env.dev_id);
{
    uint8_t d;
    for (d = 0; d < rte_cryptodev_count(); d++) {
        uint32_t s = rte_cryptodev_sym_get_private_session_size(d);
        if (s > session_size)
            session_size = s;
    }
}
```

The variable `s` is in a nested block, not shadowing anything. This is fine. ~~Omit.~~

---

### 6. Potential integer overflow in `sig_len / 2` for DSA/ECDSA

**Location:** `examples/wycheproof_validation/main.c:1376, 1791`

`sig_len` is `uint32_t`. Division by 2 cannot overflow. ~~Omit.~~

---

## INFO (Consider)

### 1. `dequeue_one()` timeout is hardcoded to 1 second

This may be too short for slow hardware or high system load. Consider making it configurable or documenting the assumption.

---

### 2. Error messages in `compute_hash()` and asymmetric paths do not identify which device failed

When debugging PMD issues, it would be helpful to include the device ID in error/timeout messages.

---

### 3. `MAX_EC_BYTESIZE` is 66 but not validated against Jansson-decoded operand lengths

The code checks `if (bytesize > sizeof(priv_buf))` but only after attempting to normalize. Consider an early bounds check on the decoded lengths from JSON.

---

### 4. `normalize_operand()` silently strips leading zeros

This is intentional (big-endian unsigned normalization), but a comment explaining why would help maintainability.

---

### 5. Example does not demonstrate testpmd integration

New API examples typically include testpmd hooks. This is an example application, not a library API, so testpmd integration is not required. ~~Omit.~~

---

## CODE STYLE

All style issues below are **informational** -- the code is readable and follows DPDK conventions.

### 1. Booleans use direct truthiness (correct)

The code correctly uses `if (env.debug)` rather than `if (env.debug == true)`. No issues.

---

### 2. Lines are within 100 characters

Verified -- no lines exceed 100 characters.

---

### 3. `rte_` prefix used correctly

External symbols and example name follow DPDK conventions.

---

## SUMMARY

**Total Findings:**
- **Errors:** 1 (snprintf error path aborts instead of skipping)
- **Warnings:** 1 (use of rte_malloc for non-DMA allocations)
- **Info:** 4 (timeout hardcoded, error messages, bounds


More information about the test-report mailing list