Skip to content

Commit 669312d

Browse files
committed
Merge branch 'PHP-8.2'
* PHP-8.2: Fix GH-9411: PgSQL large object resource is incorrectly closed
2 parents aff99f5 + 15405c6 commit 669312d

File tree

4 files changed

+91
-5
lines changed

4 files changed

+91
-5
lines changed

ext/pdo_pgsql/pgsql_driver.c

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@
3434
#include "zend_exceptions.h"
3535
#include "pgsql_driver_arginfo.h"
3636

37+
static bool pgsql_handle_in_transaction(pdo_dbh_t *dbh);
38+
3739
static char * _pdo_pgsql_trim_message(const char *message, int persistent)
3840
{
3941
size_t i = strlen(message)-1;
@@ -139,10 +141,12 @@ static ssize_t pgsql_lob_read(php_stream *stream, char *buf, size_t count)
139141
static int pgsql_lob_close(php_stream *stream, int close_handle)
140142
{
141143
struct pdo_pgsql_lob_self *self = (struct pdo_pgsql_lob_self*)stream->abstract;
144+
pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)(Z_PDO_DBH_P(&self->dbh))->driver_data;
142145

143146
if (close_handle) {
144147
lo_close(self->conn, self->lfd);
145148
}
149+
zend_hash_index_del(H->lob_streams, php_stream_get_resource_id(stream));
146150
zval_ptr_dtor(&self->dbh);
147151
efree(self);
148152
return 0;
@@ -193,6 +197,7 @@ php_stream *pdo_pgsql_create_lob_stream(zval *dbh, int lfd, Oid oid)
193197

194198
if (stm) {
195199
Z_ADDREF_P(dbh);
200+
zend_hash_index_add_ptr(H->lob_streams, php_stream_get_resource_id(stm), stm->res);
196201
return stm;
197202
}
198203

@@ -201,10 +206,29 @@ php_stream *pdo_pgsql_create_lob_stream(zval *dbh, int lfd, Oid oid)
201206
}
202207
/* }}} */
203208

209+
void pdo_pgsql_close_lob_streams(pdo_dbh_t *dbh)
210+
{
211+
zend_resource *res;
212+
pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)dbh->driver_data;
213+
if (H->lob_streams) {
214+
ZEND_HASH_REVERSE_FOREACH_PTR(H->lob_streams, res) {
215+
if (res->type >= 0) {
216+
zend_list_close(res);
217+
}
218+
} ZEND_HASH_FOREACH_END();
219+
}
220+
}
221+
204222
static void pgsql_handle_closer(pdo_dbh_t *dbh) /* {{{ */
205223
{
206224
pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)dbh->driver_data;
207225
if (H) {
226+
if (H->lob_streams) {
227+
pdo_pgsql_close_lob_streams(dbh);
228+
zend_hash_destroy(H->lob_streams);
229+
pefree(H->lob_streams, dbh->is_persistent);
230+
H->lob_streams = NULL;
231+
}
208232
if (H->server) {
209233
PQfinish(H->server);
210234
H->server = NULL;
@@ -294,6 +318,8 @@ static zend_long pgsql_handle_doer(pdo_dbh_t *dbh, const zend_string *sql)
294318
zend_long ret = 1;
295319
ExecStatusType qs;
296320

321+
bool in_trans = pgsql_handle_in_transaction(dbh);
322+
297323
if (!(res = PQexec(H->server, ZSTR_VAL(sql)))) {
298324
/* fatal error */
299325
pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, NULL);
@@ -312,6 +338,9 @@ static zend_long pgsql_handle_doer(pdo_dbh_t *dbh, const zend_string *sql)
312338
ret = Z_L(0);
313339
}
314340
PQclear(res);
341+
if (in_trans && !pgsql_handle_in_transaction(dbh)) {
342+
pdo_pgsql_close_lob_streams(dbh);
343+
}
315344

316345
return ret;
317346
}
@@ -502,9 +531,7 @@ static zend_result pdo_pgsql_check_liveness(pdo_dbh_t *dbh)
502531

503532
static bool pgsql_handle_in_transaction(pdo_dbh_t *dbh)
504533
{
505-
pdo_pgsql_db_handle *H;
506-
507-
H = (pdo_pgsql_db_handle *)dbh->driver_data;
534+
pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)dbh->driver_data;
508535

509536
return PQtransactionStatus(H->server) > PQTRANS_IDLE;
510537
}
@@ -537,7 +564,9 @@ static bool pgsql_handle_commit(pdo_dbh_t *dbh)
537564

