Skip to content

Commit c1b07f4

Browse files
committed
throw on null bytes / resolve phpGH-13952
fix a corruption issue where PDO::quote for SQLite would silently truncate strings with null bytes in them, by throwing. resolve phpGH-13952 close phpGH-13972
1 parent c8b3328 commit c1b07f4

File tree

2 files changed

+64
-0
lines changed

2 files changed

+64
-0
lines changed

ext/pdo_sqlite/sqlite_driver.c

+5
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,11 @@ static zend_string* sqlite_handle_quoter(pdo_dbh_t *dbh, const zend_string *unqu
226226
if (ZSTR_LEN(unquoted) > (INT_MAX - 3) / 2) {
227227
return NULL;
228228
}
229+
if(memchr(ZSTR_VAL(unquoted), '\0', ZSTR_LEN(unquoted)) != NULL) {
230+
zend_throw_exception_ex(php_pdo_get_exception(), 0,
231+
"SQLite PDO::quote does not support NULL bytes");
232+
return NULL;
233+
}
229234
quoted = safe_emalloc(2, ZSTR_LEN(unquoted), 3);
230235
/* TODO use %Q format? */
231236
sqlite3_snprintf(2*ZSTR_LEN(unquoted) + 3, quoted, "'%q'", ZSTR_VAL(unquoted));

ext/pdo_sqlite/tests/gh13952.phpt

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
--TEST--
2+
GH-13952 (sqlite PDO::quote silently corrupts strings with null bytes)
3+
--EXTENSIONS--
4+
pdo
5+
pdo_sqlite
6+
--FILE--
7+
<?php
8+
$db = new \PDO('sqlite::memory:', null, null, [
9+
\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
10+
\PDO::ATTR_DEFAULT_FETCH_MODE => \PDO::FETCH_ASSOC,
11+
\PDO::ATTR_EMULATE_PREPARES => false,
12+
]);
13+
14+
$test_cases = [
15+
"",
16+
"x",
17+
"\x00",
18+
"a\x00b",
19+
"\x00\x00\x00",
20+
"foobar",
21+
"foo'''bar",
22+
"'foo'''bar'",
23+
"foo\x00bar",
24+
"'foo'\x00'bar'",
25+
"foo\x00\x00\x00bar",
26+
"\x00foo\x00\x00\x00bar\x00",
27+
"\x00\x00\x00foo",
28+
"foo\x00\x00\x00",
29+
"\x80", // Invalid UTF-8 sequence
30+
"\x00\x80\x00", // Invalid UTF-8 sequence with null bytes
31+
];
32+
33+
foreach ($test_cases as $test) {
34+
$should_throw = str_contains($test, "\x00");
35+
try {
36+
$quoted = $db->quote($test);
37+
if ($should_throw) {
38+
$displayTest = var_export($test, true);
39+
throw new LogicException("Failed for {$displayTest}: expected an exception but none was thrown.");
40+
}
41+
} catch (\PDOException $e) {
42+
if (!$should_throw) {
43+
$displayTest = var_export($test, true);
44+
throw new LogicException("Failed for {$displayTest}: unexpected exception thrown.", 0, $e);
45+
}
46+
// Exception is expected
47+
continue;
48+
}
49+
$fetched = $db->query("SELECT $quoted")->fetch($db::FETCH_NUM)[0];
50+
if ($fetched !== $test) {
51+
$displayTest = var_export($test, true);
52+
$displayFetched = var_export($fetched, true);
53+
throw new LogicException("Round-trip data corruption for {$displayTest}: got {$displayFetched}.");
54+
}
55+
}
56+
echo "ok\n";
57+
?>
58+
--EXPECT--
59+
ok

0 commit comments

Comments
 (0)