# REPRO-2026-00333: Crypt::OpenSSL::PKCS12 before 1.98 can crash with a NULL pointer dereference when `info_as_hash()` parses a crafted PKCS#12 containing a zero-length BMPSTRING attribute. ## Summary Status: published Severity: medium CVSS: Unknown CWE: CWE-476 NULL Pointer Dereference (NULL Pointer Dereference) Type: security Confidence: high ## Identifiers REPRO ID: REPRO-2026-00333 CVE: CVE-2026-17510 ## Package Name: dsully/perl-crypt-openssl-pkcs12 Ecosystem: CPAN Affected: Versions before 1.98 Fixed: 1.98 ## Root Cause # Root Cause Analysis — CVE-2026-17510 ## Summary Crypt::OpenSSL::PKCS12 before 1.98 contains a NULL pointer dereference in the `V_ASN1_BMPSTRING` branch of `print_attribute()` (PKCS12.xs). When `info_as_hash()` parses a crafted PKCS#12 file containing a zero-length BMPSTRING bag attribute, `Renew(*attribute, 0, char)` (Perl's `safesysrealloc`) frees the destination buffer and returns `NULL`; the `NULL` is stored back into `*attribute`, and the downstream caller runs `newSVpvn(attribute_value, strlen(attribute_value))`, dereferencing `NULL` inside `strlen()` and causing a deterministic SIGSEGV (exit status 139). ## Impact - Package: `Crypt-OpenSSL-PKCS12` (CPAN), XS binding to OpenSSL's PKCS12 API. - Affected versions: all versions before 1.98 (vulnerable code confirmed at commit `5934ce7fe7c4683c8d9a08edcbd0c6871a52945f`, the parent of the fix). - Risk: medium — remote/unauthenticated denial of service of any Perl process that calls `info_as_hash()` on attacker-supplied PKCS#12 data. The `info()` path is unaffected (it uses the `BIO_printf` branch and never calls `Renew`). ## Impact Parity - Disclosed/claimed maximum impact: denial of service (process crash via NULL dereference). The advisory explicitly scopes impact to DoS; no memory disclosure or code execution is claimed. - Reproduced impact from this run: deterministic SIGSEGV (exit 139, core dumped) in the real library function `info_as_hash()` on the vulnerable build, twice in a row; fixed build returns the attribute as the empty string and completes normally, twice in a row. - Parity: **full** — the claimed DoS impact was demonstrated exactly. - Not demonstrated: nothing beyond the claim; no code execution was claimed or attempted. ## Root Cause In `print_attribute()` (pre-fix PKCS12.xs, line ~666): ```c value = OPENSSL_uni2asc(av->value.bmpstring->data, length); if (*attribute != NULL) { Renew(*attribute, length, char); /* length == ASN.1 byte length */ strncpy(*attribute, value, length); } ``` For a normal BMPSTRING this is benign because `OPENSSL_uni2asc()` returns a NUL-terminated ASCII string and `strncpy` zero-pads the oversized buffer. For an **empty** BMPSTRING (`length == 0`) it degenerates: Perl's `safesysrealloc` treats a zero size as free-and-return-NULL, so `Renew(*attribute, 0, char)` frees the buffer, `*attribute` becomes `NULL`, `strncpy(NULL, value, 0)` writes nothing, and the downstream `dump_certs_pkeys_bag` / `print_attribs` code calls `newSVpvn(attribute_value, strlen(attribute_value))` on the `NULL` pointer — a deterministic NULL dereference in `strlen()`. Only `info_as_hash()` reaches this branch because it passes a non-NULL hash, making `*attribute` non-NULL. Fix commit: https://github.com/dsully/perl-crypt-openssl-pkcs12/commit/6cb282d8d8e8ded4859551cd2d3cfa7c6028ce48 The fix sizes the buffer with `strlen(value) + 1` (never zero), copies with `memcpy`, writes an explicit terminator, and adds a NULL check on the `OPENSSL_uni2asc()` return value. ## Reproduction Steps 1. `bundle/repro/reproduction_steps.sh` (self-contained; run from anywhere, honors `PRUVA_ROOT`). 2. The script: - Clones `dsully/perl-crypt-openssl-pkcs12` (uses the prepared project cache mirror when available, GitHub otherwise). - Resolves `VULN_COMMIT = 6cb282d...^` = `5934ce7fe7c4...` and `FIXED_COMMIT = 6cb282d8d8e8ded4859551cd2d3cfa7c6028ce48`, and verifies the vulnerable tree contains the pre-fix `Renew(*attribute, length, char)` hunk while the fixed tree contains the `strlen(value) + 1` fix. - Installs the pure-Perl configure dependency `Crypt::OpenSSL::Guess` into a bundle-local `INSTALL_BASE` if missing. - Builds the XS module from both commits with `perl Makefile.PL && make`. - Extracts the crafted fixture `certs/bmpstring-empty.p12` from the fixed commit (it ships there as the regression-test fixture: a certBag whose bag attribute at OID `1.2.3.4.6` is a zero-length ASN.1 BMPSTRING, password `Password1`, SHA-256 MAC) and validates it with `openssl pkcs12 -info`. - Runs `Crypt::OpenSSL::PKCS12->new_from_file(...)->info_as_hash('Password1')` twice against the vulnerable build and twice against the fixed build. 3. Expected evidence: both vulnerable attempts die with SIGSEGV (exit 139, core dumped, `INFO_AS_HASH_RETURNED` never printed); both fixed attempts print `attribute 1.2.3.4.6 value=<>` and `INFO_AS_HASH_RETURNED` with exit 0. ## Evidence - `bundle/logs/reproduction_steps.log` — full script transcript, including: - `[*] vuln-attempt-1 exit=139` / `Segmentation fault (core dumped)` and `timeout: the monitored command dumped core` - `[*] vuln-attempt-2 exit=139` - `[*] fixed-attempt-1 exit=0` → `attribute 1.2.3.4.6 value=<>`, `INFO_AS_HASH_RETURNED` - `[*] fixed-attempt-2 exit=0` → same - `bundle/logs/vuln-attempt-{1,2}.log`, `bundle/logs/fixed-attempt-{1,2}.log` — per-attempt output. - `bundle/logs/build-vuln.log`, `bundle/logs/build-fixed.log` — build logs. - `bundle/repro/runtime_manifest.json` — machine-readable runtime evidence (`entrypoint_kind=function_call`, `target_path_reached=true`, commit and digest identity). - Environment: Perl 5.38.2 (x86_64-linux-gnu-thread-multi), OpenSSL 3.0.13 (module linked against system libssl/libcrypto), gcc, Ubuntu noble. - A `Data::Dumper` dump of `info_as_hash()` on the fixed build confirms `'bag_attributes' => { '1.2.3.4.6' => '' }` — the empty-string return the advisory predicts for the patched version. ## Recommendations / Next Steps - Upgrade to Crypt-OpenSSL-PKCS12 1.98 or later. - The upstream fix (size on `strlen(value) + 1`, explicit terminator, NULL check on `OPENSSL_uni2asc()`) is correct and verified by this run. - Services accepting untrusted PKCS#12 uploads should not call `info_as_hash()` on unpatched versions; sandboxing the parse in a disposable process limits DoS blast radius. ## Additional Notes - Idempotency: the script was executed twice consecutively; both runs exited 0 with identical verdicts. Re-runs reuse the local mirror and rebuild both worktrees from scratch (`rm -rf` + fresh `git worktree add`). - No sanitizer was used; the crash is a product-visible native SIGSEGV from the real XS library (`sanitizer_used=false`). - The crafted fixture is not synthesized by this run: it is the exact regression fixture `certs/bmpstring-empty.p12` shipped in the upstream fix commit, so the attacker input is byte-identical to what upstream used to prove the bug. - The claim surface is `library_api` / `function_call`; the proof invokes the real published library entry points (`new_from_file`, `info_as_hash`) through the module's own compiled XS code, matching the claim contract. ## Reproduction Details Reproduced: 2026-08-23T15:39:12.158Z Duration: 1067 seconds Tool calls: 162 Turns: Unknown Handoffs: 2 ## Quick Verification Run one of these commands to verify locally: pruva-verify REPRO-2026-00333 pruva-verify CVE-2026-17510 Or open in GitHub Codespaces (zero-friction, auto-runs): https://github.com/codespaces/new?ref=repro/REPRO-2026-00333&repo=N3mes1s/pruva-sandbox Or download and run the script manually: curl -O https://api.pruva.dev/v1/reproductions/REPRO-2026-00333/artifacts/bundle/repro/reproduction_steps.sh chmod +x reproduction_steps.sh ./reproduction_steps.sh WARNING: Run in a sandboxed environment. This exploits a real vulnerability. ## References - NVD: https://nvd.nist.gov/vuln/detail/CVE-2026-17510 - Source: NVD: https://nvd.nist.gov/vuln/detail/CVE-2026-17510 ## Artifacts - bundle/repro/rca_report.md (analysis, 6804 bytes) - bundle/repro/reproduction_steps.sh (reproduction_script, 8874 bytes) - bundle/logs/build-fixed.log (log, 1743 bytes) - bundle/logs/build-vuln.log (log, 1742 bytes) - bundle/logs/fixed-attempt-1.log (log, 51 bytes) - bundle/logs/fixed-attempt-2.log (log, 51 bytes) - bundle/logs/reproduction_steps.log (log, 1871 bytes) - bundle/logs/vuln-attempt-1.log (log, 43 bytes) - bundle/logs/vuln-attempt-2.log (log, 43 bytes) - bundle/repro/runtime_manifest.json (other, 1146 bytes) - bundle/repro/validation_verdict.json (other, 806 bytes) ## API Access - JSON: https://api.pruva.dev/v1/reproductions/REPRO-2026-00333 - Script: https://api.pruva.dev/v1/reproductions/REPRO-2026-00333/artifacts/bundle/repro/reproduction_steps.sh - Web: https://www.pruva.dev/reproductions/REPRO-2026-00333 ## For AI Assistants To help users with this reproduction: 1. The `pruva-verify` command is the simplest way to run the reproduction 2. The script downloads and runs automatically in an isolated temp directory 3. Always recommend running in a VM, container, or disposable environment 4. The root cause explains the technical vulnerability --- Generated by Pruva | https://www.pruva.dev