538565
/* When deferred constraints are used the commit could
539566
fail, and a ROLLBACK implicitly ran. See bug #67462 */
540-
if (!ret) {
567+
if (ret) {
568+
pdo_pgsql_close_lob_streams(dbh);
569+
} else {
541570
dbh->in_txn = pgsql_handle_in_transaction(dbh);
542571
}
543572

@@ -546,7 +575,13 @@ static bool pgsql_handle_commit(pdo_dbh_t *dbh)
546575

547576
static bool pgsql_handle_rollback(pdo_dbh_t *dbh)
548577
{
549-
return pdo_pgsql_transaction_cmd("ROLLBACK", dbh);
578+
int ret = pdo_pgsql_transaction_cmd("ROLLBACK", dbh);
579+
580+
if (ret) {
581+
pdo_pgsql_close_lob_streams(dbh);
582+
}
583+
584+
return ret;
550585
}
551586

552587
/* {{{ Returns true if the copy worked fine or false if error */
@@ -1241,6 +1276,8 @@ static int pdo_pgsql_handle_factory(pdo_dbh_t *dbh, zval *driver_options) /* {{{
12411276
}
12421277

12431278
H->server = PQconnectdb(conn_str);
1279+
H->lob_streams = (HashTable *) pemalloc(sizeof(HashTable), dbh->is_persistent);
1280+
zend_hash_init(H->lob_streams, 0, NULL, NULL, 1);
12441281

12451282
if (tmp_user) {
12461283
zend_string_release_ex(tmp_user, 0);

ext/pdo_pgsql/pgsql_statement.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,8 @@ static int pgsql_stmt_execute(pdo_stmt_t *stmt)
134134
pdo_pgsql_db_handle *H = S->H;
135135
ExecStatusType status;
136136

137+
bool in_trans = stmt->dbh->methods->in_transaction(stmt->dbh);
138+
137139
/* ensure that we free any previous unfetched results */
138140
if(S->result) {
139141
PQclear(S->result);
@@ -252,6 +254,10 @@ static int pgsql_stmt_execute(pdo_stmt_t *stmt)
252254
stmt->row_count = (zend_long)PQntuples(S->result);
253255
}
254256

257+
if (in_trans && !stmt->dbh->methods->in_transaction(stmt->dbh)) {
258+
pdo_pgsql_close_lob_streams(stmt->dbh);
259+
}
260+
255261
return 1;
256262
}
257263

ext/pdo_pgsql/php_pdo_pgsql_int.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ typedef struct {
4545
bool emulate_prepares;
4646
bool disable_native_prepares; /* deprecated since 5.6 */
4747
bool disable_prepares;
48+
HashTable *lob_streams;
4849
} pdo_pgsql_db_handle;
4950

5051
typedef struct {
@@ -106,5 +107,6 @@ php_stream *pdo_pgsql_create_lob_stream(zval *pdh, int lfd, Oid oid);
106107
extern const php_stream_ops pdo_pgsql_lob_stream_ops;
107108

108109
void pdo_libpq_version(char *buf, size_t len);
110+
void pdo_pgsql_close_lob_streams(pdo_dbh_t *dbh);
109111

110112
#endif /* PHP_PDO_PGSQL_INT_H */

ext/pdo_pgsql/tests/gh9411.phpt

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
--TEST--
2+
Bug GH-9411 (PgSQL large object resource is incorrectly closed)
3+
--SKIPIF--
4+
<?php
5+
if (!extension_loaded('pdo') || !extension_loaded('pdo_pgsql')) die('skip not loaded');
6+
require __DIR__ . '/config.inc';
7+
require __DIR__ . '/../../../ext/pdo/tests/pdo_test.inc';
8+
PDOTest::skip();
9+
?>
10+
--FILE--
11+
<?php
12+
require __DIR__ . '/../../../ext/pdo/tests/pdo_test.inc';
13+
$db = PDOTest::test_factory(__DIR__ . '/common.phpt');
14+
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
15+
$db->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, false);
16+
17+
$db->beginTransaction();
18+
$oid = $db->pgsqlLOBCreate();
19+
var_dump($lob = $db->pgsqlLOBOpen($oid, 'wb'));
20+
fwrite($lob, 'test');
21+
$db->rollback();
22+
var_dump($lob);
23+
24+
$db->beginTransaction();
25+
$oid = $db->pgsqlLOBCreate();
26+
var_dump($lob = $db->pgsqlLOBOpen($oid, 'wb'));
27+
fwrite($lob, 'test');
28+
$db->commit();
29+
var_dump($lob);
30+
31+
$db->beginTransaction();
32+
var_dump($lob = $db->pgsqlLOBOpen($oid, 'wb'));
33+
var_dump(fgets($lob));
34+
?>
35+
--EXPECTF--
36+
resource(%d) of type (stream)
37+
resource(%d) of type (Unknown)
38+
resource(%d) of type (stream)
39+
resource(%d) of type (Unknown)
40+
resource(%d) of type (stream)
41+
string(4) "test"

0 commit comments

Comments
 (0)