# RCA Report: CVE-2026-5479 — wolfSSL EVP ChaCha20-Poly1305 Authentication Tag Verification Failure

## Summary

CVE-2026-5479 is an integrity-check bypass (CWE-354) in wolfSSL's EVP compatibility layer for ChaCha20-Poly1305 AEAD decryption. In `wolfSSL_EVP_CipherFinal` (`wolfcrypt/src/evp.c`), the decrypt path for `WC_CHACHA20_POLY1305_TYPE` calls `wc_ChaCha20Poly1305_Final()` to compute the Poly1305 authentication tag, which overwrites the expected tag previously stored in `ctx->authTag` by `EVP_CIPHER_CTX_ctrl(EVP_CTRL_AEAD_SET_TAG)`. No comparison between the computed tag and the expected tag is ever performed, so any forged or mismatched tag is silently accepted. This completely nullifies the authenticity guarantee of the AEAD cipher, allowing an active network adversary to supply modified ciphertext that the application will treat as integrity-validated.

## Impact

- **Package/component affected**: wolfSSL `libwolfssl`, specifically the EVP compatibility API (`wolfSSL_EVP_CipherFinal`) for ChaCha20-Poly1305 decryption.
- **Affected versions**: wolfSSL `< 5.9.1` (last vulnerable release tag: `v5.9.0-stable`).
- **Fixed versions**: `5.9.1` (commit `1faddd640`).
- **Risk level**: High (CVSS 3.1: 8.1, CVSS 4.0: 7.6).
- **Consequences**: Any consumer using the standard EVP decrypt pattern (`EVP_DecryptInit_ex` → `EVP_DecryptUpdate` → `EVP_CIPHER_CTX_ctrl(SET_TAG)` → `EVP_DecryptFinal_ex`) will accept attacker-modified ciphertext as authentic. This enables message forgery in protocols that rely on ChaCha20-Poly1305 via the wolfSSL EVP API (e.g., TLS record-layer AEAD when used through the EVP abstraction).

## Root Cause

The bug is located in `wolfcrypt/src/evp.c` inside the `wolfSSL_EVP_CipherFinal` function, specifically in the `WC_CHACHA20_POLY1305_TYPE` branch (lines ~1502–1515 in v5.9.0-stable).

On the decrypt path (`!ctx->enc`), the code does:

```c
case WC_CHACHA20_POLY1305_TYPE:
    if (wc_ChaCha20Poly1305_Final(&ctx->cipher.chachaPoly,
                                  ctx->authTag) != 0) {
        WOLFSSL_MSG("wc_ChaCha20Poly1305_Final failed");
        return WOLFSSL_FAILURE;
    }
    else {
        *outl = 0;
        return WOLFSSL_SUCCESS;
    }
    break;
```

`wc_ChaCha20Poly1305_Final()` **computes** the Poly1305 tag and writes it into `ctx->authTag`, **overwriting** the expected tag that was set earlier via `EVP_CTRL_AEAD_SET_TAG`. The code then unconditionally returns `WOLFSSL_SUCCESS` without ever comparing the computed tag to the expected tag.

