Skip to content

CVE-2026-5479: Verified Repro With Script Download

CVE-2026-5479: wolfSSL: EVP ChaCha20-Poly1305 decryption returns plaintext without verifying authentication tag

CVE-2026-5479 is verified against wolfssl · source. Affected versions: < 5.9.1. Fixed in 5.9.1. This high reproduction includes runnable sandbox proof, artifacts, and a plain-text agent view under REPRO-2026-00172.

REPRO-2026-00172 wolfssl · source Variant found May 28, 2026 CVE entry ↗ .txt
Severity
HIGH
CVSS
8.1
Reproduced in
12m 46s
Tool calls
175
Spend
$2.22
01 · Overview

What Is CVE-2026-5479?

CVE-2026-5479 (CWE-354) is a high-severity vulnerability in wolfSSL's EVP compatibility API where ChaCha20-Poly1305 decryption returns plaintext to the caller without verifying that the Poly1305 authentication tag matches, silently defeating the AEAD's authenticity guarantee. Pruva reproduced it (reproduction REPRO-2026-00172).

02 · Severity & CVSS

CVE-2026-5479 Severity & CVSS Score

CVE-2026-5479 is rated high severity, with a CVSS base score of 8.1 out of 10.

HIGH threat level
8.1 / 10 CVSS base
Weakness CWE-354 (Improper Validation of Integrity Check Value)

High — serious impact or readily exploitable. Prioritize remediation.

03 · Affected Versions

Affected wolfssl Versions

wolfssl · source versions < 5.9.1 are affected.

How to Reproduce CVE-2026-5479

$ pruva-verify REPRO-2026-00172
or curl -O https://pruva.dev/api/v1/reproductions/REPRO-2026-00172/artifacts/bundle/repro/reproduction_steps.sh && chmod +x reproduction_steps.sh && ./reproduction_steps.sh
Run in a VM or disposable container. This exploits a real vulnerability.
06 · Proof of Reproduction

Proof of Reproduction for CVE-2026-5479

Reproduced by Pruva's autonomous agents — 175 tool calls over 13 min. Full root-cause analysis and the complete transcript are below.

Variants tested

Systematic variant attempts against wolfSSL EVP ChaCha20-Poly1305 tag verification fix. Three distinct variants tested: (1) EVP_CipherInit streaming path, (2) modified AAD with correct tag, (3) flipped ciphertext byte with correct tag. None reproduced on fixed version.

How the agent worked 571 events · 175 tool calls · 13 min
13 minDuration
175Tool calls
130Reasoning steps
571Events
2Dead-ends
Agent activity over 13 min
Support
18
Repro
312
Variant
236
0:0012:46

Root Cause and Exploit Chain for CVE-2026-5479

Versions: wolfSSL < 5.9.1 (last vulnerable release tag: v5.9.0-stable).Fixed: 5.9.1 (commit 1faddd640).

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.

  • 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_exEVP_DecryptUpdateEVP_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:

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:

+        {
+            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_exEVP_DecryptUpdateEVP_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.

Variant Analysis & Alternative Triggers for CVE-2026-5479

Versions: versions tested: v5.9.0-stable (vulnerable) and v5.9.1-stable (fixed).

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_exEVP_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_exEVP_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).

  • 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).

CVE-2026-5479 Reproduction Transcript

The agent's step-by-step process — every tool call, every handoff, the moment the exploit fired.

Full session Replay every step — scrub the timeline or play it back.

Event 1/40
0:000:23
0:00
session startedaccounts/fireworks/models/kimi-k2p6 · cve-2026-5479 · cve-2026
0:04
0:04
web search
0:04
error

Unknown error

0:06
0:06
0:06
error

Unknown error

0:16
0:16
extract_facts
no facts extracted
0:16
0:16
0:16
supportrepro
0:18
0:19
0:19
0:19
0:20
0:20
0:20
0:20
0:21
0:21
0:23
08 · How to Fix

How to Fix CVE-2026-5479

Upgrade wolfssl · source to 5.9.1 or later.

Coming soon

Step-by-step mitigation and hardening guidance for CVE-2026-5479 — configuration checks, workarounds where no patch exists, and how to verify you're protected — is on the way.

10 · FAQ

FAQ: CVE-2026-5479

How does the CVE-2026-5479 tag-bypass attack work?

A consumer using the standard EVP pattern (EVP_DecryptInit_ex -> EVP_DecryptUpdate -> EVP_CIPHER_CTX_ctrl(SET_TAG) -> EVP_DecryptFinal_ex) will see EVP_DecryptFinal_ex return success even for ciphertext with a mismatched tag or flipped bytes, because the expected tag is overwritten before any comparison happens. An active network attacker who can supply or modify ciphertext (e.g. in a TLS record-layer AEAD context using wolfSSL's EVP API) can therefore forge messages that the application treats as integrity-validated.

Which wolfSSL versions are affected by CVE-2026-5479, and where is it fixed?

wolfSSL versions before 5.9.1 are affected (last vulnerable release tag v5.9.0-stable). It is fixed in 5.9.1 (commit 1faddd640).

How severe is CVE-2026-5479?

The record rates it high severity; the underlying root-cause analysis cites CVSS 3.1 score 8.1 and CVSS 4.0 score 7.6.

How can I reproduce CVE-2026-5479?

Download the verified script from this page and run it in an isolated environment against wolfSSL before 5.9.1. It performs a standard EVP ChaCha20-Poly1305 decrypt with a deliberately mismatched or tampered authentication tag and shows EVP_DecryptFinal_ex succeeding anyway, then confirms 5.9.1 rejects the same input.
11 · References

References for CVE-2026-5479

Authoritative sources for CVE-2026-5479 — official vulnerability databases and the upstream advisory. Pruva's reproduction verifies the issue firsthand; these are the primary records to corroborate it.