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"
