-
Notifications
You must be signed in to change notification settings - Fork 7.9k
Fix GH-16131: Prevent mixing PDO sub-classes with different DSN #16167
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -219,7 +219,7 @@ static char *dsn_from_uri(char *uri, char *buf, size_t buflen) /* {{{ */ | |
} | ||
/* }}} */ | ||
|
||
static bool create_driver_specific_pdo_object(pdo_driver_t *driver, zend_class_entry *called_scope, zval *new_object) | ||
static bool create_driver_specific_pdo_object(pdo_driver_t *driver, zend_class_entry *called_scope, zval *new_zval_object) | ||
{ | ||
zend_class_entry *ce; | ||
zend_class_entry *ce_based_on_driver_name = NULL, *ce_based_on_called_object = NULL; | ||
|
@@ -235,47 +235,70 @@ static bool create_driver_specific_pdo_object(pdo_driver_t *driver, zend_class_e | |
|
||
if (ce_based_on_called_object) { | ||
if (ce_based_on_driver_name) { | ||
if (instanceof_function(ce_based_on_called_object, ce_based_on_driver_name) == false) { | ||
if (!instanceof_function(ce_based_on_called_object, ce_based_on_driver_name)) { | ||
zend_throw_exception_ex(pdo_exception_ce, 0, | ||
"%s::connect() cannot be called when connecting to the \"%s\" driver, " | ||
"either %s::connect() or PDO::connect() must be called instead", | ||
ZSTR_VAL(called_scope->name), driver->driver_name, ZSTR_VAL(ce_based_on_driver_name->name)); | ||
"%s::%s() cannot be used for connecting to the \"%s\" driver, " | ||
"either call %s::%s() or PDO::%s() instead", | ||
ZSTR_VAL(called_scope->name), | ||
new_zval_object ? "connect" : "__construct", | ||
driver->driver_name, | ||
ZSTR_VAL(ce_based_on_driver_name->name), | ||
new_zval_object ? "connect" : "__construct", | ||
new_zval_object ? "connect" : "__construct" | ||
); | ||
return false; | ||
} | ||
|
||
/* A driver-specific implementation was instantiated via the connect() method of the appropriate driver class */ | ||
object_init_ex(new_object, ce_based_on_called_object); | ||
/* A driver-specific implementation is instantiated with the appropriate driver class */ | ||
if (new_zval_object) { | ||
object_init_ex(new_zval_object, called_scope); | ||
} | ||
return true; | ||
} else { | ||
zend_throw_exception_ex(pdo_exception_ce, 0, | ||
"%s::connect() cannot be called when connecting to an unknown driver, " | ||
"PDO::connect() must be called instead", | ||
ZSTR_VAL(called_scope->name)); | ||
"%s::%s() cannot be used for connecting to an unknown driver, " | ||
"call PDO::%s() instead", | ||
ZSTR_VAL(called_scope->name), | ||
new_zval_object ? "connect" : "__construct", | ||
new_zval_object ? "connect" : "__construct" | ||
); | ||
return false; | ||
} | ||
} | ||
|
||
/* A non-driver specific PDO subclass is instantiated via the constructor. This results in the legacy behavior. */ | ||
if (called_scope != pdo_dbh_ce && new_zval_object == NULL) { | ||
return true; | ||
} | ||
|
||
if (ce_based_on_driver_name) { | ||
if (called_scope != pdo_dbh_ce) { | ||
/* A driver-specific implementation was instantiated via the connect method of a wrong driver class */ | ||
/* A driver-specific implementation is instantiated with a wrong driver class */ | ||
zend_throw_exception_ex(pdo_exception_ce, 0, | ||
"%s::connect() cannot be called when connecting to the \"%s\" driver, " | ||
"either %s::connect() or PDO::connect() must be called instead", | ||
ZSTR_VAL(called_scope->name), driver->driver_name, ZSTR_VAL(ce_based_on_driver_name->name)); | ||
"%s::%s() cannot be used for connecting to the \"%s\" driver, " | ||
"either call %s::%s() or PDO::%s() instead", | ||
ZSTR_VAL(called_scope->name), | ||
new_zval_object ? "connect" : "__construct", | ||
driver->driver_name, | ||
ZSTR_VAL(ce_based_on_driver_name->name), | ||
new_zval_object ? "connect" : "__construct", | ||
new_zval_object ? "connect" : "__construct" | ||
); | ||
return false; | ||
} | ||
|
||
/* A driver-specific implementation was instantiated via PDO::__construct() */ | ||
object_init_ex(new_object, ce_based_on_driver_name); | ||
} else { | ||
if (new_zval_object) { | ||
object_init_ex(new_zval_object, ce_based_on_driver_name); | ||
} | ||
} else if (new_zval_object) { | ||
/* No driver-specific implementation found */ | ||
object_init_ex(new_object, called_scope); | ||
object_init_ex(new_zval_object, called_scope); | ||
} | ||
|
||
return true; | ||
} | ||
|
||
static void internal_construct(INTERNAL_FUNCTION_PARAMETERS, zend_object *object, zend_class_entry *current_scope, zval *new_zval_object) | ||
PDO_API void php_pdo_internal_construct_driver(INTERNAL_FUNCTION_PARAMETERS, zend_object *current_object, zend_class_entry *called_scope, zval *new_zval_object) | ||
{ | ||
pdo_dbh_t *dbh = NULL; | ||
bool is_persistent = 0; | ||
|
@@ -343,15 +366,16 @@ static void internal_construct(INTERNAL_FUNCTION_PARAMETERS, zend_object *object | |
RETURN_THROWS(); | ||
} | ||
|
||
if (new_zval_object != NULL) { | ||
ZEND_ASSERT((driver->driver_name != NULL) && "PDO driver name is null"); | ||
if (!create_driver_specific_pdo_object(driver, current_scope, new_zval_object)) { | ||
RETURN_THROWS(); | ||
} | ||
ZEND_ASSERT((driver->driver_name != NULL) && "PDO driver name is null"); | ||
|
||
if (!create_driver_specific_pdo_object(driver, called_scope, new_zval_object)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
RETURN_THROWS(); | ||
} | ||
|
||
if (new_zval_object) { | ||
dbh = Z_PDO_DBH_P(new_zval_object); | ||
} else { | ||
dbh = php_pdo_dbh_fetch_inner(object); | ||
dbh = php_pdo_dbh_fetch_inner(current_object); | ||
} | ||
|
||
/* is this supposed to be a persistent connection ? */ | ||
|
@@ -413,7 +437,7 @@ static void internal_construct(INTERNAL_FUNCTION_PARAMETERS, zend_object *object | |
if (pdbh) { | ||
efree(dbh); | ||
/* switch over to the persistent one */ | ||
php_pdo_dbh_fetch_object(object)->inner = pdbh; | ||
php_pdo_dbh_fetch_object(current_object)->inner = pdbh; | ||
pdbh->refcount++; | ||
dbh = pdbh; | ||
} | ||
|
@@ -497,14 +521,14 @@ static void internal_construct(INTERNAL_FUNCTION_PARAMETERS, zend_object *object | |
/* {{{ */ | ||
PHP_METHOD(PDO, __construct) | ||
{ | ||
internal_construct(INTERNAL_FUNCTION_PARAM_PASSTHRU, Z_OBJ(EX(This)), EX(This).value.ce, NULL); | ||
php_pdo_internal_construct_driver(INTERNAL_FUNCTION_PARAM_PASSTHRU, Z_OBJ_P(ZEND_THIS), Z_OBJCE_P(ZEND_THIS), NULL); | ||
} | ||
/* }}} */ | ||
|
||
/* {{{ */ | ||
PHP_METHOD(PDO, connect) | ||
{ | ||
internal_construct(INTERNAL_FUNCTION_PARAM_PASSTHRU, Z_OBJ(EX(This)), EX(This).value.ce, return_value); | ||
php_pdo_internal_construct_driver(INTERNAL_FUNCTION_PARAM_PASSTHRU, NULL, Z_CE_P(ZEND_THIS), return_value); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
} | ||
/* }}} */ | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -694,6 +694,8 @@ PDO_API void php_pdo_dbh_delref(pdo_dbh_t *dbh); | |
PDO_API void php_pdo_free_statement(pdo_stmt_t *stmt); | ||
PDO_API void php_pdo_stmt_set_column_count(pdo_stmt_t *stmt, int new_count); | ||
|
||
PDO_API void php_pdo_internal_construct_driver(INTERNAL_FUNCTION_PARAMETERS, zend_object *current_object, zend_class_entry *called_scope, zval *new_zval_object); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess it makes sense to expose this function for extensions which override the PDO constructor (?) |
||
|
||
/* Normalization for fetching long param for driver attributes */ | ||
PDO_API bool pdo_get_long_param(zend_long *lval, zval *value); | ||
PDO_API bool pdo_get_bool_param(bool *bval, zval *value); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
--TEST-- | ||
Test calling a PDO sub-class constructor with a different DSN | ||
--EXTENSIONS-- | ||
pdo_pgsql | ||
pdo_sqlite | ||
--FILE-- | ||
<?php | ||
|
||
try { | ||
new Pdo\Pgsql('sqlite::memory:'); | ||
} catch (PDOException $e) { | ||
echo $e->getMessage() . "\n"; | ||
} | ||
|
||
class MyPgsql extends Pdo\Pgsql | ||
{ | ||
} | ||
|
||
try { | ||
new MyPgsql('sqlite::memory:'); | ||
} catch (PDOException $e) { | ||
echo $e->getMessage() . "\n"; | ||
} | ||
|
||
?> | ||
--EXPECT-- | ||
Pdo\Pgsql::__construct() cannot be used for connecting to the "sqlite" driver, either call Pdo\Sqlite::__construct() or PDO::__construct() instead | ||
MyPgsql::__construct() cannot be used for connecting to the "sqlite" driver, either call Pdo\Sqlite::__construct() or PDO::__construct() instead |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,4 +14,4 @@ try { | |
|
||
?> | ||
--EXPECT-- | ||
Pdo\Pgsql::connect() cannot be called when connecting to the "sqlite" driver, either Pdo\Sqlite::connect() or PDO::connect() must be called instead | ||
Pdo\Pgsql::connect() cannot be used for connecting to the "sqlite" driver, either call Pdo\Sqlite::connect() or PDO::connect() instead | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The new wording is more clear IMO. |
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Without this, older tests using legacy "quasi classes" would fail, now that constructor calls are also validated.