mirror of
https://github.com/php/php-src.git
synced 2026-10-28 09:46:46 +08:00
Merge branch 'PHP-8.6'
* PHP-8.6: Fix HashTable UAF when rebound from a parameter __toString()
This commit is contained in:
3 files changed
+127
-1
No files matched your search
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
Reference in new issue
Block a user