- 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
276 lines
8.2 KiB
Diff
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
|
|
|