From ab1e52741c10894e0c73e370d346079937ddae77 Mon Sep 17 00:00:00 2001 From: Ilia Alshanetsky Date: Thu, 29 Oct 2026 15:22:47 -0400 Subject: [PATCH] Fix GH-25036: odbc_prepare() double free when SQLDescribeParam() fails odbc_prepare() set conn_ptr before describing the parameters but registered the result in conn->results only afterwards. When SQLDescribeParam() failed, the error branch freed param_info, then the object destructor freed it again in odbc_result_free() and deleted index 0 from conn->results, unregistering whichever live result held that slot. Register the result as soon as conn_ptr is set, as the other result-producing functions do, and release it through odbc_result_free() on failure. Fixes GH-25036 --- NEWS | 2 ++ ext/odbc/php_odbc.c | 6 ++---- ext/odbc/tests/gh25036.phpt | 39 +++++++++++++++++++++++++++++++++++++ 3 files changed, 43 insertions(+), 4 deletions(-) create mode 100644 ext/odbc/tests/gh25036.phpt diff --git a/NEWS b/NEWS index de5109dd05ce..72042349ceb9 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 798245c4ca99..20302d56cb8b 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;inumparams;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 000000000000..16b3226361cb --- /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-- + +--FILE-- +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