Commit 6501051883f for php.net
commit 6501051883f09db5a232d160161057ed4721d006
Author: David Carlier <devnexen@gmail.com>
Date: Sat May 2 05:27:39 2026 +0100
ext/mysqli: Fix stmt->query leak in mysqli_execute_query() validation errors.
When MYSQLI_REPORT_INDEX is enabled, mysqli_execute_query() duplicates
the query string into stmt->query. The two input_params validation
error branches freed the MY_STMT wrapper directly without releasing
stmt->query, leaking the duplicated string per failing call.
close GH-21930
diff --git a/NEWS b/NEWS
index e9226c59a0f..5dac2613a0e 100644
--- a/NEWS
+++ b/NEWS
@@ -21,6 +21,10 @@ PHP NEWS
IntlCalendar::equals(), ::before(), ::after(), and ::isEquivalentTo().
(Weilin Du)
+- mysqli:
+ . Fix stmt->query leak in mysqli_execute_query() validation errors.
+ (David Carlier)
+
- Phar:
. Fixed a bypass of the magic ".phar" directory protection in
Phar::addEmptyDir() for paths starting with "/.phar", while allowing
diff --git a/ext/mysqli/mysqli_api.c b/ext/mysqli/mysqli_api.c
index 2bc33e4ad67..1325736ccf4 100644
--- a/ext/mysqli/mysqli_api.c
+++ b/ext/mysqli/mysqli_api.c
@@ -532,6 +532,10 @@ PHP_FUNCTION(mysqli_execute_query)
MYSQLND_PARAM_BIND *params;
if (!zend_array_is_list(input_params)) {
+ if (stmt->query) {
+ efree(stmt->query);
+ stmt->query = NULL;
+ }
mysqli_stmt_close(stmt->stmt, false);
stmt->stmt = NULL;
efree(stmt);
@@ -542,6 +546,10 @@ PHP_FUNCTION(mysqli_execute_query)
hash_num_elements = zend_hash_num_elements(input_params);
param_count = mysql_stmt_param_count(stmt->stmt);
if (hash_num_elements != param_count) {
+ if (stmt->query) {
+ efree(stmt->query);
+ stmt->query = NULL;
+ }
mysqli_stmt_close(stmt->stmt, false);
stmt->stmt = NULL;
efree(stmt);
diff --git a/ext/mysqli/tests/mysqli_execute_query_leak.phpt b/ext/mysqli/tests/mysqli_execute_query_leak.phpt
new file mode 100644
index 00000000000..11f56877aec
--- /dev/null
+++ b/ext/mysqli/tests/mysqli_execute_query_leak.phpt
@@ -0,0 +1,37 @@
+--TEST--
+mysqli_execute_query() does not leak stmt->query on input_params validation errors with MYSQLI_REPORT_INDEX
+--EXTENSIONS--
+mysqli
+--SKIPIF--
+<?php
+require_once 'skipifconnectfailure.inc';
+?>
+--FILE--
+<?php
+
+require 'table.inc';
+
+mysqli_report(MYSQLI_REPORT_INDEX);
+
+try {
+ $link->execute_query('SELECT label, ? AS anon, ? AS num FROM test WHERE id=?', ['foo', 42]);
+} catch (ValueError $e) {
+ echo '[001] '.$e->getMessage()."\n";
+}
+
+try {
+ $link->execute_query('SELECT label, ? AS anon, ? AS num FROM test WHERE id=?', ['foo' => 42]);
+} catch (ValueError $e) {
+ echo '[002] '.$e->getMessage()."\n";
+}
+
+print "done!";
+?>
+--CLEAN--
+<?php
+require_once 'clean_table.inc';
+?>
+--EXPECT--
+[001] mysqli::execute_query(): Argument #2 ($params) must consist of exactly 3 elements, 2 present
+[002] mysqli::execute_query(): Argument #2 ($params) must be a list array
+done!