Merge branch 'PHP-8.6'

* PHP-8.6:
  Fix HashTable UAF when rebound from a parameter __toString()
This commit is contained in:
Ilia Alshanetsky committed 2026-09-30 08:51:00 -04:00
commit cb28aa03d5
3 files changed
+127 -1

No files matched your search

+23
View File
@@ -87,6 +87,15 @@ static inline bool rewrite_name_to_position(pdo_stmt_t *stmt, struct pdo_bound_p
}
/* }}} */
static bool pdo_stmt_disallow_reentrant_param_event(pdo_stmt_t *stmt)
{
if (UNEXPECTED(stmt->in_param_event)) {
zend_throw_error(NULL, "Cannot modify a PDOStatement while parameter hooks are running");
return false;
}
return true;
}
/* trigger callback hook for parameters */
static bool dispatch_param_event(pdo_stmt_t *stmt, enum pdo_param_event event_type) /* {{{ */
{
@@ -102,6 +111,7 @@ static bool dispatch_param_event(pdo_stmt_t *stmt, enum pdo_param_event event_ty
return true;
}
stmt->in_param_event = 1;
ht = stmt->bound_params;
iterate:
@@ -119,6 +129,7 @@ iterate:
goto iterate;
}
stmt->in_param_event = 0;
return ret;
}
/* }}} */
@@ -404,6 +415,9 @@ PHP_METHOD(PDOStatement, execute)
ZEND_PARSE_PARAMETERS_END();
PHP_STMT_GET_OBJ;
if (!pdo_stmt_disallow_reentrant_param_event(stmt)) {
RETURN_THROWS();
}
if (stmt->executed) {
pdo_stmt_invalidate_result(stmt);
@@ -1331,6 +1345,9 @@ static void register_bound_param(INTERNAL_FUNCTION_PARAMETERS, int is_param) /*
ZEND_PARSE_PARAMETERS_END();
PHP_STMT_GET_OBJ;
if (!pdo_stmt_disallow_reentrant_param_event(stmt)) {
RETURN_THROWS();
}
param.param_type = (int) param_type;
@@ -1383,6 +1400,9 @@ PHP_METHOD(PDOStatement, bindValue)
ZEND_PARSE_PARAMETERS_END();
PHP_STMT_GET_OBJ;
if (!pdo_stmt_disallow_reentrant_param_event(stmt)) {
RETURN_THROWS();
}
param.param_type = (int) param_type;
if (param.name) {
@@ -1844,6 +1864,9 @@ PHP_METHOD(PDOStatement, closeCursor)
ZEND_PARSE_PARAMETERS_NONE();
PHP_STMT_GET_OBJ;
if (!pdo_stmt_disallow_reentrant_param_event(stmt)) {
RETURN_THROWS();
}
if (!stmt->methods->cursor_closer) {
/* emulate it by fetching and discarding rows */
do {
+2 -1
View File
@@ -573,7 +573,8 @@ struct _pdo_stmt_t {
* bindParam() for its prepared statements, if false, PDO should
* emulate prepare and bind on its behalf */
uint16_t supports_placeholders:2;
uint16_t reserved: 12;
uint16_t in_param_event:1;
uint16_t reserved: 11;
/* keep track of bound input parameters. Some drivers support
* input/output parameters, but you can't rely on that working */
@@ -0,0 +1,102 @@
--TEST--
Rebinding or re-executing from a parameter __toString() must not mutate bound_params mid-FOREACH
--EXTENSIONS--
pdo_sqlite
--FILE--
<?php
class Rebind {
public function __construct(private PDOStatement $stmt) {}
public function __toString() {
try {
$this->stmt->bindValue(1, 'x');
echo "bindValue: no error\n";
} catch (Error $e) {
echo $e::class, ": ", $e->getMessage(), "\n";
}
return 'rebind';
}
}
class Reexec {
public function __construct(private PDOStatement $stmt) {}
public function __toString() {
try {
$this->stmt->execute(['x', 'y']);
echo "execute: no error\n";
} catch (Error $e) {
echo $e::class, ": ", $e->getMessage(), "\n";
}
return 'reexec';
}
}
class Reclose {
public function __construct(private PDOStatement $stmt) {}
public function __toString() {
try {
$this->stmt->closeCursor();
echo "closeCursor: no error\n";
} catch (Error $e) {
echo $e::class, ": ", $e->getMessage(), "\n";
}
return 'reclose';
}
}
$db = new PDO('sqlite::memory:');
echo "bindValue:\n";
$stmt = $db->prepare('SELECT ?, ?');
$p1 = 'placeholder';
$p2 = 'second';
$stmt->bindParam(1, $p1);
$stmt->bindParam(2, $p2);
$p1 = new Rebind($stmt);
try {
$stmt->execute();
echo "execute after bindValue: no error\n";
} catch (Throwable $e) {
echo $e::class, ": ", $e->getMessage(), "\n";
}
echo "execute:\n";
$stmt = $db->prepare('SELECT ?, ?');
$p1 = 'placeholder';
$p2 = 'second';
$stmt->bindParam(1, $p1);
$stmt->bindParam(2, $p2);
$p1 = new Reexec($stmt);
try {
$stmt->execute();
echo "execute after execute: no error\n";
} catch (Throwable $e) {
echo $e::class, ": ", $e->getMessage(), "\n";
}
echo "closeCursor:\n";
$stmt = $db->prepare('SELECT ?, ?');
$p1 = 'placeholder';
$p2 = 'second';
$stmt->bindParam(1, $p1);
$stmt->bindParam(2, $p2);
$p1 = new Reclose($stmt);
try {
$stmt->execute();
echo "execute after closeCursor: no error\n";
} catch (Throwable $e) {
echo $e::class, ": ", $e->getMessage(), "\n";
}
echo "done\n";
?>
--EXPECT--
bindValue:
Error: Cannot modify a PDOStatement while parameter hooks are running
execute after bindValue: no error
execute:
Error: Cannot modify a PDOStatement while parameter hooks are running
execute after execute: no error
closeCursor:
Error: Cannot modify a PDOStatement while parameter hooks are running
execute after closeCursor: no error
done