Skip to content

Commit c7a86a3

Browse files
committed
Fix pgsql use after free trying to reuse closed connection
When a connection is closed, we also need to remove the hash entry from the regular_list, as it now points to freed memory. To do this store a reverse mapping from the connection to the hash string. It would be nicer to introduce a wrapping structure for the pgsql link resource that could store the hash (and notices), but that would require large changes to the extension, so I'm going for a more minimal fix here.
1 parent b55715d commit c7a86a3

File tree

4 files changed

+47
-9
lines changed

4 files changed

+47
-9
lines changed

Zend/zend_hash.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ static void _zend_is_inconsistent(const HashTable *ht, const char *file, int lin
6161
zend_output_debug_string(1, "%s(%d) : ht=%p is inconsistent", file, line, ht);
6262
break;
6363
}
64-
zend_bailout();
64+
ZEND_ASSERT(0);
6565
}
6666
#define IS_CONSISTENT(a) _zend_is_inconsistent(a, __FILE__, __LINE__);
6767
#define SET_INCONSISTENT(n) do { \

ext/pgsql/pgsql.c

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -939,12 +939,20 @@ static void _close_pgsql_link(zend_resource *rsrc)
939939
{
940940
PGconn *link = (PGconn *)rsrc->ptr;
941941
PGresult *res;
942+
zval *hash;
942943

943944
while ((res = PQgetResult(link))) {
944945
PQclear(res);
945946
}
946947
PQfinish(link);
947948
PGG(num_links)--;
949+
950+
/* Remove connection hash for this link */
951+
hash = zend_hash_index_find(&PGG(hashes), (uintptr_t) link);
952+
if (hash) {
953+
zend_hash_index_del(&PGG(hashes), (uintptr_t) link);
954+
zend_hash_del(&EG(regular_list), Z_STR_P(hash));
955+
}
948956
}
949957
/* }}} */
950958

@@ -1099,6 +1107,7 @@ static PHP_GINIT_FUNCTION(pgsql)
10991107
memset(pgsql_globals, 0, sizeof(zend_pgsql_globals));
11001108
/* Initilize notice message hash at MINIT only */
11011109
zend_hash_init_ex(&pgsql_globals->notices, 0, NULL, ZVAL_PTR_DTOR, 1, 0);
1110+
zend_hash_init_ex(&pgsql_globals->hashes, 0, NULL, ZVAL_PTR_DTOR, 1, 0);
11021111
}
11031112
/* }}} */
11041113

@@ -1215,6 +1224,7 @@ PHP_MSHUTDOWN_FUNCTION(pgsql)
12151224
{
12161225
UNREGISTER_INI_ENTRIES();
12171226
zend_hash_destroy(&PGG(notices));
1227+
zend_hash_destroy(&PGG(hashes));
12181228

12191229
return SUCCESS;
12201230
}
@@ -1236,6 +1246,7 @@ PHP_RSHUTDOWN_FUNCTION(pgsql)
12361246
{
12371247
/* clean up notice messages */
12381248
zend_hash_clean(&PGG(notices));
1249+
zend_hash_clean(&PGG(hashes));
12391250
/* clean up persistent connection */
12401251
zend_hash_apply(&EG(persistent_list), (apply_func_t) _rollback_transactions);
12411252
return SUCCESS;
@@ -1431,14 +1442,11 @@ static void php_pgsql_do_connect(INTERNAL_FUNCTION_PARAMETERS, int persistent)
14311442
}
14321443

14331444
link = (zend_resource *)index_ptr->ptr;
1434-
if (link->ptr && (link->type == le_link || link->type == le_plink)) {
1435-
php_pgsql_set_default_link(link);
1436-
GC_REFCOUNT(link)++;
1437-
RETVAL_RES(link);
1438-
goto cleanup;
1439-
} else {
1440-
zend_hash_del(&EG(regular_list), str.s);
1441-
}
1445+
ZEND_ASSERT(link->ptr && (link->type == le_link || link->type == le_plink));
1446+
php_pgsql_set_default_link(link);
1447+
GC_REFCOUNT(link)++;
1448+
RETVAL_RES(link);
1449+
goto cleanup;
14421450
}
14431451
if (PGG(max_links) != -1 && PGG(num_links) >= PGG(max_links)) {
14441452
php_error_docref(NULL, E_WARNING, "Cannot create new link. Too many open links (" ZEND_LONG_FMT ")", PGG(num_links));
@@ -1484,6 +1492,16 @@ static void php_pgsql_do_connect(INTERNAL_FUNCTION_PARAMETERS, int persistent)
14841492
if (zend_hash_update_mem(&EG(regular_list), str.s, (void *) &new_index_ptr, sizeof(zend_resource)) == NULL) {
14851493
goto err;
14861494
}
1495+
1496+
/* Keep track of link => hash mapping, so we can remove the hash entry from regular_list
1497+
* when the connection is closed. This uses the address of the connection rather than the
1498+
* zend_resource, because the resource destructor is passed a stack copy of the resource
1499+
* structure. */
1500+
{
1501+
zval tmp;
1502+
ZVAL_STR_COPY(&tmp, str.s);
1503+
zend_hash_index_update(&PGG(hashes), (uintptr_t) pgsql, &tmp);
1504+
}
14871505
PGG(num_links)++;
14881506
}
14891507
/* set notice processor */

ext/pgsql/php_pgsql.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,7 @@ ZEND_BEGIN_MODULE_GLOBALS(pgsql)
319319
int ignore_notices,log_notices;
320320
HashTable notices; /* notice message for each connection */
321321
zend_resource *default_link; /* default link when connection is omitted */
322+
HashTable hashes; /* hashes for each connection */
322323
ZEND_END_MODULE_GLOBALS(pgsql)
323324

324325
ZEND_EXTERN_MODULE_GLOBALS(pgsql)
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
--TEST--
2+
Reopen connection after it was closed
3+
--SKIPIF--
4+
<?php include("skipif.inc"); ?>
5+
--FILE--
6+
<?php
7+
include('config.inc');
8+
9+
/* Run me under valgrind */
10+
$db1 = pg_connect($conn_str);
11+
unset($db1);
12+
var_dump(pg_close());
13+
$db2 = pg_connect($conn_str);
14+
unset($db2);
15+
var_dump(pg_close());
16+
?>
17+
--EXPECT--
18+
bool(true)
19+
bool(true)

0 commit comments

Comments
 (0)