php/php-cve-2025-14179.patch
Remi Collet e6ee411f95 Fix CVEs up to 8.2.31:
- Fix XSS within status endpoint  CVE-2026-6735
- Fix Null pointer dereference in php_mb_check_encoding() via mb_ereg_search_init()  CVE-2026-7259
- Fix Stale SOAP_GLOBAL(ref_map) pointer with Apache Map  CVE-2026-6722
- Fix Use-after-free after header parsing failure with SOAP_PERSISTENCE_SESSION  CVE-2026-7261
- Fix Broken Apache map value NULL check  CVE-2026-7262
- Fix Signed integer overflow of char array offset  CVE-2026-7568
- Fix Consistently pass unsigned char to ctype.h functions  CVE-2026-7258

Resolves: RHEL-181025
2026-06-05 15:11:53 +02:00

276 lines
8.2 KiB
Diff

From abf5a10618537332f63194f2cf72b019c592029c Mon Sep 17 00:00:00 2001
From: Saki Takamachi <saki@sakiot.com>
Date: Sun, 3 May 2026 19:56:30 +0200
Subject: [PATCH 08/10] GHSA-w476-322c-wpvm: [pdo_firebird] Fix SQL injection
via NUL bytes in quoted strings
Fixes GHSA-w476-322c-wpvm
Fixes CVE-2025-14179
(cherry picked from commit 3f40b65323dd1b85e9bab6878237d3867e449d5c)
(cherry picked from commit 4b0dd469bbba7bf5f25f1a4f694aeb15c3515be4)
---
ext/pdo_firebird/firebird_driver.c | 68 ++++++++++++-------
.../tests/ghsa-w476-322c-wpvm.phpt | 44 ++++++++++++
2 files changed, 88 insertions(+), 24 deletions(-)
create mode 100644 ext/pdo_firebird/tests/ghsa-w476-322c-wpvm.phpt
diff --git a/ext/pdo_firebird/firebird_driver.c b/ext/pdo_firebird/firebird_driver.c
index fb69797850..06a33651d7 100644
--- a/ext/pdo_firebird/firebird_driver.c
+++ b/ext/pdo_firebird/firebird_driver.c
@@ -290,7 +290,7 @@ static FbTokenType getToken(const char** begin, const char* end)
return ret;
}
-int preprocess(const char* sql, int sql_len, char* sql_out, HashTable* named_params)
+int preprocess(const char* sql, int sql_len, char* sql_out, size_t* sql_out_len, HashTable* named_params)
{
zend_bool passAsIs = 1, execBlock = 0;
zend_long pindex = -1;
@@ -321,7 +321,7 @@ int preprocess(const char* sql, int sql_len, char* sql_out, HashTable* named_par
if (l > 252) {
return 0;
}
- strncpy(ident, i, l);
+ memcpy(ident, i, l);
ident[l] = '\0';
if (!strcasecmp(ident, "EXECUTE"))
{
@@ -346,7 +346,7 @@ int preprocess(const char* sql, int sql_len, char* sql_out, HashTable* named_par
if (l > 252) {
return 0;
}
- strncpy(ident2, i2, l);
+ memcpy(ident2, i2, l);
ident2[l] = '\0';
execBlock = !strcasecmp(ident2, "BLOCK");
passAsIs = 0;
@@ -362,11 +362,15 @@ int preprocess(const char* sql, int sql_len, char* sql_out, HashTable* named_par
if (passAsIs)
{
- strcpy(sql_out, sql);
+ memcpy(sql_out, sql, sql_len);
+ sql_out[sql_len] = '\0';
+ *sql_out_len = sql_len;
return 1;
}
- strncat(sql_out, start, p - start);
+ char *sql_out_p = sql_out;
+ memcpy(sql_out_p, start, p - start);
+ sql_out_p += p - start;
while (p < end)
{
@@ -374,10 +378,12 @@ int preprocess(const char* sql, int sql_len, char* sql_out, HashTable* named_par
tok = getToken(&p, end);
switch (tok)
{
- case ttParamMark:
- tok = getToken(&p, end);
+ case ttParamMark: {
+ const char* p_peek = p;
+ tok = getToken(&p_peek, end);
if (tok == ttIdent /*|| tok == ttString*/)
{
+ p = p_peek;
++pindex;
l = p - start;
/* check the length of the identifier */
@@ -386,7 +392,7 @@ int preprocess(const char* sql, int sql_len, char* sql_out, HashTable* named_par
if (l > 253) {
return 0;
}
- strncpy(pname, start, l);
+ memcpy(pname, start, l);
pname[l] = '\0';
if (named_params) {
@@ -395,7 +401,7 @@ int preprocess(const char* sql, int sql_len, char* sql_out, HashTable* named_par
zend_hash_str_update(named_params, pname, l, &tmp);
}
- strcat(sql_out, "?");
+ *sql_out_p++ = '?';
}
else
{
@@ -405,10 +411,11 @@ int preprocess(const char* sql, int sql_len, char* sql_out, HashTable* named_par
return 0;
}
++pindex;
- strncat(sql_out, start, p - start);
+ memcpy(sql_out_p, start, p - start);
+ sql_out_p += p - start;
}
break;
-
+ }
case ttIdent:
if (execBlock)
{
@@ -420,11 +427,14 @@ int preprocess(const char* sql, int sql_len, char* sql_out, HashTable* named_par
if (l > 252) {
return 0;
}
- strncpy(ident, start, l);
+ memcpy(ident, start, l);
ident[l] = '\0';
if (!strcasecmp(ident, "AS"))
{
- strncat(sql_out, start, end - start);
+ memcpy(sql_out_p, start, end - start);
+ sql_out_p += end - start;
+ *sql_out_p = '\0';
+ *sql_out_len = sql_out_p - sql_out;
return 1;
}
}
@@ -433,7 +443,8 @@ int preprocess(const char* sql, int sql_len, char* sql_out, HashTable* named_par
case ttComment:
case ttString:
case ttOther:
- strncat(sql_out, start, p - start);
+ memcpy(sql_out_p, start, p - start);
+ sql_out_p += p - start;
break;
case ttBrokenComment:
@@ -451,6 +462,8 @@ int preprocess(const char* sql, int sql_len, char* sql_out, HashTable* named_par
break;
}
}
+ *sql_out_p = '\0';
+ *sql_out_len = sql_out_p - sql_out;
return 1;
}
@@ -664,7 +677,7 @@ static int firebird_handle_quoter(pdo_dbh_t *dbh, const char *unquoted, size_t u
char **quoted, size_t *quotedlen, enum pdo_param_type paramtype)
{
size_t qcount = 0;
- char const *co, *l, *r;
+ char const *co, *l;
char *c;
if (!unquotedlen) {
@@ -674,9 +687,15 @@ static int firebird_handle_quoter(pdo_dbh_t *dbh, const char *unquoted, size_t u
return 1;
}
+ const char * const end = unquoted + unquotedlen;
+
/* Firebird only requires single quotes to be doubled if string lengths are used */
/* count the number of ' characters */
- for (co = unquoted; (co = strchr(co,'\'')); qcount++, co++);
+ for (co = unquoted; co < end; co++) {
+ if (*co == '\'') {
+ qcount++;
+ }
+ }
if (UNEXPECTED(unquotedlen + 2 > ZSTR_MAX_LEN - qcount)) {
return 0;
@@ -687,15 +706,15 @@ static int firebird_handle_quoter(pdo_dbh_t *dbh, const char *unquoted, size_t u
*c++ = '\'';
/* foreach (chunk that ends in a quote) */
- for (l = unquoted; (r = strchr(l,'\'')); l = r+1) {
- strncpy(c, l, r-l+1);
- c += (r-l+1);
- /* add the second quote */
- *c++ = '\'';
+ for (l = unquoted; l < end; l++) {
+ *c++ = *l;
+ if (*l == '\'') {
+ /* add the second quote */
+ *c++ = '\'';
+ }
}
/* copy the remainder */
- strncpy(c, l, *quotedlen-(c-*quoted)-1);
(*quoted)[*quotedlen-1] = '\'';
(*quoted)[*quotedlen] = '\0';
@@ -788,6 +807,7 @@ static int firebird_alloc_prepare_stmt(pdo_dbh_t *dbh, const char *sql, size_t s
{
pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data;
char *new_sql;
+ size_t new_sql_len;
/* Firebird allows SQL statements up to 64k, so bail if it doesn't fit */
if (sql_len > 65536) {
@@ -815,14 +835,14 @@ static int firebird_alloc_prepare_stmt(pdo_dbh_t *dbh, const char *sql, size_t s
we need to replace :foo by ?, and store the name we just replaced */
new_sql = emalloc(sql_len+1);
new_sql[0] = '\0';
- if (!preprocess(sql, sql_len, new_sql, named_params)) {
+ if (!preprocess(sql, sql_len, new_sql, &new_sql_len, named_params)) {
strcpy(dbh->error_code, "07000");
efree(new_sql);
return 0;
}
/* prepare the statement */
- if (isc_dsql_prepare(H->isc_status, &H->tr, s, 0, new_sql, H->sql_dialect, out_sqlda)) {
+ if (isc_dsql_prepare(H->isc_status, &H->tr, s, new_sql_len, new_sql, H->sql_dialect, out_sqlda)) {
RECORD_ERROR(dbh);
efree(new_sql);
return 0;
diff --git a/ext/pdo_firebird/tests/ghsa-w476-322c-wpvm.phpt b/ext/pdo_firebird/tests/ghsa-w476-322c-wpvm.phpt
new file mode 100644
index 0000000000..41c1125e9b
--- /dev/null
+++ b/ext/pdo_firebird/tests/ghsa-w476-322c-wpvm.phpt
@@ -0,0 +1,44 @@
+--TEST--
+GHSA-w476-322c-wpvm: SQL injection in pdo_firebird via NUL bytes in quoted strings
+--EXTENSIONS--
+pdo_firebird
+--SKIPIF--
+<?php require('skipif.inc'); ?>
+--XLEAK--
+A bug in firebird causes a memory leak when calling `isc_attach_database()`.
+See https://github.com/FirebirdSQL/firebird/issues/7849
+--FILE--
+<?php
+
+require("testdb.inc");
+
+$dbh->exec('CREATE TABLE ghsa_w476_322c_wpvm (name VARCHAR(255))');
+
+$param = $dbh->quote("\0");
+$param2 = $dbh->quote('or 1=1--');
+var_export($param);
+echo("\n");
+
+echo "prepare: ";
+$stmt = $dbh->prepare("SELECT * FROM ghsa_w476_322c_wpvm WHERE name = {$param} AND name = {$param2}");
+$stmt->execute();
+echo json_encode($stmt->fetchAll(PDO::FETCH_ASSOC)) . "\n";
+
+echo "query: ";
+$stmt = $dbh->query("SELECT * FROM ghsa_w476_322c_wpvm WHERE name = {$param} AND name = {$param2}");
+echo json_encode($stmt->fetchAll(PDO::FETCH_ASSOC)) . "\n";
+
+echo "exec: ";
+$affectedRows = $dbh->exec("UPDATE ghsa_w476_322c_wpvm SET name = 'updated' WHERE name = {$param} AND name = {$param2}");
+echo $affectedRows . "\n";
+?>
+--CLEAN--
+<?php
+require 'testdb.inc';
+$dbh->exec("DROP TABLE ghsa_w476_322c_wpvm");
+?>
+--EXPECT--
+'\'' . "\0" . '\''
+prepare: []
+query: []
+exec: 0
--
2.54.0