diff mbox series

[meta-oe,scarthgap,4/5] libdbi-perl: Fix CVE-2026-14739

Message ID 20260729092556.45457-4-hthakar@cisco.com
State Under Review
Delegated to: Anuj Mittal
Headers show
Series [meta-oe,scarthgap,1/5] libdbi-perl: Fix CVE-2026-9698 | expand

Commit Message

From: Hetvi Thakar <hthakar@cisco.com>

Backport the upstream hard limit for positional placeholders. This is
a follow-up to CVE-2026-10879 and depends on the allocation fix from
the preceding libdbi-perl commit.

Correct the upstream boundary check so that the documented maximum of
99999 placeholders is accepted and values above it are rejected. Add
focused regression coverage for the 99999 and 100000 boundaries.

[1] https://github.com/perl5-dbi/dbi/commit/2b77c88b655e9539a592c71a61fb965fc0075395
[2] https://github.com/perl5-dbi/dbi/commit/af79036c07aa9a457971c0f4136e37c85dc20978
[3] https://nvd.nist.gov/vuln/detail/CVE-2026-14739
[4] https://nvd.nist.gov/vuln/detail/CVE-2026-10879

Signed-off-by: Hetvi Thakar <hthakar@cisco.com>
---
 .../perl/libdbi-perl/CVE-2026-14739.patch     | 170 ++++++++++++++++++
 .../perl/libdbi-perl_1.643.bb                 |   1 +
 2 files changed, 171 insertions(+)
 create mode 100644 meta-oe/recipes-devtools/perl/libdbi-perl/CVE-2026-14739.patch
diff mbox series

Patch

