CVE-2026-17510: Verified Reproduction
CVE-2026-17510: 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.
CVE-2026-17510 is verified against dsully/perl-crypt-openssl-pkcs12 · CPAN. Affected versions: Versions before 1.98. Fixed in 1.98. This medium reproduction includes runnable sandbox proof, artifacts, and a plain-text agent view under REPRO-2026-00333.
What Is CVE-2026-17510?
CVE-2026-17510 is a medium-severity vulnerability affecting dsully/perl-crypt-openssl-pkcs12 Versions before 1.98. Pruva has independently reproduced it and publishes a verified, runnable proof-of-concept (reproduction REPRO-2026-00333).
CVE-2026-17510 Severity
CVE-2026-17510 is rated medium severity.
Medium — meaningful risk under specific conditions. Schedule a fix in the normal cycle.
Affected dsully/perl-crypt-openssl-pkcs12 Versions
dsully/perl-crypt-openssl-pkcs12 · CPAN versions Versions before 1.98 are affected.
How to Reproduce CVE-2026-17510
pruva-verify REPRO-2026-00333 curl -O https://www.pruva.dev/api/v1/reproductions/REPRO-2026-00333/artifacts/bundle/repro/reproduction_steps.sh && chmod +x reproduction_steps.sh && ./reproduction_steps.sh Proof of Reproduction for CVE-2026-17510
- reached the target end-to-end
- crash observed
- high confidence
- the upstream fix blocks the same trigger
crafted PKCS#12 file with a zero-length BMPSTRING bag attribute (OID 1.2.3.4.6), password 'Password1'
- Crypt::OpenSSL::PKCS12
- new_from_file()
- info_as_hash()
- print_attribute() V_ASN1_BMPSTRING branch
- Renew(*attribute, 0, char) returns NULL
- strlen(NULL) SIGSEGV
How the agent worked
Root Cause and Exploit Chain for CVE-2026-17510
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).
- 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. Theinfo()path is unaffected (it uses theBIO_printfbranch and never callsRenew).
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):
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
bundle/repro/reproduction_steps.sh(self-contained; run from anywhere, honorsPRUVA_ROOT).- The script:
- Clones
dsully/perl-crypt-openssl-pkcs12(uses the prepared project cache mirror when available, GitHub otherwise). - Resolves
VULN_COMMIT = 6cb282d...^=5934ce7fe7c4...andFIXED_COMMIT = 6cb282d8d8e8ded4859551cd2d3cfa7c6028ce48, and verifies the vulnerable tree contains the pre-fixRenew(*attribute, length, char)hunk while the fixed tree contains thestrlen(value) + 1fix. - Installs the pure-Perl configure dependency
Crypt::OpenSSL::Guessinto a bundle-localINSTALL_BASEif missing. - Builds the XS module from both commits with
perl Makefile.PL && make. - Extracts the crafted fixture
certs/bmpstring-empty.p12from the fixed commit (it ships there as the regression-test fixture: a certBag whose bag attribute at OID1.2.3.4.6is a zero-length ASN.1 BMPSTRING, passwordPassword1, SHA-256 MAC) and validates it withopenssl pkcs12 -info. - Runs
Crypt::OpenSSL::PKCS12->new_from_file(...)->info_as_hash('Password1')twice against the vulnerable build and twice against the fixed build.
- Clones
- Expected evidence: both vulnerable attempts die with SIGSEGV (exit 139,
core dumped,
INFO_AS_HASH_RETURNEDnever printed); both fixed attempts printattribute 1.2.3.4.6 value=<>andINFO_AS_HASH_RETURNEDwith exit 0.
Evidence
bundle/logs/reproduction_steps.log— full script transcript, including:[*] vuln-attempt-1 exit=139/Segmentation fault (core dumped)andtimeout: 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::Dumperdump ofinfo_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 onOPENSSL_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+ freshgit 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.p12shipped 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.
CVE-2026-17510 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.
which perl cpanm gcc openssl; perl -v | head -3; openssl version/usr/bin/perl /usr/bin/gcc /usr/bin/openssl This is perl 5, version 38, subversion 2 (v5.38.2) built for x86_64-linux-gnu-thread-multi (with 54 registered patches, see perl -V for more detail) OpenSSL 3.0.13 30 Jan 2024 (Library: OpenSSL 3.0.13 30 Jan 2024)
ls /data/pruva/project-cache/.run-views/bb68b3b73827c0bb615da89833d3f55e5a06e914e59e6940ac1a689346dd588f/ /data/pruva/project-cache/.run-views/bb68b3b73827c0bb615da89833d3f55e5a06e914e59e6940ac1a689346dd588f/repo-mirrors 2>&1; perl -MExtUtils::Embed -e ccopts 2>&1; dpkg -l | grep -E 'libperl-dev|libssl-dev' 2>&1 | head/data/pruva/project-cache/.run-views/bb68b3b73827c0bb615da89833d3f55e5a06e914e59e6940ac1a689346dd588f/: repo-mirrors /data/pruva/project-cache/.run-views/bb68b3b73827c0bb615da89833d3f55e5a06e914e59e6940ac1a689346dd588f/repo-mirrors: -D_REENTRANT -D_GNU_SOURCE -DDEBIAN -fwrapv -fno-strict-aliasing -pipe -I/usr/local/include -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 -I/usr/lib/x86_64-linux-gnu/perl/5.38/CORE ii libssl-dev:amd64 3.0.13-0ubuntu3.12 amd64 Secure Sockets Layer toolkit - development files
CACHE=/data/pruva/project-cache/.run-views/bb68b3b73827c0bb615da89833d3f55e5a06e914e59e6940ac1a689346dd588f; git clone --mirror https://github.com/dsully/perl-crypt-openssl-pkcs12.git "$CACHE/repo-mirrors/perl-crypt-openssl-pkcs12.git" 2>&1 | tail -2 && git clone "$CACHE/repo-mirrors/perl-crypt-openssl-pkcs12.git" "$CACHE/repo" 2>&1 | tail -2 && git -C "$CACHE/repo" log --oneline -5Cloning into bare repository '/data/pruva/project-cache/.run-views/bb68b3b73827c0bb615da89833d3f55e5a06e914e59e6940ac1a689346dd588f/repo-mirrors/perl-crypt-openssl-pkcs12.git'... warning: unable to access '/home/pruva/.config/git/ignore': Permission denied warning: unable to access '/home/pruva/.config/git/attributes': Permission denied 62e45cd Bump astral-sh/setup-uv from 8.3.2 to 9.0.0 in the major-updates group (#73) 46429a0 Prepare 1.98 release 9412400 Fix test triggers uninitialized error 6cb282d Fix NULL pointer dereference in print_attribute V_ASN1_BMPSTRING branch 5934ce7 Merge pull request #74 from dsully/dependabot/github_actions/minor-and-patch-013d59eb7c
CACHE=/data/pruva/project-cache/.run-views/bb68b3b73827c0bb615da89833d3f55e5a06e914e59e6940ac1a689346dd588f; git -C "$CACHE/repo" show 6cb282d --stat; echo ---; git -C "$CACHE/repo" show 6cb282d -- PKCS12.xs | head -80{"cwd":"/data/pruva/runs/b7465f5a-c19a-4342-8c26-496127abd9a5","duration_ms":8,"exit_code":0,"stderr":"warning: unable to access '/home/pruva/.config/git/attributes': Permission denied\nwarning: unable to access '/home/pruva/.config/git/attributes': Permission denied\nwarning: unable to access '/home/pruva/.config/git/attributes': Permission denied\n","stdout":"commit 6cb282d8d8e8ded4859551cd2d3cfa7c6028ce48\nAuthor: Timothy Legge <timlegge@gmail.com>\nDate: Mon Jun 8 21:31:02 2026 -0300\n\n Fix NULL pointer dereference in print_attribute V_ASN1_BMPSTRING branch\n \n Follow-up to bd04fa7 (\"Harden print_attribute BMPSTRING/UTF8STRING\n paths\"), which rewrote the UTF8STRING branch but left BMPSTRING on the\n old Renew(length)/strncpy(length) shape.\n \n For a normal BMPSTRING that shape is benign: OPENSSL_uni2asc returns a\n NUL-terminated string ~length/2 bytes long, so strncpy null-pads the\n oversized `length`-byte buffer and the result stays terminated. But an\n *empty* BMPSTRING (length == 0) degenerates. Perl's safesysrealloc\n treats a zero size as free-and-return-NULL, so Renew(*attribute, 0,\n char) frees the buffer and leaves *attribu… [truncated]CACHE=/data/pruva/project-cache/.run-views/bb68b3b73827c0bb615da89833d3f55e5a06e914e59e6940ac1a689346dd588f; cd "$CACHE/repo"; cat Makefile.PL; echo ===; git show 6cb282d:t/pkcs12-info-bmpstring-attribute.t | head -50{"cwd":"/data/pruva/runs/b7465f5a-c19a-4342-8c26-496127abd9a5","duration_ms":7,"exit_code":0,"stderr":"","stdout":"# This Makefile.PL for Crypt-OpenSSL-PKCS12 was generated by\n# Dist::Zilla::Plugin::MakeMaker::Awesome 0.49.\n# Don't edit it but the dist.ini and plugins used to construct it.\n\nuse strict;\nuse warnings;\n\nuse 5.014000;\nuse ExtUtils::MakeMaker;\n\nuse Config;\nuse Crypt::OpenSSL::Guess qw(openssl_lib_paths openssl_inc_paths);\n\nmy %args = (\n LIBS => [openssl_lib_paths().' -lssl -lcrypto'],\n INC => openssl_inc_paths(),\n);\n\nmy $cc_option_flags = \" -O2 -g\";\nmy $cc_defines = '';\n\nif ($Config::Config{cc} =~ /gcc/i) {\n $cc_option_flags .= $ENV{AUTHOR_TESTING} ? ' -Wall -Werror' : ' -Wall';\n} else {\n $cc_option_flags .= '';\n}\n\nif ($Config{gccversion} =~ /llvm/i) {\n $cc_option_flags .= ' -Wall -Wno-deprecated-declarations';\n\n if ($Config{gccversion} =~ /llvm 12/i) {\n $cc_option_flags .= ' -Wno-compound-token-split-by-macro';\n }\n\n if ($Config{gccversion} =~ /llvm 13/i) {\n $cc_option_flags .= ' -Wno-compound-token-split-by-macro';\n }\n\n if ($Config{gccversion} =~ /llvm 14/i) {\n $cc_option_flags .= ' -Wno-compound-token-sp… [truncated]Artifacts and Evidence for CVE-2026-17510
Scripts, logs, diffs, and output captured during the reproduction.
How to Fix CVE-2026-17510
Upgrade dsully/perl-crypt-openssl-pkcs12 · CPAN to 1.98 or later.
FAQ: CVE-2026-17510
Is CVE-2026-17510 exploitable?
How severe is CVE-2026-17510?
What type of vulnerability is CVE-2026-17510?
Which versions of dsully/perl-crypt-openssl-pkcs12 are affected by CVE-2026-17510?
Is there a fix for CVE-2026-17510?
How can I reproduce CVE-2026-17510?
Is the CVE-2026-17510 reproduction verified?
References for CVE-2026-17510
Authoritative sources for CVE-2026-17510 — official vulnerability databases and the upstream advisory. Pruva's reproduction verifies the issue firsthand; these are the primary records to corroborate it.