[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