mirror of
https://github.com/php/php-src.git
synced 2026-03-24 00:02:20 +01:00
ext/pdo_sqlite - throw on null bytes / resolve GH-13952 (#18320)
fix a corruption issue where PDO::quote for SQLite would silently truncate strings with null bytes in them, by throwing. Fixes #13952 Closes #18320
This commit is contained in:
committed by
Saki Takamachi
parent
93ad8e8db2
commit
0a10f6db26
3
NEWS
3
NEWS
@@ -112,6 +112,9 @@ PHP NEWS
|
||||
Pdo\Pgsql::prepare(…, [ PDO::ATTR_PREFETCH => 0 ]) make fetch() lazy
|
||||
instead of storing the whole result set in memory (Guillaume Outters)
|
||||
|
||||
- PDO_SQLITE:
|
||||
. throw on null bytes / resolve GH-13952 (divinity76).
|
||||
|
||||
- PGSQL:
|
||||
. Added pg_close_stmt to close a prepared statement while allowing
|
||||
its name to be reused. (David Carlier)
|
||||
|
||||
@@ -222,6 +222,10 @@ PHP 8.5 UPGRADE NOTES
|
||||
PDO::ATTR_PREFETCH sets to 0 which set to lazy fetch mode.
|
||||
In this mode, statements cannot be run parallely.
|
||||
|
||||
- PDO_SQLITE:
|
||||
. SQLite PDO::quote() will now throw an exception or emit a warning,
|
||||
depending on the error mode, if the string contains a null byte.
|
||||
|
||||
- PGSQL:
|
||||
. pg_copy_from also supports inputs as Iterable.
|
||||
. pg_connect checks if the connection_string argument contains
|
||||
|
||||
@@ -226,6 +226,19 @@ static zend_string* sqlite_handle_quoter(pdo_dbh_t *dbh, const zend_string *unqu
|
||||
if (ZSTR_LEN(unquoted) > (INT_MAX - 3) / 2) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (UNEXPECTED(zend_str_has_nul_byte(unquoted))) {
|
||||
if (dbh->error_mode == PDO_ERRMODE_EXCEPTION) {
|
||||
zend_throw_exception_ex(
|
||||
php_pdo_get_exception(), 0,
|
||||
"SQLite PDO::quote does not support null bytes");
|
||||
} else if (dbh->error_mode == PDO_ERRMODE_WARNING) {
|
||||
php_error_docref(NULL, E_WARNING,
|
||||
"SQLite PDO::quote does not support null bytes");
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
quoted = safe_emalloc(2, ZSTR_LEN(unquoted), 3);
|
||||
/* TODO use %Q format? */
|
||||
sqlite3_snprintf(2*ZSTR_LEN(unquoted) + 3, quoted, "'%q'", ZSTR_VAL(unquoted));
|
||||
@@ -741,7 +754,7 @@ static const struct pdo_dbh_methods sqlite_methods = {
|
||||
pdo_sqlite_request_shutdown,
|
||||
pdo_sqlite_in_transaction,
|
||||
pdo_sqlite_get_gc,
|
||||
pdo_sqlite_scanner
|
||||
pdo_sqlite_scanner
|
||||
};
|
||||
|
||||
static char *make_filename_safe(const char *filename)
|
||||
|
||||
79
ext/pdo_sqlite/tests/gh13952.phpt
Normal file
79
ext/pdo_sqlite/tests/gh13952.phpt
Normal file
@@ -0,0 +1,79 @@
|
||||
--TEST--
|
||||
GH-13952 (sqlite PDO::quote handles null bytes correctly)
|
||||
--EXTENSIONS--
|
||||
pdo
|
||||
pdo_sqlite
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
$modes = [
|
||||
'exception' => PDO::ERRMODE_EXCEPTION,
|
||||
'warning' => PDO::ERRMODE_WARNING,
|
||||
'silent' => PDO::ERRMODE_SILENT,
|
||||
];
|
||||
|
||||
$test_cases = [
|
||||
"",
|
||||
"x",
|
||||
"\x00",
|
||||
"a\x00b",
|
||||
"\x00\x00\x00",
|
||||
"foobar",
|
||||
"foo'''bar",
|
||||
"'foo'''bar'",
|
||||
"foo\x00bar",
|
||||
"'foo'\x00'bar'",
|
||||
"foo\x00\x00\x00bar",
|
||||
"\x00foo\x00\x00\x00bar\x00",
|
||||
"\x00\x00\x00foo",
|
||||
"foo\x00\x00\x00",
|
||||
"\x80", // << invalid UTF-8
|
||||
"\x00\x80\x00", // << invalid UTF-8 with null bytes
|
||||
];
|
||||
|
||||
foreach ($modes as $mode_name => $mode) {
|
||||
echo "Testing error mode: $mode_name\n";
|
||||
$db = new PDO('sqlite::memory:', null, null, [PDO::ATTR_ERRMODE => $mode]);
|
||||
|
||||
foreach ($test_cases as $test) {
|
||||
$contains_null = str_contains($test, "\x00");
|
||||
|
||||
if ($mode === PDO::ERRMODE_EXCEPTION && $contains_null) {
|
||||
set_error_handler(fn() => throw new PDOException(), E_WARNING);
|
||||
try {
|
||||
$db->quote($test);
|
||||
throw new LogicException("Expected exception not thrown.");
|
||||
} catch (PDOException) {
|
||||
// expected
|
||||
} finally {
|
||||
restore_error_handler();
|
||||
}
|
||||
} else {
|
||||
set_error_handler(fn() => null, E_WARNING);
|
||||
$quoted = $db->quote($test);
|
||||
restore_error_handler();
|
||||
|
||||
if ($contains_null) {
|
||||
if ($quoted !== false) {
|
||||
throw new LogicException("Expected false, got: " . var_export($quoted, true));
|
||||
}
|
||||
} else {
|
||||
if ($quoted === false) {
|
||||
throw new LogicException("Unexpected false from quote().");
|
||||
}
|
||||
$fetched = $db->query("SELECT $quoted")->fetchColumn();
|
||||
if ($fetched !== $test) {
|
||||
throw new LogicException("Data corrupted: expected " . var_export($test, true) . " got " . var_export($fetched, true));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
echo "ok\n";
|
||||
?>
|
||||
--EXPECT--
|
||||
Testing error mode: exception
|
||||
Testing error mode: warning
|
||||
Testing error mode: silent
|
||||
ok
|
||||
Reference in New Issue
Block a user