Commit a7539ff8344 for php.net
commit a7539ff8344c7290b3e0e275e2b095a572d84b28
Author: Ilia Alshanetsky <ilia@ilia.ws>
Date: Fri Jul 10 08:21:24 2026 -0400
Fix GH-22666: pdo_odbc heap overflow on oversized output param
odbc_stmt_param_hook() sizes an output-param buffer from the declared maxlen
but then copies the runtime value into it using the value's own length, and
at fetch-back hands ZVAL_STRINGL the driver-reported length. Both trust a
length that can exceed the buffer: the input copy overflows the heap
(controlled write), and on a truncated OUTPUT value the driver reports the
full length via the shared indicator, so the readback over-reads. Record the
bound capacity in a new outbuflen field and clamp the copy and the readback to
it. LOB output is rejected at bind, so those copy sites stay unreachable.
Fixes GH-22666
Closes GH-22674
diff --git a/NEWS b/NEWS
index a9ad00c2be6..76be38bcaa7 100644
--- a/NEWS
+++ b/NEWS
@@ -51,6 +51,8 @@ PHP NEWS
carries no credentials). (iliaal)
. Fixed bug GH-22667 (Heap buffer over-read when a column value exceeds the
driver-reported display size). (iliaal)
+ . Fixed bug GH-22666 (Heap buffer overflow when an output parameter value is
+ longer than the declared maxlen). (iliaal)
- Phar:
. Fixed inconsistent handling of the magic ".phar" directory. Paths such as
diff --git a/ext/pdo_odbc/odbc_stmt.c b/ext/pdo_odbc/odbc_stmt.c
index 5b910489585..8940143da09 100644
--- a/ext/pdo_odbc/odbc_stmt.c
+++ b/ext/pdo_odbc/odbc_stmt.c
@@ -360,6 +360,7 @@ static int odbc_stmt_param_hook(pdo_stmt_t *stmt, struct pdo_bound_param_data *p
param->driver_data = P;
P->len = 0; /* is re-populated each EXEC_PRE */
+ P->outbuflen = 0;
P->outbuf = NULL;
P->is_unicode = pdo_odbc_sqltype_is_unicode(S, sqltype);
@@ -384,6 +385,7 @@ static int odbc_stmt_param_hook(pdo_stmt_t *stmt, struct pdo_bound_param_data *p
P->len *= 2;
}
P->outbuf = emalloc(P->len + (P->is_unicode ? 2:1));
+ P->outbuflen = P->len;
}
}
@@ -481,10 +483,16 @@ static int odbc_stmt_param_hook(pdo_stmt_t *stmt, struct pdo_bound_param_data *p
case PDO_ODBC_CONV_FAIL:
case PDO_ODBC_CONV_NOT_REQUIRED:
P->len = Z_STRLEN_P(parameter);
+ if (P->len > P->outbuflen) {
+ P->len = P->outbuflen;
+ }
memcpy(P->outbuf, Z_STRVAL_P(parameter), P->len);
break;
case PDO_ODBC_CONV_OK:
P->len = ulen;
+ if (P->len > P->outbuflen) {
+ P->len = P->outbuflen;
+ }
memcpy(P->outbuf, S->convbuf, P->len);
break;
}
@@ -506,6 +514,9 @@ static int odbc_stmt_param_hook(pdo_stmt_t *stmt, struct pdo_bound_param_data *p
zval_ptr_dtor(parameter);
if (P->len >= 0) {
+ if (P->len > P->outbuflen) {
+ P->len = P->outbuflen;
+ }
ZVAL_STRINGL(parameter, P->outbuf, P->len);
switch (pdo_odbc_ucs22utf8(stmt, P->is_unicode, parameter)) {
case PDO_ODBC_CONV_FAIL:
diff --git a/ext/pdo_odbc/php_pdo_odbc_int.h b/ext/pdo_odbc/php_pdo_odbc_int.h
index 473d70ff707..ea93c58b2b6 100644
--- a/ext/pdo_odbc/php_pdo_odbc_int.h
+++ b/ext/pdo_odbc/php_pdo_odbc_int.h
@@ -153,6 +153,7 @@ typedef struct {
typedef struct {
SQLLEN len;
+ SQLLEN outbuflen;
SQLSMALLINT paramtype;
char *outbuf;
unsigned is_unicode:1;
diff --git a/ext/pdo_odbc/tests/gh22666.phpt b/ext/pdo_odbc/tests/gh22666.phpt
new file mode 100644
index 00000000000..b6ba2265b9b
--- /dev/null
+++ b/ext/pdo_odbc/tests/gh22666.phpt
@@ -0,0 +1,30 @@
+--TEST--
+GH-22666 (Heap buffer overflow when an output param value exceeds its maxlen)
+--EXTENSIONS--
+pdo_odbc
+--SKIPIF--
+<?php
+require __DIR__ . '/config.inc';
+try {
+ $pdo = new PDO(PDO_ODBC_SQLITE_DSN);
+} catch (PDOException $e) {
+ die("skip requires the SQLite3 ODBC driver");
+}
+?>
+--FILE--
+<?php
+require __DIR__ . '/config.inc';
+$pdo = new PDO(PDO_ODBC_SQLITE_DSN);
+
+// An INPUT_OUTPUT parameter declares a maxlen of 4, so the output buffer is 4
+// bytes, but the runtime value is longer. The copy into that buffer must be
+// bounded to the declared capacity, not overflow it.
+$value = str_repeat('A', 64);
+$stmt = $pdo->prepare('SELECT ?');
+$stmt->bindParam(1, $value, PDO::PARAM_STR | PDO::PARAM_INPUT_OUTPUT, 4);
+$stmt->execute();
+
+echo "bounded to maxlen: "; var_dump($value);
+?>
+--EXPECT--
+bounded to maxlen: string(4) "AAAA"