The fix (commit `1faddd640edc8195c1fb7cb3904876e434ecab87`, PR #10102) mirrors the existing AES-GCM branch: it saves the expected tag before calling `_Final()`, then uses `wc_ChaCha20Poly1305_CheckTag()` to verify the computed tag against the saved expected tag on the decrypt path. If the tags do not match, `WOLFSSL_FAILURE` is returned.

Diff excerpt from the fix:

```diff
+        {
+            byte computedTag[CHACHA20_POLY1305_AEAD_AUTHTAG_SIZE];
+            if (!ctx->enc) {
+                XMEMCPY(computedTag, ctx->authTag, sizeof(computedTag));
+            }
             if (wc_ChaCha20Poly1305_Final(&ctx->cipher.chachaPoly,
                                           ctx->authTag) != 0) {
                 WOLFSSL_MSG("wc_ChaCha20Poly1305_Final failed");
                 return WOLFSSL_FAILURE;
             }
-            else {
-                *outl = 0;
-                return WOLFSSL_SUCCESS;
+            if (!ctx->enc) {
+                int tagErr = wc_ChaCha20Poly1305_CheckTag(computedTag,
+                                                          ctx->authTag);
+                ForceZero(computedTag, sizeof(computedTag));
+                if (tagErr != 0) {
+                    WOLFSSL_MSG("ChaCha20-Poly1305 tag mismatch");
+                    return WOLFSSL_FAILURE;
+                }
             }
-            break;
+            *outl = 0;
+            return WOLFSSL_SUCCESS;
+        }
+        break;
```

## Reproduction Steps

1. **Run `repro/reproduction_steps.sh`**. This self-contained script:
   - Builds vulnerable wolfSSL (`v5.9.0-stable`) into `/tmp/wolfssl-vuln` and fixed wolfSSL (`v5.9.1-stable`) into `/tmp/wolfssl-fixed` (or uses pre-built installations if present).
   - Compiles `repro/aead_tag_check.c` against both libraries.
   - Executes the test harness against each build.
   - Generates `logs/runtime_manifest.json` with concrete API return values.
   - Prints a verdict based on observable cryptographic behavior.

2. **What the script does**:
   - Encrypts a known plaintext under ChaCha20-Poly1305 with a fixed key/nonce.
   - Performs three tamper attacks on the ciphertext/tag pair:
     1. Replaces the 16-byte authentication tag with all zeros.
     2. Replaces the tag with a random bad tag (`0xAB` repeated).
     3. Flips the first byte of the ciphertext while keeping the original tag.
   - Decrypts each tampered pair using the standard EVP API (`EVP_DecryptInit_ex` → `EVP_DecryptUpdate` → `EVP_CIPHER_CTX_ctrl(SET_TAG)` → `EVP_DecryptFinal_ex`).
   - Captures the return value of `EVP_DecryptFinal_ex` and the decrypted bytes.

3. **Expected evidence**:
   - **Vulnerable build**: `EVP_DecryptFinal_ex` returns `1` (success) for all three tampered inputs, and the decrypted plaintext is delivered to the caller (either matching the original plaintext for forged tags, or producing modified bytes for flipped ciphertext).
   - **Fixed build**: `EVP_DecryptFinal_ex` returns `0` (failure) for all three tampered inputs, correctly rejecting the forgeries.

## Evidence

- **Vulnerable run log**: `logs/vulnerable_output.txt`
  - Excerpt (Test 1 — zeroed tag):
    ```
    EVP_DecryptUpdate ret=1, EVP_DecryptFinal_ex ret=1
    Decrypted:  54686520717569636b2062726f776e20666f78206a756d7073206f7665720000
    Plaintext match: YES
    VULNERABLE: YES (Final succeeded with forged tag)
    ```
  - Excerpt (Test 3 — flipped ciphertext byte):
    ```
    EVP_DecryptUpdate ret=1, EVP_DecryptFinal_ex ret=1
    Decrypted:  ab686520717569636b2062726f776e20666f78206a756d7073206f7665720000
    Plaintext match: NO
    VULNERABLE: YES (Final succeeded with modified ciphertext)
    ```

- **Fixed run log**: `logs/fixed_output.txt`
  - Excerpt (Test 1 — zeroed tag):
    ```
    EVP_DecryptUpdate ret=1, EVP_DecryptFinal_ex ret=0
    Decrypted:  54686520717569636b2062726f776e20666f78206a756d7073206f7665720000
    Plaintext match: YES
    VULNERABLE: NO (Final rejected forged tag)
    ```

- **Runtime manifest**: `logs/runtime_manifest.json`
  - Contains structured JSON with the exact `EVP_DecryptFinal_ex` return codes for each tamper strategy on both the vulnerable and fixed builds.

- **Environment**:
  - Host: `x86_64-pc-linux-gnu`
  - Compiler: `gcc`
  - wolfSSL configure flags: `--enable-chacha --enable-poly1305 --enable-aesgcm --enable-opensslextra --disable-shared --enable-static --enable-debug CFLAGS='-g -O0'`

## Recommendations / Next Steps

1. **Upgrade immediately** to wolfSSL `5.9.1` or later, which contains commit `1faddd640`.
2. **Verify tag-check coverage** in any custom fork or wrapper around `wolfSSL_EVP_CipherFinal` for ChaCha20-Poly1305; the missing `wc_ChaCha20Poly1305_CheckTag()` call is the root cause.
3. **Regression testing**: Add the exact tamper strategies from this reproduction (zeroed tag, random tag, flipped ciphertext byte) to the project's EVP cipher unit tests. The upstream fix already includes a negative test for an all-zero forged tag.
4. **Audit other AEAD paths**: Review `wolfSSL_EVP_CipherFinal` for any other AEAD ciphers that might lack tag verification. The AES-GCM branch already had the correct pattern; ChaCha20-Poly1305 was the outlier.

## Additional Notes

- **Idempotency**: `repro/reproduction_steps.sh` was executed twice consecutively on the same host and produced identical results (exit code 0, `VERDICT: CONFIRMED`).
- **Edge cases**: The reproduction uses the standard `EVP_DecryptInit_ex` + `EVP_DecryptUpdate` + `EVP_DecryptFinal_ex` pattern. The streaming `EVP_CipherUpdate`/`EVP_CipherFinal` equivalents are also affected because they route through the same `wolfSSL_EVP_CipherFinal` code path.
- **Limitations**: The reproduction requires building wolfSSL from source with `--enable-chacha --enable-poly1305 --enable-opensslextra`. On systems without a C compiler or git, the script will exit with a descriptive error rather than a false positive.
