{"repro_id":"REPRO-2026-00346","version":6,"title":"curl: native CA store flag not part of connection reuse matching, wrong trust store may authenticate reused TLS connections","repro_type":"security","status":"published","severity":"low","description":"Native CA store selection was omitted from TLS connection-pool matching, allowing an HTTPS connection authenticated under one trust-store policy to be reused under another.","root_cause":"# Root Cause Analysis: CVE-2026-80231\n\n## Summary\n\nCVE-2026-80231 is a libcurl TLS connection-reuse matching flaw. Before the fix, the effective `native_ca_store` setting was stored outside `ssl_primary_config`, so `match_ssl_primary_config()` did not include it when deciding whether a pooled connection was compatible with a later transfer. A same-origin request configured with explicit custom CA material could therefore reuse an HTTPS connection established using native system trust, even though opening a fresh connection under that later policy correctly rejected the server certificate.\n\n## Impact\n\n- **Affected package/component:** curl/libcurl virtual TLS configuration and connection cache.\n- **Affected versions:** curl 7.71.0 through 8.21.0, introduced by commit `148534db57dda611cf8516e92e4d6e35fc1e5074` and fixed by commit `7be1e70cb6bcd83e130ecfe8cb91b6a7dcdeff42` in curl 8.22.0.\n- **Risk and consequence:** A caller's later trust-store restriction is not enforced when an origin connection created under native CA trust is reused. A certificate that the later transfer's configured CA policy does not authorize is accepted through stale pooled TLS state. This is an authentication/authorization-policy bypass (CWE-488) rated Low upstream because it requires connection reuse and platform/backend native-store support.\n\n## Impact Parity\n\n- **Disclosed/claimed maximum impact:** Authorization/authentication-policy bypass through reuse of a TLS connection created under a different native CA-store setting.\n- **Reproduced impact from this run:** The vulnerable parent sent an HTTPS request successfully over a native-trusted pooled connection after the second easy handle explicitly selected an empty CA file. The same explicit policy on a fresh cache failed certificate verification (`CURLE_PEER_FAILED_VERIFICATION`, code 60). The fixed commit did not reuse that connection and failed the second request closed.\n- **Parity:** `full`.\n- **Not demonstrated:** No code execution, memory corruption, or privilege escalation was claimed or attempted.\n\n## Root Cause\n\n`Curl_ssl_easy_config_complete()` determines whether native CA trust is active from `CURLOPT_SSL_OPTIONS`, native-CA build defaults, and whether the application explicitly supplied CA material. In the vulnerable parent, that result was written to `ssl_config_data.native_ca_store`. Connection pooling, however, calls `Curl_ssl_conn_config_match()`, which compares only fields in `ssl_primary_config` through `match_ssl_primary_config()`. Thus, two easy handles could have equal compared values such as `ssl_options` and final `CAfile`, while differing in the effective native CA decision. The pooled connection carried the first handle's already completed trust decision into the second transfer.\n\nThe reproducer creates exactly this state without mocking libcurl: both handles resolve to the same empty CA-file path and use `ssl_options=0`; the first gets the path as a build default, which leaves implicit native CA active, while the second explicitly sets the same path, marking CA material custom and disabling implicit native CA. The vulnerable matcher treats these configurations as equal. Fix commit [`7be1e70cb6bc`](https://github.com/curl/curl/commit/7be1e70cb6bcd83e130ecfe8cb91b6a7dcdeff42) moves `native_ca_store` into `ssl_primary_config`, clones it into connections, compares it in `match_ssl_primary_config()`, and updates the OpenSSL, GnuTLS, rustls, and wolfSSL backends to consult connection-primary state.\n\n## Reproduction Steps\n\n1. Run `bundle/repro/reproduction_steps.sh` from any directory (the script derives or honors `PRUVA_ROOT`).\n2. The script uses the prepared project cache when available, resolves the fixed commit and its exact parent, verifies the patch hunk is absent/present as expected, and builds both commits with shared libcurl, GnuTLS, and `CURL_CA_NATIVE=ON`.\n3. It creates a localhost HTTPS certificate trusted only through GnuTLS system/native trust, starts a keep-alive HTTPS origin, compiles the real libcurl multi-API harness, and executes two vulnerable plus two fixed attempts.\n4. Expected result: the script exits 0 and prints `CONFIRMED`. Vulnerable logs contain `ORACLE=VULNERABLE_WRONG_TRUST_REUSE`; fixed logs contain `ORACLE=FIXED_POLICY_ISOLATION`.\n\n## Evidence\n\n- **Runtime contract:** `bundle/repro/runtime_manifest.json` binds target commit `7ea37abc6ac0120ba5f6d94be8d196f7cf1506bb`, tested build identities, and SHA-256 digests for all proof artifacts.\n- **Vulnerable observations:** `bundle/repro/vulnerable-attempt-1.log` and `vulnerable-attempt-2.log` show:\n  - `native-default-prime code=0 ... new_connects=1 http=200`\n  - `Reusing existing https: connection with host localhost`\n  - `custom-same-path-shared-cache code=0 ... new_connects=0 http=200`\n  - Fresh control: `code=60 ... new_connects=1 http=0`\n  - `ORACLE=VULNERABLE_WRONG_TRUST_REUSE`\n- **Fixed negative controls:** `bundle/repro/fixed-attempt-1.log` and `fixed-attempt-2.log` show the shared-cache custom policy creates a new connection and fails with code 60, followed by `ORACLE=FIXED_POLICY_ISOLATION`.\n- **Target/linker identity:** `bundle/repro/build-identity-vulnerable.txt` and `build-identity-fixed.txt` record commits, curl/GnuTLS versions, `ldd` resolution to each intended built `libcurl.so.4`, and binary SHA-256 values.\n- **Origin evidence:** `bundle/repro/server.log` records readiness and the six successful HTTP requests expected across two vulnerable reuse attempts plus two fixed priming requests.\n- **Diagnostics:** `bundle/logs/reproduction_steps.log`, `build-vulnerable*.log`, and `build-fixed*.log`.\n- **Environment:** Linux x86_64, GnuTLS 3.8.12, libcurl 8.22.0-DEV source at the exact vulnerable parent/fixed commits. Although the upstream advisory states deployed exposure exists on Windows/macOS, `CURL_CA_NATIVE=ON` with GnuTLS exposes the same real libcurl native-store decision and connection matcher on this Linux worker, allowing direct runtime validation of the root cause and fixed divergence.\n\n## Recommendations / Next Steps\n\n- Upgrade to curl 8.22.0 or later, or apply commit `7be1e70cb6bc`.\n- As an interim mitigation, use `CURLOPT_FORBID_REUSE` for transfers using native CA trust, as recommended by the upstream advisory.\n- Keep every certificate-verification option that affects connection compatibility in the primary connection configuration and include it in clone, session/cache, and reuse comparisons.\n- Add upstream regression coverage equivalent to this proof for each native-CA-capable TLS backend, plus Windows and macOS product builds.\n\n## Additional Notes\n\n- The final reproduction script passed twice consecutively. Each invocation itself ran two vulnerable and two fixed attempts, yielding four fresh observations per role across final verification.\n- The script restores the host trust store during cleanup and generates its server certificate and server helper at runtime.\n- The only non-generated bundle dependency is `bundle/repro/native_ca_reuse_harness.c`; all other runtime helpers, keys, certificates, builds, and logs are created by the script.\n- The proof uses a real libcurl library API boundary and a real TLS/HTTP exchange, not a parser reimplementation, mock result, sanitizer, or source-only assertion.\n","cve_id":"CVE-2026-80231","cwe_id":"CWE-488: Exposure of Data Element to Wrong Session.","source_url":"https://curl.se/docs/CVE-2026-80231.html","package":{"name":"curl/libcurl","ecosystem":"github","affected_versions":"curl 7.71.0 through 8.21.0 inclusive."},"reproduced_at":"2026-09-11T10:13:11.073809+00:00","duration_secs":2428.9158,"tool_calls":329,"handoffs":2,"total_cost_usd":17.287764,"agent_costs":{"claim_matcher":0.022299,"judge":0.858612,"learning_policy":0.010712,"repro":9.225094,"support":0.297716,"vuln_variant":6.873331},"cost_breakdown":{"claim_matcher":{"gpt-5.4-mini-2026-03-17":0.022299},"judge":{"gpt-5.6-sol":0.858612},"learning_policy":{"gpt-5.4-mini-2026-03-17":0.010712},"repro":{"gpt-5.6-sol":9.225094},"support":{"gpt-5.6-sol":0.297716},"vuln_variant":{"gpt-5.6-sol":6.873331}},"vulnerable_version_variant_outcome":"unknown","fix_bypass_outcome":"not_found","variant_disclosure_state":"not_applicable","quality":{"confidence":"high","idempotent_verified":false,"community_verifications":0},"evidence":{"workflow":{"profile":"known_vulnerability","schema_version":2,"stages":["support","claim_contract","repro","judge","vuln_variant"]}},"environment":{"sandbox_image":"ghcr.io/n3mes1s/pruva-sandbox@sha256:8096b2518d6022e13d68f885c3b8ded6b4fe607098b1a1ccbfb99abc004d1dc1"},"published_at":"2026-09-11T10:13:11.798062+00:00","retracted":false,"artifacts":[{"path":"bundle/repro/rca_report.md","filename":"rca_report.md","size":7280,"category":"analysis"},{"path":"bundle/repro/reproduction_steps.sh","filename":"reproduction_steps.sh","size":11754,"category":"reproduction_script"},{"path":"bundle/repro/build-identity-fixed.txt","filename":"build-identity-fixed.txt","size":2460,"category":"other"},{"path":"bundle/repro/build-identity-vulnerable.txt","filename":"build-identity-vulnerable.txt","size":2480,"category":"other"},{"path":"bundle/repro/fixed-attempt-2.log","filename":"fixed-attempt-2.log","size":3398,"category":"log"},{"path":"bundle/repro/native_ca_reuse_harness.c","filename":"native_ca_reuse_harness.c","size":4505,"category":"other"},{"path":"bundle/repro/runtime_manifest.json","filename":"runtime_manifest.json","size":2201,"category":"other"},{"path":"bundle/repro/server-cert.pem","filename":"server-cert.pem","size":1151,"category":"other"},{"path":"bundle/repro/source-identity.txt","filename":"source-identity.txt","size":143,"category":"other"},{"path":"bundle/repro/validation_verdict.json","filename":"validation_verdict.json","size":1378,"category":"other"}]}