diff --git a/NEWS b/NEWS index 519b0ccaf053..f00c6dce9b5c 100644 --- a/NEWS +++ b/NEWS @@ -56,6 +56,10 @@ PHP NEWS . Fixed a leak when a persistent connection failed a liveness check with no other live PDO handle. (iliaal) +- PDO_PGSQL: + . Fixed PDO::CURSOR_SCROLL statements closing a cursor that does not exist. + (KentarouTakeda) + - Phar: . Fixed bug GH-23418 (Use-after-free when looking up mounted directories). (Weilin Du) diff --git a/ext/pdo_pgsql/pgsql_statement.c b/ext/pdo_pgsql/pgsql_statement.c index 8f3dd5237b5a..7864db94bd7d 100644 --- a/ext/pdo_pgsql/pgsql_statement.c +++ b/ext/pdo_pgsql/pgsql_statement.c @@ -114,7 +114,7 @@ static int pgsql_stmt_dtor(pdo_stmt_t *stmt) } if (S->cursor_name) { - if (server_obj_usable) { + if (S->is_cursor_declared && server_obj_usable) { pdo_pgsql_db_handle *H = S->H; char *q = NULL; PGresult *res; @@ -156,10 +156,11 @@ static int pgsql_stmt_execute(pdo_stmt_t *stmt) if (S->cursor_name) { char *q = NULL; - if (S->is_prepared) { + if (S->is_cursor_declared) { spprintf(&q, 0, "CLOSE %s", S->cursor_name); PQclear(PQexec(H->server, q)); efree(q); + S->is_cursor_declared = false; } spprintf(&q, 0, "DECLARE %s SCROLL CURSOR WITH HOLD FOR %s", S->cursor_name, ZSTR_VAL(stmt->active_query_string)); @@ -175,7 +176,7 @@ static int pgsql_stmt_execute(pdo_stmt_t *stmt) PQclear(S->result); /* the cursor was declared correctly */ - S->is_prepared = 1; + S->is_cursor_declared = true; /* fetch to be able to get the number of tuples later, but don't advance the cursor pointer */ spprintf(&q, 0, "FETCH FORWARD 0 FROM %s", S->cursor_name); diff --git a/ext/pdo_pgsql/php_pdo_pgsql_int.h b/ext/pdo_pgsql/php_pdo_pgsql_int.h index fc9f1664cc3d..c4ea431da21b 100644 --- a/ext/pdo_pgsql/php_pdo_pgsql_int.h +++ b/ext/pdo_pgsql/php_pdo_pgsql_int.h @@ -68,6 +68,7 @@ typedef struct { Oid *param_types; int current_row; bool is_prepared; + bool is_cursor_declared; } pdo_pgsql_stmt; typedef struct { diff --git a/ext/pdo_pgsql/tests/cursor_scroll_failed_redeclare.phpt b/ext/pdo_pgsql/tests/cursor_scroll_failed_redeclare.phpt new file mode 100644 index 000000000000..4b8d7f135c93 --- /dev/null +++ b/ext/pdo_pgsql/tests/cursor_scroll_failed_redeclare.phpt @@ -0,0 +1,37 @@ +--TEST-- +PDO PgSQL PDO::CURSOR_SCROLL sends no CLOSE after a failed re-declare +--EXTENSIONS-- +pdo_pgsql +--SKIPIF-- + +--FILE-- +prepare('SELECT CAST(:v AS int)', [PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL]); +$stmt->execute([':v' => '1']); + +try { + $stmt->execute([':v' => 'not an int']); +} catch (PDOException $e) { + echo $e::class, ': ', $e->getCode(), PHP_EOL; +} + +$db->beginTransaction(); +unset($stmt); + +$db->exec('SELECT 2'); + +echo 'Done', PHP_EOL; + +?> +--EXPECT-- +PDOException: 22P02 +Done diff --git a/ext/pdo_pgsql/tests/cursor_scroll_without_declare.phpt b/ext/pdo_pgsql/tests/cursor_scroll_without_declare.phpt new file mode 100644 index 000000000000..69a8e8add609 --- /dev/null +++ b/ext/pdo_pgsql/tests/cursor_scroll_without_declare.phpt @@ -0,0 +1,30 @@ +--TEST-- +PDO PgSQL PDO::CURSOR_SCROLL sends no CLOSE for a cursor it never declared +--EXTENSIONS-- +pdo_pgsql +--SKIPIF-- + +--FILE-- +beginTransaction(); + +$stmt = $db->prepare('SELECT 1', [PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL]); +unset($stmt); + +$db->exec('SELECT 2'); + +echo 'Done'; + +?> +--EXPECT-- +Done