Skip to content

Commit ac4c145

Browse files
committed
Fix GH-23756: crash when a collation callback closes the statement cursor
1 parent b55c619 commit ac4c145

7 files changed

Lines changed: 180 additions & 2 deletions

File tree

‎NEWS‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ PHP NEWS
3232
. Fixed PDOStatement::getColumnMeta() reading out of bounds for an invalid
3333
column index. (Ilia Alshanetsky)
3434

35+
- PDO Sqlite:
36+
. Fixed bug GH-23756 (crash when a callback closes the cursor of or
37+
re-executes the statement being executed). (Lazizbek Ergashev)
38+
3539
- Sockets:
3640
. Fixed socket_select() silently truncating sets larger than FD_SETSIZE on
3741
Windows. (David Carlier)

‎ext/pdo_sqlite/php_pdo_sqlite_int.h‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ typedef struct {
5757
sqlite3_stmt *stmt;
5858
unsigned pre_fetched:1;
5959
unsigned done:1;
60+
unsigned stepping:1;
6061
} pdo_sqlite_stmt;
6162

6263
extern const pdo_driver_t pdo_sqlite_driver;

‎ext/pdo_sqlite/sqlite_statement.c‎

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,16 +39,41 @@ static int pdo_sqlite_stmt_dtor(pdo_stmt_t *stmt)
3939
return 1;
4040
}
4141

