Skip to content

Commit 6f4579a

Browse files
authored
Introduce php_pdo_stmt_valid_db_obj_handle() (php#17567)
1 parent ad6cdb5 commit 6f4579a

File tree

7 files changed

+23
-18
lines changed

7 files changed

+23
-18
lines changed

UPGRADING.INTERNALS

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@ PHP 8.5 INTERNALS UPGRADE NOTES
3535
- ext/libxml
3636
. The refcount APIs now return an `unsigned int` instead of an `int`.
3737

38+
- ext/pdo
39+
. Added `php_pdo_stmt_valid_db_obj_handle()` to check if the database object
40+
is still valid. This is useful when a GC cycle is collected and the
41+
database object can be destroyed prior to destroying the statement.
42+
3843
========================
3944
4. OpCode changes
4045
========================

ext/pdo/pdo_dbh.c

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,13 @@ void pdo_throw_exception(unsigned int driver_errcode, char *driver_errmsg, pdo_e
6565
zend_throw_exception_object(&pdo_exception);
6666
}
6767

68+
PDO_API bool php_pdo_stmt_valid_db_obj_handle(const pdo_stmt_t *stmt)
69+
{
70+
return !Z_ISUNDEF(stmt->database_object_handle)
71+
&& IS_OBJ_VALID(EG(objects_store).object_buckets[Z_OBJ_HANDLE(stmt->database_object_handle)])
72+
&& !(OBJ_FLAGS(Z_OBJ(stmt->database_object_handle)) & IS_OBJ_FREE_CALLED);
73+
}
74+
6875
void pdo_raise_impl_error(pdo_dbh_t *dbh, pdo_stmt_t *stmt, pdo_error_type sqlstate, const char *supp) /* {{{ */
6976
{
7077
pdo_error_type *pdo_err = &dbh->error_code;

ext/pdo/php_pdo_driver.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -696,4 +696,11 @@ PDO_API bool pdo_get_long_param(zend_long *lval, zval *value);
696696
PDO_API bool pdo_get_bool_param(bool *bval, zval *value);
697697

698698
PDO_API void pdo_throw_exception(unsigned int driver_errcode, char *driver_errmsg, pdo_error_type *pdo_error);
699+
700+
/* When a GC cycle is collected, it's possible that the database object is destroyed prior to destroying
701+
* the statement. In that case, accessing the database object will cause a UAF.
702+
* This function checks if the database object is still valid.
703+
* If it is invalid, the internal driver statement data should have been cleared by the native driver API already. */
704+
PDO_API bool php_pdo_stmt_valid_db_obj_handle(const pdo_stmt_t *stmt);
705+
699706
#endif /* PHP_PDO_DRIVER_H */

ext/pdo_firebird/firebird_statement.c

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -158,15 +158,9 @@ static int pdo_firebird_stmt_dtor(pdo_stmt_t *stmt) /* {{{ */
158158
pdo_firebird_stmt *S = (pdo_firebird_stmt*)stmt->driver_data;
159159
int result = 1;
160160

161-
/* TODO: for master, move this check to a separate function shared between pdo drivers.
162-
* pdo_pgsql and pdo_mysql do this exact same thing */
163-
bool server_obj_usable = !Z_ISUNDEF(stmt->database_object_handle)
164-
&& IS_OBJ_VALID(EG(objects_store).object_buckets[Z_OBJ_HANDLE(stmt->database_object_handle)])
165-
&& !(OBJ_FLAGS(Z_OBJ(stmt->database_object_handle)) & IS_OBJ_FREE_CALLED);
166-
167161
/* release the statement.
168162
* Note: if the server object is already gone then the statement was closed already as well. */
169-
if (server_obj_usable && isc_dsql_free_statement(S->H->isc_status, &S->stmt, DSQL_drop)) {
163+
if (php_pdo_stmt_valid_db_obj_handle(stmt) && isc_dsql_free_statement(S->H->isc_status, &S->stmt, DSQL_drop)) {
170164
php_firebird_error_stmt(stmt);
171165
result = 0;
172166
}

ext/pdo_mysql/mysql_statement.c

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -95,9 +95,7 @@ static int pdo_mysql_stmt_dtor(pdo_stmt_t *stmt) /* {{{ */
9595
}
9696
#endif
9797

98-
if (!S->done && !Z_ISUNDEF(stmt->database_object_handle)
99-
&& IS_OBJ_VALID(EG(objects_store).object_buckets[Z_OBJ_HANDLE(stmt->database_object_handle)])
100-
&& (!(OBJ_FLAGS(Z_OBJ(stmt->database_object_handle)) & IS_OBJ_FREE_CALLED))) {
98+
if (!S->done && php_pdo_stmt_valid_db_obj_handle(stmt)) {
10199
while (mysql_more_results(S->H->server)) {
102100
MYSQL_RES *res;
103101
if (mysql_next_result(S->H->server) != 0) {

ext/pdo_odbc/odbc_stmt.c

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -136,11 +136,7 @@ static int odbc_stmt_dtor(pdo_stmt_t *stmt)
136136
{
137137
pdo_odbc_stmt *S = (pdo_odbc_stmt*)stmt->driver_data;
138138

139-
// TODO: Factor this out; pg/mysql/firebird do the same thing
140-
bool server_obj_usable = !Z_ISUNDEF(stmt->database_object_handle)
141-
&& IS_OBJ_VALID(EG(objects_store).object_buckets[Z_OBJ_HANDLE(stmt->database_object_handle)])
142-
&& !(OBJ_FLAGS(Z_OBJ(stmt->database_object_handle)) & IS_OBJ_FREE_CALLED);
143-
if (S->stmt != SQL_NULL_HANDLE && server_obj_usable) {
139+
if (S->stmt != SQL_NULL_HANDLE && php_pdo_stmt_valid_db_obj_handle(stmt)) {
144140
if (stmt->executed) {
145141
SQLCloseCursor(S->stmt);
146142
}

ext/pdo_pgsql/pgsql_statement.c

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -122,9 +122,7 @@ static void pgsql_stmt_finish(pdo_pgsql_stmt *S, int fin_mode)
122122
static int pgsql_stmt_dtor(pdo_stmt_t *stmt)
123123
{
124124
pdo_pgsql_stmt *S = (pdo_pgsql_stmt*)stmt->driver_data;
125-
bool server_obj_usable = !Z_ISUNDEF(stmt->database_object_handle)
126-
&& IS_OBJ_VALID(EG(objects_store).object_buckets[Z_OBJ_HANDLE(stmt->database_object_handle)])
127-
&& !(OBJ_FLAGS(Z_OBJ(stmt->database_object_handle)) & IS_OBJ_FREE_CALLED);
125+
bool server_obj_usable = php_pdo_stmt_valid_db_obj_handle(stmt);
128126

129127
pgsql_stmt_finish(S, FIN_DISCARD|(server_obj_usable ? FIN_CLOSE|FIN_ABORT : 0));
130128

0 commit comments

Comments
 (0)