[packages/perl-Apache-Session] - security fixes: CVE-2025-40931 (predictable session ids), CVE-2013-10075 (deleted session revival)

arekm arekm at pld-linux.org
Mon Aug 3 22:48:46 CEST 2026


commit 4517c06193c37cf1b347b968a87f73f9e359e0ee
Author: Arkadiusz Miśkiewicz <arekm at maven.pl>
Date:   Mon Aug 3 22:48:01 2026 +0200

    - security fixes: CVE-2025-40931 (predictable session ids), CVE-2013-10075 (deleted session revival), unchecked GET_LOCK result; run tests
    - upstream is dead
    
    Rel 2

 perl-Apache-Session-CVE-2013-10075.patch    | 117 +++++++++++++++++++++++
 perl-Apache-Session-CVE-2025-40931.patch    | 143 ++++++++++++++++++++++++++++
 perl-Apache-Session-mysql-lock-result.patch | 131 +++++++++++++++++++++++++
 perl-Apache-Session.spec                    |  24 ++++-
 4 files changed, 412 insertions(+), 3 deletions(-)
---
diff --git a/perl-Apache-Session.spec b/perl-Apache-Session.spec
index aa56263..843bb16 100644
--- a/perl-Apache-Session.spec
+++ b/perl-Apache-Session.spec
@@ -1,21 +1,34 @@
+#
+# Conditional build:
+%bcond_without	tests	# do not perform "make test"
+
 %define		pdir	Apache
 %define		pnam	Session
 Summary:	Apache::Session - a persistence framework for session data
 Summary(pl.UTF-8):	Apache::Session - szkielet trwałości dla danych w sesji
 Name:		perl-Apache-Session
 Version:	1.94
-Release:	1
+Release:	2
 Epoch:		1
 # same as perl
 License:	GPL v1+ or Artistic
 Group:		Development/Languages/Perl
 Source0:	http://www.cpan.org/modules/by-module/%{pdir}/%{pdir}-%{pnam}-%{version}.tar.gz
 # Source0-md5:	122b69a50cda8a22cb407d56c51a39ba
-URL:		http://search.cpan.org/dist/Apache-Session/
-BuildRequires:	perl-Digest-MD5
+Patch0:		%{name}-CVE-2025-40931.patch
+Patch1:		%{name}-CVE-2013-10075.patch
+Patch2:		%{name}-mysql-lock-result.patch
+URL:		https://metacpan.org/dist/Apache-Session
 BuildRequires:	perl-devel >= 1:5.8.0
 BuildRequires:	rpm-perlprov >= 4.1-13
 BuildRequires:	unzip
+%if %{with tests}
+BuildRequires:	perl-Crypt-URandom
+BuildRequires:	perl-DB_File
+BuildRequires:	perl-Test-Deep
+BuildRequires:	perl-Test-Exception
+BuildRequires:	perl-Test-Simple
+%endif
 BuildArch:	noarch
 BuildRoot:	%{tmpdir}/%{name}-%{version}-root-%(id -u -n)
 
@@ -36,12 +49,17 @@ z innymi serwerami HTTP, a także zupełnie poza serwerem HTTP.
 
 %prep
 %setup -q -n %{pdir}-%{pnam}-%{version}
+%patch -P0 -p1
+%patch -P1 -p1
+%patch -P2 -p1
 
 %build
 %{__perl} Makefile.PL \
 	INSTALLDIRS=vendor
 %{__make}
 
+%{?with_tests:%{__make} test}
+
 %install
 rm -rf $RPM_BUILD_ROOT
 install -d $RPM_BUILD_ROOT%{_examplesdir}/%{name}-%{version}