42+
static int pdo_sqlite_stmt_step(pdo_sqlite_stmt *S)
43+
{
44+
int return_code;
45+
bool bailout = false;
46+
47+
S->stepping = 1;
48+
zend_try {
49+
return_code = sqlite3_step(S->stmt);
50+
} zend_catch {
51+
bailout = true;
52+
} zend_end_try();
53+
S->stepping = 0;
54+
55+
if (bailout) {
56+
zend_bailout();
57+
}
58+
59+
return return_code;
60+
}
61+
4262
static int pdo_sqlite_stmt_execute(pdo_stmt_t *stmt)
4363
{
4464
pdo_sqlite_stmt *S = (pdo_sqlite_stmt*)stmt->driver_data;
4565

66+
if (S->stepping) {
67+
zend_throw_error(NULL, "Cannot execute a PDOStatement while it is executing");
68+
return 0;
69+
}
70+
4671
if (stmt->executed && !S->done) {
4772
sqlite3_reset(S->stmt);
4873
}
4974

5075
S->done = 0;
51-
switch (sqlite3_step(S->stmt)) {
76+
switch (pdo_sqlite_stmt_step(S)) {
5277
case SQLITE_ROW:
5378
S->pre_fetched = 1;
5479
php_pdo_stmt_set_column_count(stmt, sqlite3_data_count(S->stmt));
@@ -80,6 +105,11 @@ static int pdo_sqlite_stmt_param_hook(pdo_stmt_t *stmt, struct pdo_bound_param_d
80105

81106
switch (event_type) {
82107
case PDO_PARAM_EVT_EXEC_PRE:
108+
if (S->stepping) {
109+
zend_throw_error(NULL, "Cannot execute a PDOStatement while it is executing");
110+
return 0;
111+
}
112+
83113
if (stmt->executed && !S->done) {
84114
sqlite3_reset(S->stmt);
85115
S->done = 1;
@@ -214,7 +244,11 @@ static int pdo_sqlite_stmt_fetch(pdo_stmt_t *stmt,
214244
if (S->done) {
215245
return 0;
216246
}
217-
i = sqlite3_step(S->stmt);
247+
if (S->stepping) {
248+
zend_throw_error(NULL, "Cannot fetch from a PDOStatement while it is executing");
249+
return 0;
250+
}
251+
i = pdo_sqlite_stmt_step(S);
218252
switch (i) {
219253
case SQLITE_ROW:
220254
return 1;
@@ -363,6 +397,12 @@ static int pdo_sqlite_stmt_col_meta(pdo_stmt_t *stmt, zend_long colno, zval *ret
363397
static int pdo_sqlite_stmt_cursor_closer(pdo_stmt_t *stmt)
364398
{
365399
pdo_sqlite_stmt *S = (pdo_sqlite_stmt*)stmt->driver_data;
400+
401+
if (S->stepping) {
402+
zend_throw_error(NULL, "Cannot close the cursor of a PDOStatement while it is executing");
403+
return 0;
404+
}
405+
366406
sqlite3_reset(S->stmt);
367407
return 1;
368408
}

‎ext/pdo_sqlite/tests/gh23756.phpt‎

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
--TEST--
2+
GH-23756 (crash when closeCursor() is called from a collation callback)
3+
--EXTENSIONS--
4+
pdo_sqlite
5+
--FILE--
6+
<?php
7+
$db = Pdo\Sqlite::connect('sqlite::memory:');
8+
$db->exec('CREATE TABLE t (x TEXT)');
9+
$db->exec("INSERT INTO t VALUES ('b'), ('a'), ('c')");
10+
11+
$stmt = null;
12+
$db->createCollation('evil', function ($a, $b) use (&$stmt) {
13+
$stmt->closeCursor();
14+
return $a <=> $b;
15+
});
16+
17+
$stmt = $db->prepare('SELECT x FROM t ORDER BY x COLLATE evil');
18+
try {
19+
$stmt->execute();
20+
} catch (Error $e) {
21+
echo $e->getMessage(), "\n";
22+
}
23+
24+
var_dump($db->query('SELECT x FROM t ORDER BY x')->fetchAll(PDO::FETCH_COLUMN));
25+
?>
26+
--EXPECT--
27+
Cannot close the cursor of a PDOStatement while it is executing
28+
array(3) {
29+
[0]=>
30+
string(1) "a"
31+
[1]=>
32+
string(1) "b"
33+
[2]=>
34+
string(1) "c"
35+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
--TEST--
2+
GH-23756 (crash when closeCursor() is called from a collation callback while fetching)
3+
--EXTENSIONS--
4+
pdo_sqlite
5+
--FILE--
6+
<?php
7+
$db = Pdo\Sqlite::connect('sqlite::memory:');
8+
$db->exec('CREATE TABLE t (x TEXT)');
9+
$db->exec("INSERT INTO t VALUES ('b'), ('a'), ('c')");
10+
11+
$stmt = null;
12+
$armed = false;
13+
$db->createCollation('evil', function ($a, $b) use (&$stmt, &$armed) {
14+
if ($armed) {
15+
$armed = false;
16+
$stmt->closeCursor();
17+
}
18+
return $a <=> $b;
19+
});
20+
21+
$stmt = $db->prepare("SELECT x FROM t WHERE x <> 'zzz' COLLATE evil");
22+
$stmt->execute();
23+
var_dump($stmt->fetchColumn());
24+
25+
$armed = true;
26+
try {
27+
$stmt->fetchColumn();
28+
} catch (Error $e) {
29+
echo $e->getMessage(), "\n";
30+
}
31+
?>
32+
--EXPECT--
33+
string(1) "b"
34+
Cannot close the cursor of a PDOStatement while it is executing
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
--TEST--
2+
GH-23756 (crash when execute() is called from a collation callback)
3+
--EXTENSIONS--
4+
pdo_sqlite
5+
--FILE--
6+
<?php
7+
$db = Pdo\Sqlite::connect('sqlite::memory:');
8+
$db->exec('CREATE TABLE t (x TEXT)');
9+
$db->exec("INSERT INTO t VALUES ('b'), ('a'), ('c')");
10+
11+
$stmt = null;
12+
$armed = false;
13+
$db->createCollation('evil', function ($a, $b) use (&$stmt, &$armed) {
14+
if ($armed) {
15+
$armed = false;
16+
$stmt->execute();
17+
}
18+
return $a <=> $b;
19+
});
20+
21+
$stmt = $db->prepare('SELECT x FROM t ORDER BY x COLLATE evil');
22+
$armed = true;
23+
try {
24+
$stmt->execute();
25+
} catch (Error $e) {
26+
echo 'without parameters: ', $e->getMessage(), "\n";
27+
}
28+
29+
$stmt = $db->prepare('SELECT x FROM t WHERE x <> :p ORDER BY x COLLATE evil');
30+
$stmt->bindValue(':p', 'zzz');
31+
$armed = true;
32+
try {
33+
$stmt->execute();
34+
} catch (Error $e) {
35+
echo 'with a bound parameter: ', $e->getMessage(), "\n";
36+
}
37+
?>
38+
--EXPECT--
39+
without parameters: Cannot execute a PDOStatement while it is executing
40+
with a bound parameter: Cannot execute a PDOStatement while it is executing
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
--TEST--
2+
GH-23756 (closeCursor() still works after a collation callback bails out)
3+
--EXTENSIONS--
4+
pdo_sqlite
5+
--FILE--
6+
<?php
7+
$db = Pdo\Sqlite::connect('sqlite::memory:');
8+
$db->exec('CREATE TABLE t (x TEXT)');
9+
$db->exec("INSERT INTO t VALUES ('b'), ('a'), ('c')");
10+
11+
$db->createCollation('evil', function ($a, $b) {
12+
exit("bailing out\n");
13+
});
14+
15+
register_shutdown_function(function () use (&$stmt) {
16+
var_dump($stmt->closeCursor());
17+
});
18+
19+
$stmt = $db->prepare('SELECT x FROM t ORDER BY x COLLATE evil');
20+
$stmt->execute();
21+
?>
22+
--EXPECT--
23+
bailing out
24+
bool(true)

0 commit comments

Comments
 (0)