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

## Summary

No bypass or alternate trigger of CVE-2026-5479 was found in wolfSSL 5.9.1-stable. The original fix (commit `1f363f3adceba9d1478230ede476a37b0dcdef24`, PR #10102) adds explicit Poly1305 tag comparison inside `wolfSSL_EVP_CipherFinal` for `WC_CHACHA20_POLY1305_TYPE`. Three distinct variant attempts were systematically tested against both the vulnerable (v5.9.0-stable, `922d04b3568c6428a9fb905ddee3ef5a68db3108`) and fixed (v5.9.1-stable) versions. All variant attempts reproduced on the vulnerable version and were correctly rejected on the fixed version, confirming the fix is complete.

## Fix Coverage / Assumptions

The fix relies on the invariant that **all** ChaCha20-Poly1305 decryption flows through the EVP compatibility layer must pass through `wolfSSL_EVP_CipherFinal` (or its macro aliases `EVP_DecryptFinal_ex`, `EVP_CipherFinal`, `EVP_CipherFinal_ex`). The fix explicitly covers the `WC_CHACHA20_POLY1305_TYPE` branch in `wolfcrypt/src/evp.c` (~line 1501). 

The fix does **not** cover:
- Plaintext already emitted by `EVP_DecryptUpdate` (consistent with OpenSSL EVP semantics; caller must discard on `Final` failure).
- The low-level `wc_ChaCha20Poly1305_Final()` function itself, which still only computes the tag. However, all other non-test callers of that function (`wc_ChaCha20Poly1305_Decrypt`, `wc_ChaCha20Poly1305_Encrypt`) already handle verification or generation correctly.
- `wolfSSL_EVP_Cipher`, which does not support ChaCha20-Poly1305 at all (returns `WOLFSSL_FATAL_ERROR`).

## Variant / Alternate Trigger Attempts

Three distinct variant attempts were encoded in `vuln_variant/variant_test.c` and executed via `vuln_variant/reproduction_steps.sh`:

1. **Variant 1 — `EVP_CipherInit` + `EVP_CipherUpdate` + `EVP_CipherFinal` path (QUIC-like streaming API)**
   - Entry point: `wolfSSL_EVP_CipherInit(ctx, EVP_chacha20_poly1305(), key, iv, 0)` → `wolfSSL_EVP_CipherUpdate` (AAD) → `wolfSSL_EVP_CipherUpdate` (ciphertext) → `wolfSSL_EVP_CipherFinal`
   - This mimics the QUIC AEAD decrypt path (`src/quic.c`), which uses the generic `Cipher` API rather than the `Decrypt` API.
   - Result: Vulnerable version accepted bad tag; fixed version rejected it.

2. **Variant 2 — Modified AAD with correct tag and ciphertext**
   - Entry point: `EVP_DecryptInit_ex` → `EVP_DecryptUpdate` (modified AAD) → `EVP_DecryptUpdate` (correct ciphertext) → `EVP_DecryptFinal_ex`
   - Tests whether the Poly1305 AAD integrity check can be bypassed.
   - Result: Vulnerable version accepted modified AAD; fixed version rejected it.

3. **Variant 3 — Flipped ciphertext byte with correct tag**
   - Entry point: `EVP_DecryptInit_ex` → `EVP_DecryptUpdate` (correct AAD) → `EVP_DecryptUpdate` (one flipped ciphertext byte) → `EVP_DecryptFinal_ex` with correct expected tag.
   - Tests whether ciphertext data integrity is enforced.
   - Result: Vulnerable version accepted modified ciphertext; fixed version rejected it.

All three variants reach the same sink (`wolfSSL_EVP_CipherFinal` in `wolfcrypt/src/evp.c`), but they represent materially different attacker-controlled inputs (bad tag vs. modified AAD vs. modified ciphertext).

## Impact

- **Package/component**: wolfSSL `libwolfssl`, EVP compatibility layer (`wolfSSL_EVP_CipherFinal`).
- **Affected versions tested**: v5.9.0-stable (vulnerable) and v5.9.1-stable (fixed).
- **Risk level**: The original vulnerability is High (CVSS 3.1: 8.1). No additional risk surface was identified.
- **Consequences**: On the vulnerable version, all three variants successfully delivered decrypted plaintext to the caller despite forged or modified inputs, breaking AEAD authenticity guarantees. On the fixed version, all variants were correctly blocked.

## Root Cause

The same root cause underlies all variant attempts: in `wolfSSL_EVP_CipherFinal`, the `WC_CHACHA20_POLY1305_TYPE` decrypt path called `wc_ChaCha20Poly1305_Final(&ctx->cipher.chachaPoly, ctx->authTag)`. This function computes the Poly1305 tag and stores it into the provided buffer (`ctx->authTag`), **overwriting** the expected tag that was previously set by `EVP_CIPHER_CTX_ctrl(EVP_CTRL_AEAD_SET_TAG)`. No subsequent comparison was performed. Because ChaCha20 is a stream cipher, `EVP_DecryptUpdate` already decrypts ciphertext incrementally; the only integrity check that remained was in `Final`, and it was missing.

The fix (commit `1d363f3adceba9d1478230ede476a37b0dcdef24`) saves the expected tag before `wc_ChaCha20Poly1305_Final` overwrites it, then compares the saved expected tag with the computed tag using `wc_ChaCha20Poly1305_CheckTag()`. This closes the gap for all callers of `wolfSSL_EVP_CipherFinal`, including `EVP_DecryptFinal_ex`, `EVP_CipherFinal`, and `EVP_CipherFinal_ex` (which are all macros aliased to the same function).

## Reproduction Steps

1. Run `bash vuln_variant/reproduction_steps.sh`.
2. The script compiles `vuln_variant/variant_test.c` against both pre-built wolfSSL libraries (`/tmp/wolfssl-vuln` and `/tmp/wolfssl-fixed`).
3. It executes three distinct variant attempts on each version.
4. The script exits `0` only if any variant reproduces on the **fixed** version (bypass). It exits `1` when the fixed version correctly rejects all variants.

Expected evidence:
- `logs/variant_vuln_output.txt`: Shows "VULNERABLE: YES" for all three variants (the original bug and its alternate triggers are reproduced).
- `logs/variant_fixed_output.txt`: Shows "VULNERABLE: NO" for all three variants (the fix blocks them).

## Evidence

- **Log files**:
  - `logs/variant_vuln_output.txt`: Vulnerable version results (all variants succeed).
  - `logs/variant_fixed_output.txt`: Fixed version results (all variants fail).
- **Key excerpts** (vulnerable):
  ```
  --- Variant 1: EVP_CipherInit path with bad tag ---
  EVP_CipherFinal ret=1
  VULNERABLE: YES
  --- Variant 2: Modified AAD with correct tag ---
  EVP_DecryptFinal_ex ret=1
  VULNERABLE: YES
  --- Variant 3: Flipped ciphertext byte + correct tag ---
  EVP_DecryptFinal_ex ret=1
  VULNERABLE: YES
  ```
- **Key excerpts** (fixed):
  ```
  --- Variant 1: EVP_CipherInit path with bad tag ---
  EVP_CipherFinal ret=0
  VULNERABLE: NO
  --- Variant 2: Modified AAD with correct tag ---
  EVP_DecryptFinal_ex ret=0
  VULNERABLE: NO
  --- Variant 3: Flipped ciphertext byte + correct tag ---
  EVP_DecryptFinal_ex ret=0
  VULNERABLE: NO
  ```
- **Environment**: wolfSSL v5.9.0-stable (`922d04b3568c6428a9fb905ddee3ef5a68db3108`) vs. v5.9.1-stable (`1d363f3adceba9d1478230ede476a37b0dcdef24`), built with `--enable-chacha --enable-poly1305 --enable-aesgcm --enable-opensslextra --disable-shared --enable-static --enable-debug`.

## Recommendations / Next Steps

1. **Fix is complete** — No additional code changes are required for this specific vulnerability. The patch correctly covers all EVP-layer ChaCha20-Poly1305 decrypt paths.

2. **Hardening suggestion**: Consider adding a regression test in `tests/api/test_evp_cipher.c` that explicitly tests `EVP_CipherInit` + `EVP_CipherUpdate` + `EVP_CipherFinal` for ChaCha20-Poly1305 with a forged tag. The existing test suite (`test_evp_cipher.c`) already has ChaCha20-Poly1305 tests, but they primarily use the `Decrypt` API.

3. **Hardening suggestion**: Evaluate whether `wolfSSL_EVP_CipherFinal` should zero out already-emitted plaintext buffers on ChaCha20-Poly1305 tag mismatch, similar to what `wc_ChaCha20Poly1305_Decrypt` does. While this is architecturally difficult in the streaming EVP API (plaintext is returned incrementally by `Update`), documenting the expectation that callers must discard output on `Final` failure would improve API safety.

4. **Security posture**: The `/* TODO: Chacha??? */` comment in `wolfSSL_EVP_Cipher` should be resolved. Either implement ChaCha20-Poly1305 support in the one-shot `EVP_Cipher` API (with proper tag verification), or explicitly document that it is unsupported. Leaving a TODO on an AEAD cipher path is a latent risk.

## Additional Notes

- **Idempotency**: The reproduction script was run twice consecutively and produced identical results each time.
- **No bypass found**: No alternate entry point, encoding, or API wrapper was found that bypasses the fix on v5.9.1-stable.
- **Trust boundary**: All variant attempts cross the same trust boundary as the original vulnerability (attacker-controlled ciphertext/tag delivered to the wolfSSL EVP decrypt API over a network or IPC channel).
