From 7c59735fdbc8ef5bb34abaab89eddf601f789811 Mon Sep 17 00:00:00 2001 From: David Carlier Date: Sat, 2 May 2026 05:27:39 +0100 Subject: [PATCH 1/2] 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. --- ext/mysqli/mysqli_api.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/ext/mysqli/mysqli_api.c b/ext/mysqli/mysqli_api.c index 2bc33e4ad673..1325736ccf42 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); From 24b40bcaec58a3dc26e3a0dc41e0abe59da96715 Mon Sep 17 00:00:00 2001 From: David Carlier Date: Sat, 2 May 2026 05:29:25 +0100 Subject: [PATCH 2/2] add test --- .../tests/mysqli_execute_query_leak.phpt | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 ext/mysqli/tests/mysqli_execute_query_leak.phpt 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 000000000000..11f56877aece --- /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-- + +--FILE-- +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-- + +--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!