Skip to content

Commit 23b4581

Browse files
committed
Add chunk boundary test for read_quoted_text() OOB
Add a test that simulates the real-world trigger: a streaming SQL processor splitting input at a chunk boundary that falls inside a quoted string, with a backslash as the last byte. This is the exact scenario from MySQL dump processing where FROM_BASE64() is replaced with regular escaped string literals.
1 parent f8bd020 commit 23b4581

1 file changed

Lines changed: 42 additions & 0 deletions

File tree

packages/mysql-on-sqlite/tests/mysql/WP_MySQL_Lexer_Tests.php

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,48 @@ public function data_valid_escaped_strings(): array {
325325
);
326326
}
327327

328+
/**
329+
* Test that a chunk boundary splitting a quoted string with a trailing
330+
* backslash does not cause an out-of-bounds string access.
331+
*
332+
* This simulates streaming SQL processing where a buffer boundary falls
333+
* inside a string literal right after a backslash escape character.
334+
*/
335+
public function test_chunk_boundary_inside_escaped_string(): void {
336+
set_error_handler(
337+
function ( $severity, $message, $file, $line ) {
338+
throw new \ErrorException( $message, 0, $severity, $file, $line );
339+
},
340+
E_WARNING | E_NOTICE
341+
);
342+
343+
try {
344+
// Build a SQL string where a backslash falls at the chunk boundary.
345+
// The string content before the boundary is padded to place the
346+
// backslash at exactly position $chunk_size - 1.
347+
$chunk_size = 8192;
348+
349+
// "SELECT '" = 8 bytes, so we need chunk_size - 8 - 1 bytes of
350+
// padding before the trailing backslash to place '\' at the last
351+
// byte of the chunk.
352+
$padding = str_repeat( 'A', $chunk_size - 8 - 1 );
353+
$sql = "SELECT '" . $padding . "\\";
354+
355+
// The chunk is exactly $chunk_size bytes. The last byte is '\'.
356+
// The lexer should handle this as an unclosed string without OOB.
357+
$this->assertSame( $chunk_size, strlen( $sql ) );
358+
359+
$lexer = new WP_MySQL_Lexer( $sql );
360+
while ( $lexer->next_token() ) {
361+
// Consume all tokens.
362+
}
363+
} finally {
364+
restore_error_handler();
365+
}
366+
367+
$this->assertNull( $lexer->get_token() );
368+
}
369+
328370
private function get_token_names( array $token_types ): array {
329371
return array_map(
330372
function ( $token_type ) {

0 commit comments

Comments
 (0)