Commit 083e1a6d79d for php
commit 083e1a6d79d8e913cec95f251c3c830709f5da92
Author: Ilia Alshanetsky <ilia@ilia.ws>
Date: Thu Oct 29 15:22:47 2026 -0400
ext/odbc: Fix double free on parameter description failure
Register the result before describing its parameters, so its destructor
can remove the correct connection entry on every failure path. Release it
through odbc_result_free() when SQLDescribeParam() fails instead of freeing
param_info separately and leaving it to be freed again by the destructor.
Fixes GH-25036
Closes GH-25044
diff --git a/NEWS b/NEWS
index 718e381b746..d84ff40a31a 100644
--- a/NEWS
+++ b/NEWS
@@ -125,6 +125,8 @@ PHP NEWS
- ODBC:
. Fixed bug GH-24736 (php_odbc_connstr_is_quoted() treats a value with no
closing brace as quoted). (lazerg)
+ . Fixed bug GH-25036 (odbc_prepare() corrupts the heap when the driver
+ cannot describe a parameter). (Ilia Alshanetsky)
- Opcache:
. Fixed tracing JIT returning a wrapped integer instead of a float on
diff --git a/ext/odbc/php_odbc.c b/ext/odbc/php_odbc.c
index 798245c4ca9..20302d56cb8 100644
--- a/ext/odbc/php_odbc.c
+++ b/ext/odbc/php_odbc.c
@@ -997,6 +997,7 @@ PHP_FUNCTION(odbc_prepare)
}
result->conn_ptr = conn;
result->fetched = 0;
+ odbc_insert_new_result(conn, return_value);
result->param_info = (odbc_param_info *) safe_emalloc(sizeof(odbc_param_info), result->numparams, 0);
for (i=0;i<result->numparams;i++) {
@@ -1004,14 +1005,11 @@ PHP_FUNCTION(odbc_prepare)
&result->param_info[i].scale, &result->param_info[i].nullable);
if (rc == SQL_ERROR) {
odbc_sql_error(result->conn_ptr, result->stmt, "SQLDescribeParameter");
- SQLFreeStmt(result->stmt, SQL_RESET_PARAMS);
- efree(result->param_info);
+ odbc_result_free(result);
zval_ptr_dtor(return_value);
RETURN_FALSE;
}
}
-
- odbc_insert_new_result(conn, return_value);
}
/* }}} */
diff --git a/ext/odbc/tests/gh25036.phpt b/ext/odbc/tests/gh25036.phpt
new file mode 100644
index 00000000000..16b3226361c
--- /dev/null
+++ b/ext/odbc/tests/gh25036.phpt
@@ -0,0 +1,39 @@
+--TEST--
+GH-25036 (odbc_prepare() double free when SQLDescribeParam() fails)
+--EXTENSIONS--
+odbc
+--SKIPIF--
+<?php include 'skipif.inc'; ?>
+--FILE--
+<?php
+
+include 'config.inc';
+
+$conn = odbc_connect($dsn, $user, $pass);
+
+$live = odbc_exec($conn, 'SELECT 1 AS a');
+
+var_dump(odbc_prepare($conn, 'SELECT ? FROM gh25036_missing'));
+var_dump(odbc_prepare($conn, 'SELECT ?, ? FROM gh25036_missing'));
+
+var_dump(odbc_fetch_array($live));
+
+odbc_close($conn);
+
+try {
+ odbc_fetch_row($live);
+} catch (Error $e) {
+ echo $e::class, ": ", $e->getMessage(), PHP_EOL;
+}
+?>
+--EXPECTF--
+Warning: odbc_prepare(): SQL error: %s, SQL state %s in SQLDescribeParameter in %s on line %d
+bool(false)
+
+Warning: odbc_prepare(): SQL error: %s, SQL state %s in SQLDescribeParameter in %s on line %d
+bool(false)
+array(1) {
+ ["a"]=>
+ string(1) "1"
+}
+Error: ODBC result has already been closed