diff mbox series

[meta-oe,wrynose,2/5] libdbi-perl: fix CVE-2026-111879

Message ID 20261111053834.3702246-2-dkelaiya@cisco.com
State New
Headers show
Series [meta-oe,wrynose,1/5] libdbi-perl: fix CVE-2026-78030 | expand

Commit Message

From: Darsh Kelaiya <dkelaiya@cisco.com>

Backport the lock cleanup fix [1] to DBI 1.647 after the
CVE-2026-78030 backport. Reject invalid serializer names before
acquiring a table lock and release the lock when opening a table
fails, addressing the denial of service described in [2].

Retain the nonblocking-lock regression. Include the helper's
explicit unlock from [3] and serializer test cleanup from [4].
Keep the existing DBD::File version and adjust the optional MLDBM
skip counts for the additional assertion.

[1] https://github.com/perl5-dbi/dbi/commit/99bbf2ab9d137e6f042a770bd3bbba3bc0b3a901
[2] https://github.com/perl5-dbi/dbi/security/advisories/GHSA-8cx8-9vj9-9x7j
[3] https://github.com/perl5-dbi/dbi/commit/cd1172e92c8a40f0070c881055c080095d8cb48a
[4] https://github.com/perl5-dbi/dbi/commit/291a494d0968da9af1a27292d713d17a608a7398

Signed-off-by: Darsh Kelaiya <dkelaiya@cisco.com>
---
 .../perl/libdbi-perl/CVE-2026-111879.patch    | 195 ++++++++++++++++++
 .../perl/libdbi-perl_1.647.bb                 |   1 +
 2 files changed, 196 insertions(+)
 create mode 100644 meta-oe/recipes-devtools/perl/libdbi-perl/CVE-2026-111879.patch
diff mbox series

Patch

diff --git a/meta-oe/recipes-devtools/perl/libdbi-perl/CVE-2026-111879.patch b/meta-oe/recipes-devtools/perl/libdbi-perl/CVE-2026-111879.patch
new file mode 100644
index 0000000000..8a18c082b8
--- /dev/null
+++ b/meta-oe/recipes-devtools/perl/libdbi-perl/CVE-2026-111879.patch
@@ -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;
+ }
+ 
diff --git a/meta-oe/recipes-devtools/perl/libdbi-perl_1.647.bb b/meta-oe/recipes-devtools/perl/libdbi-perl_1.647.bb
index e2760d84d5..5d766a29cf 100644
--- a/meta-oe/recipes-devtools/perl/libdbi-perl_1.647.bb
+++ b/meta-oe/recipes-devtools/perl/libdbi-perl_1.647.bb
@@ -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"