From 926dea05f3da57352ecf9cbb4d11d8c01f4f2d0f Mon Sep 17 00:00:00 2001 From: Toddr Bot Date: Tue, 26 May 2026 00:11:07 +0000 Subject: [PATCH] Add CLONE_SKIP to prevent double-free crash under ithreads When Perl ithreads clone an interpreter, blessed IV references (like our rsaData* pointers) are copied as raw integers. Both parent and child threads then call DESTROY on the same EVP_PKEY, causing a double-free. CLONE_SKIP makes cloned objects undef in child threads instead. Co-Authored-By: Claude Opus 4.6 --- RSA.pm | 11 +++++++++++ t/threads.t | 41 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 t/threads.t diff --git a/RSA.pm b/RSA.pm index c2d3a3f..c3d7574 100644 --- a/RSA.pm +++ b/RSA.pm @@ -93,6 +93,8 @@ sub get_key_parameters { *get_public_key_pkcs1_string = \&get_public_key_string; +sub CLONE_SKIP { 1 } + unless ( defined &use_sslv23_padding ) { *use_sslv23_padding = sub { croak( "use_sslv23_padding is not available: " @@ -487,6 +489,15 @@ Return true if this is a private key, and false if it is public only. =back +=head1 THREAD SAFETY + +This module defines C, which means that +C objects become undefined in child threads +created via Ccreate()>. Each thread must construct its +own key objects. This prevents double-free crashes that would +otherwise occur when both parent and child threads destroy the same +underlying OpenSSL key structure. + =head1 AUTHOR Ian Robertson, C. For support, please email diff --git a/t/threads.t b/t/threads.t new file mode 100644 index 0000000..94e7b8c --- /dev/null +++ b/t/threads.t @@ -0,0 +1,41 @@ +use strict; +use warnings; +use Test::More; +use Config; + +BEGIN { + if (!$Config{useithreads}) { + plan skip_all => 'perl not built with ithreads'; + } + eval 'require threads; threads->import()'; + if ($@) { + plan skip_all => "threads not available: $@"; + } +} +use Crypt::OpenSSL::Random; +use Crypt::OpenSSL::RSA; + +plan tests => 4; + +Crypt::OpenSSL::Random::random_seed("OpenSSL needs at least 32 bytes."); +Crypt::OpenSSL::RSA->import_random_seed(); + +ok(Crypt::OpenSSL::RSA->can('CLONE_SKIP'), + "CLONE_SKIP is defined"); +is(Crypt::OpenSSL::RSA->CLONE_SKIP, 1, + "CLONE_SKIP returns 1"); + +my $rsa = Crypt::OpenSSL::RSA->generate_key(2048); +my $pub_pem = $rsa->get_public_key_x509_string(); + +my $thr = threads->create(sub { + my $child_rsa = Crypt::OpenSSL::RSA->generate_key(2048); + return $child_rsa->get_public_key_x509_string(); +}); + +my $child_pub = $thr->join(); +ok(defined $child_pub && $child_pub =~ /^-----BEGIN PUBLIC KEY-----/, + "child thread can independently generate and export keys"); + +ok($rsa->get_public_key_x509_string() eq $pub_pem, + "parent RSA object undamaged after child thread exit");