Skip to content

Commit 73841a9

Browse files
jamescater2claude
andcommitted
Follow-ups to 854eeb0: funcptrs_win v3-span backfill + string-overload AOOR guard
Two extensions of the same bug family 854eeb0 addressed for span overloads: 1. funcptrs_win.cs v3-span ReadOnlySpan overload was missed in 854eeb0 (the other three provider variants got the guard there, and the provider.tt template was already updated correctly). Re-sync funcptrs_win.cs with the template. 2. raw.cs string overloads of sqlite3_prepare_v2/v3 still sliced the returned span unconditionally. With 854eeb0 making the provider return an empty span on error, the slice becomes sp_tail.Slice(0, -1) and throws ArgumentOutOfRangeException. Guard the slice behind sp_tail.Length > 0. Adds two regression tests that deterministically fire the AOOR on unpatched string-overload code by closing the db with manual_close_v2 and growing the heap into the bit-31-clear address range. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 854eeb0 commit 73841a9

3 files changed

Lines changed: 67 additions & 6 deletions

File tree

src/SQLitePCLRaw.core/raw.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -830,7 +830,8 @@ static public int sqlite3_prepare_v2(sqlite3 db, string sql, out sqlite3_stmt st
830830
var ba = sql.to_utf8_with_z();
831831
var sp = new ReadOnlySpan<byte>(ba);
832832
int rc = sqlite3_prepare_v2(db, sp, out stmt, out var sp_tail);
833-
tail = utf8_span_to_string(sp_tail.Slice(0, sp_tail.Length - 1));
833+
// Audit #1: provider now returns an empty span on error; guard Slice(0, -1).
834+
tail = sp_tail.Length > 0 ? utf8_span_to_string(sp_tail.Slice(0, sp_tail.Length - 1)) : "";
834835
return rc;
835836
}
836837

@@ -874,7 +875,8 @@ static public int sqlite3_prepare_v3(sqlite3 db, string sql, uint flags, out sql
874875
var ba = sql.to_utf8_with_z();
875876
var sp = new ReadOnlySpan<byte>(ba);
876877
int rc = sqlite3_prepare_v3(db, sp, flags, out stmt, out var sp_tail);
877-
tail = utf8_span_to_string(sp_tail.Slice(0, sp_tail.Length - 1));
878+
// Audit #1: provider now returns an empty span on error; guard Slice(0, -1).
879+
tail = sp_tail.Length > 0 ? utf8_span_to_string(sp_tail.Slice(0, sp_tail.Length - 1)) : "";
878880
return rc;
879881
}
880882

src/SQLitePCLRaw.provider.e_sqlite3/Generated/provider_e_sqlite3_funcptrs_win.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -304,11 +304,12 @@ unsafe int ISQLite3Provider.sqlite3_prepare_v3(sqlite3 db, ReadOnlySpan<byte> sq
304304
fixed (byte* p_sql = sql)
305305
{
306306
var rc = NativeMethods.sqlite3_prepare_v3(db, p_sql, sql.Length, flags, out stm, out var p_tail);
307-
var len_consumed = (int) (p_tail - p_sql);
308-
int len_remain = sql.Length - len_consumed;
309-
if (len_remain > 0)
307+
// Follow-up to 854eeb0: funcptrs_win's v3 span variant was missed in that commit — same p_tail bounds guard as the other three provider variants.
308+
if (p_tail != null && p_tail >= p_sql && p_tail <= p_sql + sql.Length)
310309
{
311-
tail = sql.Slice(len_consumed, len_remain);
310+
var len_consumed = (int) (p_tail - p_sql);
311+
int len_remain = sql.Length - len_consumed;
312+
tail = len_remain > 0 ? sql.Slice(len_consumed, len_remain) : ReadOnlySpan<byte>.Empty;
312313
}
313314
else
314315
{

src/common/tests_xunit.cs

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -433,6 +433,64 @@ public void test_prepare_v3_span_tolerates_uninitialised_pzTail()
433433
GC.KeepAlive(rolling);
434434
}
435435

436+
// Audit #1: the string overloads of prepare_v2/v3 used to throw
437+
// ArgumentOutOfRangeException on the same MISUSE path because they
438+
// unconditionally sliced the (now empty) tail span. These tests
439+
// assert they return rc cleanly with an empty tail string instead.
440+
[Fact]
441+
public void test_prepare_v2_string_tolerates_uninitialised_pzTail()
442+
{
443+
var rolling = new byte[256][];
444+
var rnd = new Random(42);
445+
for (var i = 0; i < 512; i++)
446+
{
447+
rolling[i % rolling.Length] = new byte[rnd.Next(1024, 128 * 1024)];
448+
449+
var db = ugly.open(":memory:");
450+
db.manual_close_v2();
451+
try
452+
{
453+
int rc = raw.sqlite3_prepare_v2(db, "SELECT 1;", out var stmt, out string tail);
454+
455+
Assert.Equal(raw.SQLITE_MISUSE, rc);
456+
Assert.Equal("", tail);
457+
stmt?.Dispose();
458+
}
459+
finally
460+
{
461+
db.Dispose();
462+
}
463+
}
464+
GC.KeepAlive(rolling);
465+
}
466+
467+
[Fact]
468+
public void test_prepare_v3_string_tolerates_uninitialised_pzTail()
469+
{
470+
var rolling = new byte[256][];
471+
var rnd = new Random(42);
472+
for (var i = 0; i < 512; i++)
473+
{
474+
rolling[i % rolling.Length] = new byte[rnd.Next(1024, 128 * 1024)];
475+
476+
var db = ugly.open(":memory:");
477+
db.manual_close_v2();
478+
try
479+
{
480+
int rc = raw.sqlite3_prepare_v3(db, "SELECT 1;", 0, out var stmt, out string tail);
481+
482+
Assert.Equal(raw.SQLITE_MISUSE, rc);
483+
Assert.Equal("", tail);
484+
stmt?.Dispose();
485+
}
486+
finally
487+
{
488+
db.Dispose();
489+
}
490+
}
491+
GC.KeepAlive(rolling);
492+
}
493+
436494
[Fact]
437495
public void test_bind_parameter_index()
438496
{

0 commit comments

Comments
 (0)