Skip to content

Commit b095801

Browse files
committed
ext/pdo: Prevent setting bogus default fetch modes from PDO::setAttribute()
1 parent 9734f50 commit b095801

File tree

4 files changed

+43
-46
lines changed

4 files changed

+43
-46
lines changed

ext/pdo/pdo_dbh.c

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -861,22 +861,19 @@ static bool pdo_dbh_attribute_set(pdo_dbh_t *dbh, zend_long attr, zval *value, u
861861
return true;
862862

863863
case PDO_ATTR_DEFAULT_FETCH_MODE:
864-
if (Z_TYPE_P(value) == IS_ARRAY) {
865-
zval *tmp;
866-
if ((tmp = zend_hash_index_find(Z_ARRVAL_P(value), 0)) != NULL && Z_TYPE_P(tmp) == IS_LONG) {
867-
if (Z_LVAL_P(tmp) == PDO_FETCH_INTO || Z_LVAL_P(tmp) == PDO_FETCH_CLASS) {
868-
zend_argument_value_error(value_arg_num, "PDO::FETCH_INTO and PDO::FETCH_CLASS cannot be set as the default fetch mode");
869-
return false;
870-
}
871-
}
872-
lval = zval_get_long(value);
873-
} else {
874-
if (!pdo_get_long_param(&lval, value)) {
875-
return false;
876-
}
864+
if (!pdo_get_long_param(&lval, value)) {
865+
return false;
866+
}
867+
if (!pdo_verify_fetch_mode(PDO_FETCH_USE_DEFAULT, lval, value_arg_num, false)) {
868+
return false;
877869
}
878-
if (lval == PDO_FETCH_USE_DEFAULT) {
879-
zend_argument_value_error(value_arg_num, "Fetch mode must be a bitmask of PDO::FETCH_* constants");
870+
if (UNEXPECTED(
871+
lval == PDO_FETCH_USE_DEFAULT
872+
|| lval == PDO_FETCH_INTO
873+
|| lval == PDO_FETCH_CLASS
874+
|| lval == PDO_FETCH_FUNC
875+
)) {
876+
zend_argument_value_error(value_arg_num, "cannot set default fetch mode to PDO::FETCH_USE_DEFAULT, PDO::FETCH_INTO, PDO::FETCH_CLASS, or PDO::FETCH_FUNC");
880877
return false;
881878
}
882879
dbh->default_fetch_type = lval;

ext/pdo/pdo_stmt.c

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -947,8 +947,7 @@ static bool do_fetch(pdo_stmt_t *stmt, zval *return_value, enum pdo_fetch_type h
947947

948948
// TODO Error on the following cases:
949949
// Combining PDO_FETCH_UNIQUE and PDO_FETCH_GROUP
950-
// Reject $pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, value); bypass
951-
static bool pdo_verify_fetch_mode(uint32_t default_mode_and_flags, zend_long mode_and_flags, uint32_t mode_arg_num, bool fetch_all) /* {{{ */
950+
PDO_API bool pdo_verify_fetch_mode(uint32_t default_mode_and_flags, zend_long mode_and_flags, uint32_t mode_arg_num, bool fetch_all) /* {{{ */
952951
{
953952
/* Mode must be a positive */
954953
if (mode_and_flags < 0 || mode_and_flags >= PDO_FIRST_INVALID_FLAG) {

ext/pdo/php_pdo_driver.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -704,6 +704,8 @@ PDO_API void php_pdo_internal_construct_driver(INTERNAL_FUNCTION_PARAMETERS, zen
704704
PDO_API bool pdo_get_long_param(zend_long *lval, const zval *value);
705705
PDO_API bool pdo_get_bool_param(bool *bval, const zval *value);
706706

707+
PDO_API bool pdo_verify_fetch_mode(uint32_t default_mode_and_flags, zend_long mode_and_flags, uint32_t mode_arg_num, bool fetch_all);
708+
707709
PDO_API void pdo_throw_exception(unsigned int driver_errcode, char *driver_errmsg, pdo_error_type *pdo_error);
708710

709711
/* When a GC cycle is collected, it's possible that the database object is destroyed prior to destroying

ext/pdo/tests/bug_38253.phpt

Lines changed: 28 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -18,20 +18,32 @@ $pdo = PDOTest::factory();
1818
$pdo->exec ("create table test38253 (id integer primary key, n varchar(255))");
1919
$pdo->exec ("INSERT INTO test38253 (id, n) VALUES (1, 'hi')");
2020

21-
$pdo->setAttribute (PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_CLASS);
22-
$stmt = $pdo->prepare ("SELECT * FROM test38253");
23-
$stmt->execute();
24-
var_dump($stmt->fetchAll());
21+
try {
22+
$pdo->setAttribute (PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_CLASS);
23+
$stmt = $pdo->prepare ("SELECT * FROM test38253");
24+
$stmt->execute();
25+
var_dump($stmt->fetchAll());
26+
} catch (\Throwable $e) {
27+
echo $e::class, ': ', $e->getMessage(), \PHP_EOL;
28+
}
2529

26-
$pdo->setAttribute (PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_FUNC);
27-
$stmt = $pdo->prepare ("SELECT * FROM test38253");
28-
$stmt->execute();
29-
var_dump($stmt->fetchAll());
30+
try {
31+
$pdo->setAttribute (PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_FUNC);
32+
$stmt = $pdo->prepare ("SELECT * FROM test38253");
33+
$stmt->execute();
34+
var_dump($stmt->fetchAll());
35+
} catch (\Throwable $e) {
36+
echo $e::class, ': ', $e->getMessage(), \PHP_EOL;
37+
}
3038

31-
$pdo->setAttribute (PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_INTO);
32-
$stmt = $pdo->prepare ("SELECT * FROM test38253");
33-
$stmt->execute();
34-
var_dump($stmt->fetch());
39+
try {
40+
$pdo->setAttribute (PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_INTO);
41+
$stmt = $pdo->prepare ("SELECT * FROM test38253");
42+
$stmt->execute();
43+
var_dump($stmt->fetch());
44+
} catch (\Throwable $e) {
45+
echo $e::class, ': ', $e->getMessage(), \PHP_EOL;
46+
}
3547

3648
?>
3749
--CLEAN--
@@ -40,20 +52,7 @@ require_once getenv('REDIR_TEST_DIR') . 'pdo_test.inc';
4052
$db = PDOTest::factory();
4153
PDOTest::dropTableIfExists($db, "test38253");
4254
?>
43-
--EXPECTF--
44-
Warning: PDOStatement::fetchAll(): SQLSTATE[HY000]: General error: No fetch class specified in %s on line %d
45-
46-
Warning: PDOStatement::fetchAll(): SQLSTATE[HY000]: General error in %s on line %d
47-
array(0) {
48-
}
49-
50-
Warning: PDOStatement::fetchAll(): SQLSTATE[HY000]: General error: No fetch function specified in %s on line %d
51-
52-
Warning: PDOStatement::fetchAll(): SQLSTATE[HY000]: General error in %s on line %d
53-
array(0) {
54-
}
55-
56-
Warning: PDOStatement::fetch(): SQLSTATE[HY000]: General error: No fetch-into object specified. in %s on line %d
57-
58-
Warning: PDOStatement::fetch(): SQLSTATE[HY000]: General error in %s on line %d
59-
bool(false)
55+
--EXPECT--
56+
ValueError: PDO::setAttribute(): Argument #2 ($value) cannot set default fetch mode to PDO::FETCH_USE_DEFAULT, PDO::FETCH_INTO, PDO::FETCH_CLASS, or PDO::FETCH_FUNC
57+
ValueError: PDO::setAttribute(): Argument #2 ($value) PDO::FETCH_FUNC can only be used with PDOStatement::fetchAll()
58+
ValueError: PDO::setAttribute(): Argument #2 ($value) cannot set default fetch mode to PDO::FETCH_USE_DEFAULT, PDO::FETCH_INTO, PDO::FETCH_CLASS, or PDO::FETCH_FUNC

0 commit comments

Comments
 (0)