new file mode 100644
@@ -0,0 +1,195 @@
+From 99bbf2ab9d137e6f042a770bd3bbba3bc0b3a901 Mon Sep 17 00:00:00 2001
+From: Robert Rothenberg <perl@rhizomnic.com>
+Date: Sat, 17 Oct 2026 12:10:51 +0100
+Subject: [PATCH] fix: validate DBD::DBM MLDBM serializer before lock file is
+ created (CVE-2026-111879)
+
+This was a bug introduced by the fix for CVE-2026-78030.
+
+The exception unwound DBI::DBD::SqlEngine::Table::new before the Table object
+was blessed, so no DESTROY ever releases the lock, while $meta (with the
+open, locked lockfh) stays cached in $dbh->{sql_meta}{t_ser}. The next
+open of the table opens a second descriptor on the same .lck and blocks in
+flock() -- forever, because the holder is the same process.
+
+Why only the Gofer variant hung: in plain mode the DROP reuses the same
+$meta, so DBD::File's $meta->{lockfh} = $fh drops the old handle (and
+with it the lock) just before it calls flock. Under Gofer the DROP is
+served by a different server-side dbh with its own fresh $meta, so the
+stale handle is never replaced. The same thing happens without Gofer for
+any second handle or any second process: a separate process doing
+SELECT * FROM t_ser blocks until the first process exits (measured on
+1.652, 1.655 and master).
+
+This change also factors out code for releasing the lock into a separate
+method that is used by DESTROY.
+
+The version for DBD::File is bumped.
+
+CVE: CVE-2026-111879
+Upstream-Status: Backport [https://github.com/perl5-dbi/dbi/commit/99bbf2ab9d137e6f042a770bd3bbba3bc0b3a901]
+
+Backport Changes:
+- Retained DBD::File version 0.44 and omitted release notes.
+- Adapted the lock helper to the older close-only cleanup paths.
+- Included the flock(..., 0) call from [1] in the lock helper.
+- Added serializer DROP cleanup from [2] for the lock regression.
+- Set both MLDBM skip counts to two for the added lock check.
+
+[1] https://github.com/perl5-dbi/dbi/commit/cd1172e92c8a40f0070c881055c080095d8cb48a
+[2] https://github.com/perl5-dbi/dbi/commit/291a494d0968da9af1a27292d713d17a608a7398
+
+Assisted-By: Claude Mythos 5.1 <noreply@anthropic.com>
+
+Signed-off-by: Robert Rothenberg <perl@rhizomnic.com>
+Signed-off-by: Darsh Kelaiya <dkelaiya@cisco.com>
+---
+ lib/DBD/DBM.pm | 24 ++++++++++++++++++++----
+ lib/DBD/File.pm | 23 +++++++++++++----------
+ t/53dbm_module_name.t | 20 ++++++++++++++++++--
+ 3 files changed, 51 insertions(+), 16 deletions(-)
+
+diff --git a/lib/DBD/DBM.pm b/lib/DBD/DBM.pm
+index 26b1497..1f21dcd 100644
+--- a/lib/DBD/DBM.pm
++++ b/lib/DBD/DBM.pm
+@@ -367,6 +367,17 @@ sub init_table_meta
+ sub open_data
+ {
+ my ( $className, $meta, $attrs, $flags ) = @_;
++
++ # Validate dbm_mldbm BEFORE SUPER::open_data creates and flock()s the
++ # lock file. A croak after that point leaves $meta->{lockfh} holding an
++ # exclusive lock for as long as the table meta data lives, and the next
++ # open of the same table in this process blocks on it forever.
++ if ( $meta->{dbm_mldbm} and not $flags->{dropMode} )
++ {
++ DBD::DBM::_CLASS( $meta->{dbm_mldbm} )
++ or croak "Invalid dbm_mldbm '$meta->{dbm_mldbm}': expected a module name";
++ }
++
+ $className->SUPER::open_data( $meta, $attrs, $flags );
+
+ unless ( $flags->{dropMode} )
+@@ -407,15 +418,20 @@ sub open_data
+ {
+ $MLDBM::UseDB = $meta->{dbm_usedb};
+ $MLDBM::Serializer = $meta->{dbm_mldbm};
+- DBD::DBM::_CLASS($MLDBM::Serializer)
+- or croak "Invalid dbm_mldbm '${MLDBM::Serializer}': expected a module name";
+ }
+
+ $meta->{hash} = {};
+ my $tie_class = $meta->{dbm_tietype};
+ eval { tie %{ $meta->{hash} }, $tie_class, @tie_args };
+- $@ and croak "Cannot tie(\%h $tie_class @tie_args): $@";
+- -f $meta->{f_fqfn} or croak( "No such file: '" . $meta->{f_fqfn} . "'" );
++ if ( $@ or not -f $meta->{f_fqfn} )
++ {
++ # Release the lock SUPER::open_data took, or it outlives this
++ # failure (see above).
++ my $err = $@ ? "Cannot tie(\%h $tie_class @tie_args): $@"
++ : "No such file: '" . $meta->{f_fqfn} . "'";
++ $className->_release_lock($meta);
++ croak $err;
++ }
+ }
+
+ unless ( $flags->{createMode} )
+diff --git a/lib/DBD/File.pm b/lib/DBD/File.pm
+index 3647acd..e3d27d7 100644
+--- a/lib/DBD/File.pm
++++ b/lib/DBD/File.pm
+@@ -894,18 +894,24 @@ sub open_data {
+ sub drop ($) {
+ my ($self, $data) = @_;
+ my $meta = $self->{meta};
+- # We have to close the file before unlinking it: Some OS'es will
+- # refuse the unlink otherwise.
+- $meta->{fh} and $meta->{fh}->close ();
+- $meta->{lockfh} and $meta->{lockfh}->close ();
+- undef $meta->{fh};
+- undef $meta->{lockfh};
++ $self->_release_lock($meta);
+ $meta->{f_fqfn} and unlink $meta->{f_fqfn}; # XXX ==> sql_data_source
+ $meta->{f_fqln} and unlink $meta->{f_fqln}; # XXX ==> sql_data_source
+ delete $data->{Database}{sql_meta}{$self->{table}};
+ return 1;
+ } # drop
+
++sub _release_lock {
++ my ($self, $meta) = @_;
++ # We have to close the file before unlinking it: Some OS'es will
++ # refuse the unlink otherwise.
++ $meta->{fh} and $meta->{fh}->close ();
++ $meta->{lockfh} and eval { flock $meta->{lockfh}, 0 };
++ $meta->{lockfh} and $meta->{lockfh}->close ();
++ undef $meta->{fh};
++ undef $meta->{lockfh};
++}
++
+ sub seek ($$$$) {
+ my ($self, $data, $pos, $whence) = @_;
+ my $meta = $self->{meta};
+@@ -931,10 +937,7 @@ sub truncate ($$) {
+ sub DESTROY {
+ my $self = shift;
+ my $meta = $self->{meta};
+- $meta->{fh} and $meta->{fh}->close ();
+- $meta->{lockfh} and $meta->{lockfh}->close ();
+- undef $meta->{fh};
+- undef $meta->{lockfh};
++ $self->_release_lock($meta);
+
+ $self->SUPER::DESTROY();
+ } # DESTROY
+diff --git a/t/53dbm_module_name.t b/t/53dbm_module_name.t
+index e331fa4..7707b18 100644
+--- a/t/53dbm_module_name.t
++++ b/t/53dbm_module_name.t
+@@ -20,6 +20,7 @@ use File::Path qw(mkpath);
+ use File::Spec;
+ use File::Temp qw(tempdir);
+ use Test::More;
++use Fcntl qw(:flock);
+
+ my $dir = tempdir( CLEANUP => 1 );
+ my $dbdir = File::Spec->catdir( $dir, 'db' );
+@@ -76,13 +77,13 @@ my $have_mldbm = eval { require 'MLDBM.pm'; 1 };
+
+ # --- dbm_mldbm must not escape the MLDBM::Serializer:: prefix ------------
+ SKIP: {
+- skip "MLDBM not installed", 1 unless $have_mldbm;
++ skip "MLDBM not installed", 2 unless $have_mldbm;
+
+ # "MLDBM::Serializer::" only looks like containment. s|::|/|g rewrites
+ # "::" and leaves a literal "/" alone, and MLDBM ships MLDBM/Serializer/
+ # into @INC, so every component of a traversal out of it exists.
+ my ($inc) = grep { -d File::Spec->catdir( $_, 'MLDBM', 'Serializer' ) } @INC;
+- skip "no \@INC entry holds MLDBM/Serializer/", 1 unless defined $inc;
++ skip "no \@INC entry holds MLDBM/Serializer/", 2 unless defined $inc;
+
+ my @up = File::Spec->splitdir( File::Spec->canonpath($inc) );
+ shift(@up) while ( @up && $up[0] eq '' );
+@@ -99,6 +100,21 @@ SKIP: {
+ };
+ ok( !$::DBM_PLANT_LOADED,
+ "a dbm_mldbm that traverses out of MLDBM::Serializer:: does not load the file it names" );
++
++ # The rejection must happen before the table's lock file is created and
++ # flock()ed. Otherwise the lock outlives the failed CREATE and every
++ # later open of the table in this process blocks on it: the DROP below
++ # hung under DBI_AUTOPROXY=dbi:Gofer:transport=null, where the dbh that
++ # serves the DROP is not the one holding the stale lock.
++ my $lck = File::Spec->catfile( $dbdir, "t_ser.lck" );
++ my $free = 1;
++ if ( -f $lck and open( my $lfh, "<", $lck ) )
++ {
++ $free = flock( $lfh, LOCK_EX | LOCK_NB ) ? 1 : 0;
++ close($lfh);
++ }
++ ok( $free, "a rejected dbm_mldbm does not leave the table's lock file locked" );
++ eval { $dbh->do( "DROP TABLE t_ser" ); 1 };
+ $dbh->disconnect;
+ }
+
@@ -21,6 +21,7 @@ SRC_URI = "${CPAN_MIRROR}/authors/id/H/HM/HMBRAND/DBI-${PV}.tgz \
file://CVE-2026-73193_p1.patch \
file://CVE-2026-73193_p2.patch \
file://CVE-2026-78030.patch \
+ file://CVE-2026-111879.patch \
"
SRC_URI[sha256sum] = "0df16af8e5b3225a68b7b592ab531004ddb35a9682b50300ce50174ad867d9aa"