diff --git a/perl-Apache-Session-CVE-2013-10075.patch b/perl-Apache-Session-CVE-2013-10075.patch
new file mode 100644
index 0000000..1a4b179
--- /dev/null
+++ b/perl-Apache-Session-CVE-2013-10075.patch
@@ -0,0 +1,117 @@
+From: PLD Linux
+Subject: [PATCH] CVE-2013-10075: do not revive a deleted session
+
+After delete() the tied object cleared the DELETED flag and marked itself
+SYNCED, so it claimed to be in sync with a store it no longer existed in.
+Any later write was then issued as an update, and Store::File (O_CREAT) and
+Store::DB_File (plain hash assignment) recreated the session - with the data
+that was meant to be gone.
+
+Mark the object dead once the store has removed it, so save() stops writing.
+Fixing it here rather than in the two stores covers every store with one
+change, and matches what Store::DBI already does: an UPDATE against a
+deleted row affects no rows and quietly does nothing.
+
+Not done as a die() in the stores: save() runs from DESTROY, where Perl
+discards the exception, and dying there skips the release_all_locks() call
+that follows it - trading a data leak for a lock leak.
+
+https://github.com/chorny/Apache-Session/issues/6
+https://rt.cpan.org/Public/Bug/Display.html?id=83525
+--- Apache-Session-1.94.orig/lib/Apache/Session.pm	2020-09-18 23:56:08.000000000 +0200
++++ Apache-Session-1.94/lib/Apache/Session.pm	2026-08-03 22:34:40.361696542 +0200
+@@ -517,6 +517,10 @@
+ sub save {
+     my $self = shift;
+     
++    #once the store has dropped the session the object must stop writing,
++    #or the next update() resurrects what delete() removed
++    return if $self->{deleted};
++    
+     return unless (
+         $self->{status} & MODIFIED || 
+         $self->{status} & NEW      || 
+@@ -527,6 +531,7 @@
+ 
+     if ($self->{status} & DELETED) {
+         $self->{object_store}->remove($self);
++        $self->{deleted} = 1;
+         $self->{status} |= SYNCED;
+         $self->{status} &= ($self->{status} ^ MODIFIED);
+         $self->{status} &= ($self->{status} ^ DELETED);
+--- Apache-Session-1.94.orig/t/99delete.t	1970-01-01 01:00:00.000000000 +0100
++++ Apache-Session-1.94/t/99delete.t	1970-01-01 01:00:00.000000000 +0100
+@@ -0,0 +1,73 @@
++use Test::More;
++use File::Temp qw[tempdir];
++use Cwd qw[getcwd];
++
++plan skip_all => "Optional module (DB_File) not installed"
++  unless eval {
++               require DB_File;
++              };
++
++plan tests => 4;
++
++use_ok 'Apache::Session::File';
++use_ok 'Apache::Session::DB_File';
++
++my $origdir = getcwd;
++my $tempdir = tempdir( DIR => '.', CLEANUP => 1 );
++chdir( $tempdir );
++
++#a write issued after delete must not bring the session back to life
++
++{
++    my $package = 'Apache::Session::File';
++    my %tie_params = (
++        Directory     => '.',
++        LockDirectory => '.',
++    );
++
++    my %session;
++    tie %session, $package, undef, { %tie_params };
++    my $id = $session{_session_id};
++    $session{foo} = 'bar';
++    untie %session;
++    undef %session;
++
++    tie %session, $package, $id, { %tie_params };
++    tied(%session)->delete;
++    $session{foo} = 'revived';
++    untie %session;
++    undef %session;
++
++    ok( !-e "./$id", "deleted session is not re-created by a later write" );
++}
++
++{
++    my $package = 'Apache::Session::DB_File';
++    my $dbfile  = './delete.db';
++    my %tie_params = (
++        FileName      => $dbfile,
++        LockDirectory => '.',
++    );
++
++    my %session;
++    tie %session, $package, undef, { %tie_params };
++    my $id = $session{_session_id};
++    $session{foo} = 'bar';
++    untie %session;
++    undef %session;
++
++    tie %session, $package, $id, { %tie_params };
++    tied(%session)->delete;
++    $session{foo} = 'revived';
++    untie %session;
++    undef %session;
++
++    my %dbm;
++    tie %dbm, 'DB_File', $dbfile;
++    my $present = exists $dbm{$id};
++    untie %dbm;
++
++    ok( !$present, "deleted session is not re-created by a later write" );
++}
++
++chdir( $origdir );
diff --git a/perl-Apache-Session-CVE-2025-40931.patch b/perl-Apache-Session-CVE-2025-40931.patch
new file mode 100644
index 0000000..7e6bc44
--- /dev/null
+++ b/perl-Apache-Session-CVE-2025-40931.patch
@@ -0,0 +1,143 @@
+From: PLD Linux
+Subject: [PATCH] CVE-2025-40931: generate session ids from the system CSPRNG
+
+Apache::Session::Generate::MD5 is the default id generator. It seeded a
+double MD5 with rand(), time() and $$ - all guessable, and rand() is not a
+CSPRNG - so session ids were predictable.
+
+Read the id straight from the OS random source instead. Unlike Debian's
+variant (bug #930659) there is no eval fallback to the old code: a session
+id generator must fail closed, not silently degrade to a guessable id.
+
+The module keeps its name because configurations reference it by name.
+
+https://github.com/chorny/Apache-Session/issues/4
+https://rt.cpan.org/Ticket/Display.html?id=173631
+--- Apache-Session-1.94.orig/lib/Apache/Session/Generate/MD5.pm	2009-09-15 23:08:50.000000000 +0200
++++ Apache-Session-1.94/lib/Apache/Session/Generate/MD5.pm	2026-08-03 22:34:40.357550512 +0200
+@@ -1,7 +1,7 @@
+ #############################################################################
+ #
+ # Apache::Session::Generate::MD5;
+-# Generates session identifier tokens using MD5
++# Generates session identifier tokens from the system CSPRNG
+ # Copyright(c) 2000, 2001 Jeffrey William Baker (jwbaker at acm.org)
+ # Distribute under the Perl License
+ #
+@@ -11,22 +11,22 @@
+ 
+ use strict;
+ use vars qw($VERSION);
+-use Digest::MD5;
++use Crypt::URandom;
+ 
+ $VERSION = '2.12';
+ 
+ sub generate {
+     my $session = shift;
+     my $length = 32;
+-    
++
+     if (exists $session->{args}->{IDLength}) {
+         $length = $session->{args}->{IDLength};
+     }
+-    
+-    $session->{data}->{_session_id} = 
+-        substr(Digest::MD5::md5_hex(Digest::MD5::md5_hex(time(). {}. rand(). $$)), 0, $length);
+-    
+ 
++    #urandom() dies if the system CSPRNG is unavailable; that must abort
++    #session creation rather than fall back to a guessable id
++    $session->{data}->{_session_id} =
++        substr(unpack('H*', Crypt::URandom::urandom(int(($length + 1) / 2))), 0, $length);
+ }
+ 
+ sub validate {
+@@ -49,7 +49,7 @@
+ 
+ =head1 NAME
+ 
+-Apache::Session::Generate::MD5 - Use MD5 to create random object IDs
++Apache::Session::Generate::MD5 - Create random object IDs
+ 
+ =head1 SYNOPSIS
+ 
+@@ -60,12 +60,8 @@
+ =head1 DESCRIPTION
+ 
+ This module fulfills the ID generation interface of Apache::Session.  The
+-IDs are generated using a two-round MD5 of a random number, the time since the
+-epoch, the process ID, and the address of an anonymous hash.  The resultant ID
+-number is highly entropic on Linux and other platforms that have good
+-random number generators.  You are encouraged to investigate the quality of
+-your system's random number generator if you are using the generated ID
+-numbers in a secure environment.
++IDs are read from the operating system's cryptographically secure random
++number source using L<Crypt::URandom>.
+ 
+ This module can also examine session IDs to ensure that they are, indeed,
+ session ID numbers and not evil attacks.  The reader is encouraged to 
+--- Apache-Session-1.94.orig/t/99md5gen.t	2009-09-15 23:08:50.000000000 +0200
++++ Apache-Session-1.94/t/99md5gen.t	2026-08-03 22:34:40.358032874 +0200
+@@ -3,12 +3,12 @@
+ use File::Temp qw[tempdir];
+ use Cwd qw[getcwd];
+ 
+-plan skip_all => "Optional module (Digest::MD5) not installed"
++plan skip_all => "Optional module (Crypt::URandom) not installed"
+   unless eval {
+-               require Digest::MD5;
++               require Crypt::URandom;
+               };
+ 
+-plan tests => 33;
++plan tests => 34;
+ 
+ my $package = 'Apache::Session::Generate::MD5';
+ use_ok $package;
+@@ -17,6 +17,19 @@
+ #my $tempdir = tempdir( DIR => '.', CLEANUP => 1 );
+ #chdir( $tempdir );
+ 
++#the id must come from the system CSPRNG, not from rand()/time()/$$
++{
++    no warnings 'redefine';
++    local *Crypt::URandom::urandom =
++        sub { substr("\x01\x23\x45\x67\x89\xab\xcd\xef" x 4, 0, $_[0]) };
++
++    my $csprng = {};
++    Apache::Session::Generate::MD5::generate($csprng);
++
++    is $csprng->{data}->{_session_id}, '0123456789abcdef' x 2,
++       'session id is read from the system CSPRNG';
++}
++
+ my $session = {};
+ 
+ Apache::Session::Generate::MD5::generate($session);
+--- Apache-Session-1.94.orig/t/99flex.t	2010-08-18 12:04:58.000000000 +0200
++++ Apache-Session-1.94/t/99flex.t	2026-08-03 22:44:56.027094401 +0200
+@@ -11,10 +11,10 @@
+    #perl 5.6 does not likes this test. See RT#16539.
+ #  };
+ #use Module::Mask;my $mask = new Module::Mask ('Storable');
+-plan skip_all => "Optional modules (Fcntl, Digest::MD5) not installed"
++plan skip_all => "Optional modules (Fcntl, Crypt::URandom) not installed"
+   unless eval {
+                require Fcntl;
+-               require Digest::MD5;
++               require Crypt::URandom;
+               };
+ 
+ plan tests => 7;
+--- Apache-Session-1.94.orig/Makefile.PL	2020-09-18 01:46:14.000000000 +0200
++++ Apache-Session-1.94/Makefile.PL	2026-08-03 22:44:56.027418570 +0200
+@@ -22,7 +22,7 @@
+                      'constant'        => 0, #Available on CPAN now
+ #                     'MIME::Base64'    => 0, #new versions require 5.6
+ #                     'DB_File'         => 0, #test
+-                     'Digest::MD5'     => 0, #core from 5.8
++                     'Crypt::URandom'  => 0,
+                      'IO::File'        => 0, #core
+                     },
+     BUILD_REQUIRES => {
diff --git a/perl-Apache-Session-mysql-lock-result.patch b/perl-Apache-Session-mysql-lock-result.patch
new file mode 100644
index 0000000..f9e43c0
--- /dev/null
+++ b/perl-Apache-Session-mysql-lock-result.patch
@@ -0,0 +1,131 @@
+From: PLD Linux
+Subject: [PATCH] check whether GET_LOCK actually granted the lock
+
+acquire_read_lock() ran SELECT GET_LOCK(?, 3600), threw the result away and
+set $self->{lock} = 1 unconditionally. GET_LOCK returns 0 on timeout and
+NULL on error; neither is a DBI error, so the RaiseError above it does not
+catch them. After the hour elapsed the caller proceeded believing it held
+the lock.
+
+Apache::Session reads, modifies and writes the whole session blob, so two
+requests inside the same session overwrite each other - the later write
+silently discards the earlier one.
+
+Fetch the result and die when the lock was not granted. The timeout also
+stops being hardcoded, so a web application can give up sooner than an hour.
+--- Apache-Session-1.94.orig/lib/Apache/Session/Lock/MySQL.pm	2020-09-18 23:50:53.000000000 +0200
++++ Apache-Session-1.94/lib/Apache/Session/Lock/MySQL.pm	2026-08-03 22:34:40.362188658 +0200
+@@ -12,10 +12,12 @@
+ use strict;
+ 
+ use DBI;
+-use vars qw($VERSION);
++use vars qw($VERSION $LOCK_TIMEOUT);
+ 
+ $VERSION = '1.01';
+ 
++$Apache::Session::Lock::MySQL::LOCK_TIMEOUT = 3600;
++
+ sub new {
+     my $class = shift;
+     
+@@ -51,11 +53,20 @@
+     $self->{lockid} = "Apache-Session-$session->{data}->{_session_id}";
+     
+     #MySQL requires a timeout on the lock operation.  There is no option
+-    #to simply wait forever.  So we'll wait for a hour.
++    #to simply wait forever.
+     
+-    my $sth = $self->{dbh}->prepare_cached(q{SELECT GET_LOCK(?, 3600)}, {}, 1);
+-    $sth->execute($self->{lockid});
++    my $sth = $self->{dbh}->prepare_cached(q{SELECT GET_LOCK(?, ?)}, {}, 1);
++    $sth->execute($self->{lockid}, $LOCK_TIMEOUT);
++
++    #GET_LOCK returns 1 when the lock was taken, 0 on timeout and NULL on
++    #error.  Neither of the latter two is a DBI error, so RaiseError does not
++    #catch them and the caller would silently proceed without the lock.
++    my ($acquired) = $sth->fetchrow_array;
+     $sth->finish();
++
++    if (!$acquired) {
++        die "Could not acquire lock $self->{lockid}";
++    }
+     
+     $self->{lock} = 1;
+ }
+@@ -144,6 +155,14 @@
+      LockHandle => $dbh
+  };
+ 
++The lock is requested with a timeout of one hour.  If a request should give up
++sooner, lower it:
++
++ $Apache::Session::Lock::MySQL::LOCK_TIMEOUT = 60;
++
++Failing to acquire the lock within that time is fatal: proceeding without it
++would let two requests write the same session and lose each other's data.
++
+ =head1 AUTHOR
+ 
+ This module was written by Jeffrey William Baker <jwbaker at acm.org>.
+--- Apache-Session-1.94.orig/t/99mysqllockresult.t	1970-01-01 01:00:00.000000000 +0100
++++ Apache-Session-1.94/t/99mysqllockresult.t	1970-01-01 01:00:00.000000000 +0100
+@@ -0,0 +1,58 @@
++use Test::More;
++use Test::Exception;
++
++plan tests => 6;
++
++my $package = 'Apache::Session::Lock::MySQL';
++use_ok $package;
++
++#GET_LOCK reports failure in its result set, not as a DBI error, so these
++#cases need a handle that can hand back an arbitrary result
++
++{
++    package Fake::sth;
++    sub new       { my ($class, @row) = @_; return bless { row => [@row] }, $class }
++    sub execute   { my $self = shift; $self->{bound} = [@_]; return 1 }
++    sub fetchrow_array { return @{ $_[0]->{row} } }
++    sub finish    { return 1 }
++
++    package Fake::dbh;
++    sub new { my ($class, @row) = @_; return bless { row => [@row] }, $class }
++    sub prepare_cached {
++        my ($self, $sql) = @_;
++        $self->{sql} = $sql;
++        return $self->{sth} = Fake::sth->new(@{ $self->{row} });
++    }
++}
++
++sub session_for {
++    my $dbh = shift;
++    return {
++        args => { LockHandle => $dbh },
++        data => { _session_id => 'deadbeef' },
++    };
++}
++
++{
++    local $Apache::Session::Lock::MySQL::LOCK_TIMEOUT = 42;
++
++    my $dbh = Fake::dbh->new(1);
++    my $locker = $package->new;
++    lives_ok { $locker->acquire_read_lock(session_for($dbh)) } 'lock granted';
++    is $locker->{lock}, 1, 'locker records that it holds the lock';
++    is $dbh->{sth}->{bound}->[1], 42, 'the configured timeout is used';
++}
++
++{
++    my $dbh = Fake::dbh->new(0);
++    my $locker = $package->new;
++    dies_ok { $locker->acquire_read_lock(session_for($dbh)) }
++            'timed-out lock is fatal';
++}
++
++{
++    my $dbh = Fake::dbh->new(undef);
++    my $locker = $package->new;
++    dies_ok { $locker->acquire_read_lock(session_for($dbh)) }
++            'NULL from GET_LOCK is fatal';
++}
================================================================

---- gitweb:

http://git.pld-linux.org/gitweb.cgi/packages/perl-Apache-Session.git/commitdiff/4517c06193c37cf1b347b968a87f73f9e359e0ee



More information about the pld-cvs-commit mailing list