diff --git a/meta-oe/recipes-devtools/perl/libdbi-perl/CVE-2026-14739.patch b/meta-oe/recipes-devtools/perl/libdbi-perl/CVE-2026-14739.patch
new file mode 100644
index 0000000000..b43e3a20ba
--- /dev/null
+++ b/meta-oe/recipes-devtools/perl/libdbi-perl/CVE-2026-14739.patch
@@ -0,0 +1,170 @@ 
+From 57d0a1f66b9ed1b54f11af661da4719c43409497 Mon Sep 17 00:00:00 2001
+From: "H.Merijn Brand - Tux" <linux@tux.freedom.nl>
+Date: Sat, 4 Jul 2026 11:38:24 +0200
+Subject: [PATCH] Set a hard limit of 99999 on '?' placeholders 
+ (CVE-2026-14739)
+
+CVE: CVE-2026-14739
+Upstream-Status: Backport [https://github.com/perl5-dbi/dbi/commit/2b77c88b655e9539a592c71a61fb965fc0075395]
+
+Backport Changes:
+ - Omit ChangeLog, dbixs_rev.h, lib/DBI/Changes.pm, and the DBI.pm version-number hunks because the target remains DBI 1.643.
+ - Omit regenerated doc/DBI.3, doc/DBI.html, doc/DBI.man, and doc/DBI.md files; retain the DBI.pm source-documentation and DBI.xs security changes unchanged.
+ - Correct the upstream boundary check to accept exactly 99999 placeholders and reject values above that documented limit; add t/60preparse.t regression coverage for 99999 and 100000 placeholders.
+
+(cherry picked from commit 2b77c88b655e9539a592c71a61fb965fc0075395)
+Signed-off-by: Hetvi Thakar <hthakar@cisco.com>
+---
+ DBI.pm         |  6 ++++--
+ DBI.xs         | 53 +++++++++++++++++++++++++++++++++--------------------
+ t/60preparse.t | 13 ++++++++++++-
+ 3 files changed, 49 insertions(+), 23 deletions(-)
+
+diff --git a/DBI.pm b/DBI.pm
+index d62a32d..b988e40 100644
+--- a/DBI.pm
++++ b/DBI.pm
+@@ -7123,7 +7123,8 @@ a ref to an empty hash because they can't pre-determine the names.
+ It is possible that the keys in the hash returned by C<ParamValues>
+ are not exactly the same as those implied by the prepared statement.
+ For example, DBD::Oracle translates 'C<?>' placeholders into 'C<:pN>'
+-where N is a sequence number starting at 1.
++where N is a sequence number starting at C<1> with a hard limit of
++C<99999>.
+ 
+ * Values:
+ 
+@@ -7225,7 +7226,8 @@ integer.
+ It is also possible that the keys in the hash returned by
+ C<ParamArrays> are not exactly the same as those implied by the
+ prepared statement.  For example, DBD::Oracle translates 'C<?>'
+-placeholders into 'C<:pN>' where N is a sequence number starting at 1.
++placeholders into 'C<:pN>' where N is a sequence number starting at
++C<1> with a hard limit of C<99999>.
+ 
+ =head3 C<RowsInCache>
+ 
+diff --git a/DBI.xs b/DBI.xs
+index 8858e21..23ad34a 100644
+--- a/DBI.xs
++++ b/DBI.xs
+@@ -4201,7 +4201,14 @@ preparse(SV *dbh, const char *statement, IV ps_return, IV ps_accept, void *foo)
+     }
+ 
+     /* XXX this allocation strategy won't work when we get to more advanced stuff */
+-    new_stmt_sv = newSV(strlen(statement) * 6 + 16);
++    /* The 7 is for length increase from '?' (length 1) to :p99999 (length 7)
++     * which imposes a limit of 99999 '?' placeholders POSIX style. Actual counts
++     * are a bit higher:
++     * using factor 5: :p1 .. :p1107
++     * using factor 6: :p1 .. :p11106
++     * using factor 7: :p1 .. :p111105
++     * and that count is insane already */
++    new_stmt_sv = newSV(strlen(statement) * 7 + 16);
+     sv_setpv(new_stmt_sv,"");
+     src  = statement;
+     dest = SvPVX(new_stmt_sv);
+@@ -4340,9 +4347,9 @@ preparse(SV *dbh, const char *statement, IV ps_return, IV ps_accept, void *foo)
+             continue;
+         }
+ 
+-       if (    !(*src==':' && (PS_accept(DBIpp_ph_cn) || PS_accept(DBIpp_ph_cs)))
+-           &&  !(*src=='?' &&  PS_accept(DBIpp_ph_qm))
+-       ){
++        if (    !(*src==':' && (PS_accept(DBIpp_ph_cn) || PS_accept(DBIpp_ph_cs)))
++            &&  !(*src=='?' &&  PS_accept(DBIpp_ph_qm))
++        ){
+             if (*src == '\'' || *src == '"')
+                 in_quote = *src;
+             *dest++ = *src++;
+@@ -4361,12 +4368,18 @@ preparse(SV *dbh, const char *statement, IV ps_return, IV ps_accept, void *foo)
+             if (PS_return(DBIpp_ph_qm))
+                 ;
+             else if (PS_return(DBIpp_ph_cn)) { /* '?' -> ':p1' (etc) */
++                if (idx > 99999) {
++                    char buf[99];
++                    sprintf(buf, "preparse found more than 99999 '?' placeholders. Limit exceeded.");
++                    set_err_char(dbh, imp_xxh, "1", 1, buf, 0, "preparse");
++                    return &PL_sv_undef;
++                }
+                 sprintf(start,":p%d", idx++);
+                 dest = start+strlen(start);
+             }
+             else if (PS_return(DBIpp_ph_sp)) { /* '?' -> '%s' */
+-                   *start  = '%';
+-                   *dest++ = 's';
++                *start  = '%';
++                *dest++ = 's';
+             }
+         }
+         else if (isDIGIT(*src)) {   /* :1 */
+@@ -4374,24 +4387,24 @@ preparse(SV *dbh, const char *statement, IV ps_return, IV ps_accept, void *foo)
+             style = ":1";
+ 
+             if (PS_return(DBIpp_ph_cn)) { /* ':1'->':p1'  */
+-                   idx = pln;
+-                   *dest++ = 'p';
+-                   while(isDIGIT(*src))
+-                       *dest++ = *src++;
++                idx = pln;
++                *dest++ = 'p';
++                while(isDIGIT(*src))
++                    *dest++ = *src++;
+             }
+             else if (PS_return(DBIpp_ph_qm) /* ':1' -> '?'  */
+                  ||  PS_return(DBIpp_ph_sp) /* ':1' -> '%s' */
+             ) {
+-                   PS_return(DBIpp_ph_qm) ? sprintf(start,"?") : sprintf(start,"%%s");
+-                   dest = start + strlen(start);
+-                   if (pln != idx) {
+-                        char buf[99];
+-                        sprintf(buf, "preparse found placeholder :%d out of sequence, expected :%d", pln, idx);
+-                        set_err_char(dbh, imp_xxh, "1", 1, buf, 0, "preparse");
+-                        return &PL_sv_undef;
+-                   }
+-                   while(isDIGIT(*src)) src++;
+-                   idx++;
++                PS_return(DBIpp_ph_qm) ? sprintf(start,"?") : sprintf(start,"%%s");
++                dest = start + strlen(start);
++                if (pln != idx) {
++                    char buf[99];
++                    sprintf(buf, "preparse found placeholder :%d out of sequence, expected :%d", pln, idx);
++                    set_err_char(dbh, imp_xxh, "1", 1, buf, 0, "preparse");
++                    return &PL_sv_undef;
++                }
++                while(isDIGIT(*src)) src++;
++                idx++;
+             }
+         }
+         else if (isALNUM(*src))         /* :name */
+diff --git a/t/60preparse.t b/t/60preparse.t
+index 6432feb..668dfa3 100755
+--- a/t/60preparse.t
++++ b/t/60preparse.t
+@@ -11,7 +11,7 @@ BEGIN {
+ 		plan skip_all => 'preparse not supported for DBI::PurePerl';
+ 	}
+ 	else {
+-		plan tests => 39;
++		plan tests => 43;
+ 	}
+ }
+ 
+@@ -71,6 +71,17 @@ is( pp($dbh, "a = :name",  DBIpp_ph_sp,	DBIpp_ph_cs), "a = %s" );
+ 
+ is( pp($dbh, "a = ? b = ? c = ?", DBIpp_ph_cn,	DBIpp_ph_XX), "a = :p1 b = :p2 c = :p3" );
+ 
++my $max_placeholders = pp(
++    $dbh, "?" x 99999, DBIpp_ph_cn, DBIpp_ph_qm
++);
++ok(defined $max_placeholders, "accepts the documented limit of 99999 placeholders");
++is(substr($max_placeholders, -7), ":p99999", "numbers the final allowed placeholder");
++
++is(pp($dbh, "?" x 100000, DBIpp_ph_cn, DBIpp_ph_qm), undef,
++   "rejects placeholders above the documented limit");
++is($DBI::errstr, "preparse found more than 99999 '?' placeholders. Limit exceeded.",
++   "reports the placeholder limit");
++
+ ## Placeholders inside comments (should be ignored where comments style is accepted):
+ 
+ is( pp( $dbh,
diff --git a/meta-oe/recipes-devtools/perl/libdbi-perl_1.643.bb b/meta-oe/recipes-devtools/perl/libdbi-perl_1.643.bb
index c604e46334..4733378700 100644
--- a/meta-oe/recipes-devtools/perl/libdbi-perl_1.643.bb
+++ b/meta-oe/recipes-devtools/perl/libdbi-perl_1.643.bb
@@ -17,6 +17,7 @@  SRC_URI = "http://search.cpan.org/CPAN/authors/id/T/TI/TIMB/DBI-${PV}.tar.gz \
            file://CVE-2026-14380_p2.patch \
            file://CVE-2026-14380_p3.patch \
            file://CVE-2026-14380_p4.patch \
+           file://CVE-2026-14739.patch \
            "
 SRC_URI[md5sum] = "352f80b1e23769c116082a90905d7398"
 SRC_URI[sha256sum] = "8a2b993db560a2c373c174ee976a51027dd780ec766ae17620c20393d2e836fa"