Skip to content

Fix GH-23741: pdo_dblib use-after-free of statement error state - #23751

Open
iliaal wants to merge 1 commit into
php:PHP-8.4from
iliaal:fix/gh-23741-dblib-stmt-err-lifetime
Open

iliaal wants to merge 1 commit into
php:PHP-8.4from
iliaal:fix/gh-23741-dblib-stmt-err-lifetime

Conversation

@iliaal

@iliaal iliaal commented Sep 18, 2026

Copy link
Copy Markdown
Member

pdo_dblib_stmt_execute() publishes the statement's error struct to the DBPROCESS with dbsetuserdata(), and FreeTDS keeps handing that pointer to the message handlers for the rest of the connection's life, long after the statement is freed. A statement cannot take the pointer back from its own destructor, since a GC cycle can free the connection handle first, so every function that hands the link to libdblib now installs the error struct it owns.

A failing beginTransaction(), commit(), rollBack() or lastInsertId() now reports its error; the stale SQLSTATE write previously left dbh->error_code untouched, so PDO stayed silent. rollBack() returns false in the reporter's script for an unrelated reason, SQL Server having already rolled the batch back itself, so the test does not assert its return value.

Fixes #23741

pdo_dblib_stmt_execute() published &S->err to the DBPROCESS through
dbsetuserdata(), but FreeTDS keeps handing that pointer to the error and
message handlers for the life of the connection, long after the statement is
freed. A statement cannot retract it from its own destructor, since a GC
cycle can free the connection handle first, so every function that hands
H->link to libdblib now installs the pdo_dblib_err it owns. get_column_meta()
also stops allocating return_value before the dbcoltypeinfo() check that can
fail, which the new test would otherwise leak.

Fixes phpGH-23741
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

pdo_dblib: segfault after a failed query inside a PDO transaction

1 participant