diff --git a/src/SQLitePCLRaw.provider.e_sqlite3/Generated/provider_e_sqlite3_funcptrs_notwin.cs b/src/SQLitePCLRaw.provider.e_sqlite3/Generated/provider_e_sqlite3_funcptrs_notwin.cs index c3de4c0c..b199fd5e 100644 --- a/src/SQLitePCLRaw.provider.e_sqlite3/Generated/provider_e_sqlite3_funcptrs_notwin.cs +++ b/src/SQLitePCLRaw.provider.e_sqlite3/Generated/provider_e_sqlite3_funcptrs_notwin.cs @@ -270,11 +270,11 @@ unsafe int ISQLite3Provider.sqlite3_prepare_v2(sqlite3 db, ReadOnlySpan sq fixed (byte* p_sql = sql) { var rc = NativeMethods.sqlite3_prepare_v2(db, p_sql, sql.Length, out stm, out var p_tail); - var len_consumed = (int) (p_tail - p_sql); - int len_remain = sql.Length - len_consumed; - if (len_remain > 0) + if (p_tail != null && p_tail >= p_sql && p_tail <= p_sql + sql.Length) { - tail = sql.Slice(len_consumed, len_remain); + var len_consumed = (int) (p_tail - p_sql); + int len_remain = sql.Length - len_consumed; + tail = len_remain > 0 ? sql.Slice(len_consumed, len_remain) : ReadOnlySpan.Empty; } else { @@ -301,11 +301,11 @@ unsafe int ISQLite3Provider.sqlite3_prepare_v3(sqlite3 db, ReadOnlySpan sq fixed (byte* p_sql = sql) { var rc = NativeMethods.sqlite3_prepare_v3(db, p_sql, sql.Length, flags, out stm, out var p_tail); - var len_consumed = (int) (p_tail - p_sql); - int len_remain = sql.Length - len_consumed; - if (len_remain > 0) + if (p_tail != null && p_tail >= p_sql && p_tail <= p_sql + sql.Length) { - tail = sql.Slice(len_consumed, len_remain); + var len_consumed = (int) (p_tail - p_sql); + int len_remain = sql.Length - len_consumed; + tail = len_remain > 0 ? sql.Slice(len_consumed, len_remain) : ReadOnlySpan.Empty; } else { diff --git a/src/SQLitePCLRaw.provider.e_sqlite3/Generated/provider_e_sqlite3_funcptrs_win.cs b/src/SQLitePCLRaw.provider.e_sqlite3/Generated/provider_e_sqlite3_funcptrs_win.cs index c702f004..1ef85ec7 100644 --- a/src/SQLitePCLRaw.provider.e_sqlite3/Generated/provider_e_sqlite3_funcptrs_win.cs +++ b/src/SQLitePCLRaw.provider.e_sqlite3/Generated/provider_e_sqlite3_funcptrs_win.cs @@ -273,11 +273,11 @@ unsafe int ISQLite3Provider.sqlite3_prepare_v2(sqlite3 db, ReadOnlySpan sq fixed (byte* p_sql = sql) { var rc = NativeMethods.sqlite3_prepare_v2(db, p_sql, sql.Length, out stm, out var p_tail); - var len_consumed = (int) (p_tail - p_sql); - int len_remain = sql.Length - len_consumed; - if (len_remain > 0) + if (p_tail != null && p_tail >= p_sql && p_tail <= p_sql + sql.Length) { - tail = sql.Slice(len_consumed, len_remain); + var len_consumed = (int) (p_tail - p_sql); + int len_remain = sql.Length - len_consumed; + tail = len_remain > 0 ? sql.Slice(len_consumed, len_remain) : ReadOnlySpan.Empty; } else { diff --git a/src/SQLitePCLRaw.provider.e_sqlite3/Generated/provider_e_sqlite3_prenet5_notwin.cs b/src/SQLitePCLRaw.provider.e_sqlite3/Generated/provider_e_sqlite3_prenet5_notwin.cs index be1fccb6..95efe09b 100644 --- a/src/SQLitePCLRaw.provider.e_sqlite3/Generated/provider_e_sqlite3_prenet5_notwin.cs +++ b/src/SQLitePCLRaw.provider.e_sqlite3/Generated/provider_e_sqlite3_prenet5_notwin.cs @@ -269,11 +269,11 @@ unsafe int ISQLite3Provider.sqlite3_prepare_v2(sqlite3 db, ReadOnlySpan sq fixed (byte* p_sql = sql) { var rc = NativeMethods.sqlite3_prepare_v2(db, p_sql, sql.Length, out stm, out var p_tail); - var len_consumed = (int) (p_tail - p_sql); - int len_remain = sql.Length - len_consumed; - if (len_remain > 0) + if (p_tail != null && p_tail >= p_sql && p_tail <= p_sql + sql.Length) { - tail = sql.Slice(len_consumed, len_remain); + var len_consumed = (int) (p_tail - p_sql); + int len_remain = sql.Length - len_consumed; + tail = len_remain > 0 ? sql.Slice(len_consumed, len_remain) : ReadOnlySpan.Empty; } else { @@ -300,11 +300,11 @@ unsafe int ISQLite3Provider.sqlite3_prepare_v3(sqlite3 db, ReadOnlySpan sq fixed (byte* p_sql = sql) { var rc = NativeMethods.sqlite3_prepare_v3(db, p_sql, sql.Length, flags, out stm, out var p_tail); - var len_consumed = (int) (p_tail - p_sql); - int len_remain = sql.Length - len_consumed; - if (len_remain > 0) + if (p_tail != null && p_tail >= p_sql && p_tail <= p_sql + sql.Length) { - tail = sql.Slice(len_consumed, len_remain); + var len_consumed = (int) (p_tail - p_sql); + int len_remain = sql.Length - len_consumed; + tail = len_remain > 0 ? sql.Slice(len_consumed, len_remain) : ReadOnlySpan.Empty; } else { diff --git a/src/SQLitePCLRaw.provider.e_sqlite3/Generated/provider_e_sqlite3_prenet5_win.cs b/src/SQLitePCLRaw.provider.e_sqlite3/Generated/provider_e_sqlite3_prenet5_win.cs index 566752e5..45108262 100644 --- a/src/SQLitePCLRaw.provider.e_sqlite3/Generated/provider_e_sqlite3_prenet5_win.cs +++ b/src/SQLitePCLRaw.provider.e_sqlite3/Generated/provider_e_sqlite3_prenet5_win.cs @@ -272,11 +272,11 @@ unsafe int ISQLite3Provider.sqlite3_prepare_v2(sqlite3 db, ReadOnlySpan sq fixed (byte* p_sql = sql) { var rc = NativeMethods.sqlite3_prepare_v2(db, p_sql, sql.Length, out stm, out var p_tail); - var len_consumed = (int) (p_tail - p_sql); - int len_remain = sql.Length - len_consumed; - if (len_remain > 0) + if (p_tail != null && p_tail >= p_sql && p_tail <= p_sql + sql.Length) { - tail = sql.Slice(len_consumed, len_remain); + var len_consumed = (int) (p_tail - p_sql); + int len_remain = sql.Length - len_consumed; + tail = len_remain > 0 ? sql.Slice(len_consumed, len_remain) : ReadOnlySpan.Empty; } else { @@ -303,11 +303,11 @@ unsafe int ISQLite3Provider.sqlite3_prepare_v3(sqlite3 db, ReadOnlySpan sq fixed (byte* p_sql = sql) { var rc = NativeMethods.sqlite3_prepare_v3(db, p_sql, sql.Length, flags, out stm, out var p_tail); - var len_consumed = (int) (p_tail - p_sql); - int len_remain = sql.Length - len_consumed; - if (len_remain > 0) + if (p_tail != null && p_tail >= p_sql && p_tail <= p_sql + sql.Length) { - tail = sql.Slice(len_consumed, len_remain); + var len_consumed = (int) (p_tail - p_sql); + int len_remain = sql.Length - len_consumed; + tail = len_remain > 0 ? sql.Slice(len_consumed, len_remain) : ReadOnlySpan.Empty; } else { diff --git a/src/common/tests_xunit.cs b/src/common/tests_xunit.cs index 7bb69d66..c6b579f2 100644 --- a/src/common/tests_xunit.cs +++ b/src/common/tests_xunit.cs @@ -349,6 +349,90 @@ public void test_prepare_tail_string() } } + // Regression test for the ArgumentOutOfRangeException variant of the + // long-standing prepare-race bug class (see issues #108, #321, #430, + // #479, #588). The provider's span overload of sqlite3_prepare_v2 + // computes `(int)(p_tail - p_sql)` and uses it as a Slice start. + // + // On SQLITE_MISUSE paths (unsafe db handle, null zSql), native returns + // without writing *pzTail, leaving the caller's `out byte* p_tail` + // local at its .locals-init default of 0. For a non-empty SQL input, + // p_sql is a real non-null pointer, so `p_tail - p_sql` is a 64-bit + // signed difference that truncates to an int whose sign depends on + // bit 31 of the low 32 bits of p_sql. When bit 31 is clear (the sql + // buffer sits at a typical high managed-heap address on x64), the + // cast produces a large negative int, and `sql.Slice(negative, ...)` + // throws ArgumentOutOfRangeException instead of cleanly returning + // the error rc to the caller. + // + // We trigger the unsafe-db path deterministically via manual_close_v2, + // and we force the sql buffer into the bit-31-clear address range by + // growing the heap with a rolling allocator before each iteration. + // On a fresh process the heap can start either side of 2^31, so + // without the rolling growth the test is stochastic. With it, every + // iteration fires the bug on unpatched builds. + // + // The patched provider bounds-checks p_tail against + // [p_sql, p_sql + sql.Length] before computing the slice, so this + // returns rc=SQLITE_MISUSE + tail=Empty instead of throwing. + [Fact] + public void test_prepare_v2_span_tolerates_uninitialised_pzTail() + { + var rolling = new byte[256][]; + var rnd = new Random(42); + for (var i = 0; i < 512; i++) + { + rolling[i % rolling.Length] = new byte[rnd.Next(1024, 128 * 1024)]; + + var db = ugly.open(":memory:"); + db.manual_close_v2(); + try + { + var sql = u.to_utf8("SELECT 1;"); + + int rc = raw.sqlite3_prepare_v2(db, sql.AsSpan(), out var stmt, out var tail); + + Assert.Equal(raw.SQLITE_MISUSE, rc); + Assert.True(tail.IsEmpty); + stmt?.Dispose(); + } + finally + { + db.Dispose(); + } + } + GC.KeepAlive(rolling); + } + + [Fact] + public void test_prepare_v3_span_tolerates_uninitialised_pzTail() + { + var rolling = new byte[256][]; + var rnd = new Random(42); + for (var i = 0; i < 512; i++) + { + rolling[i % rolling.Length] = new byte[rnd.Next(1024, 128 * 1024)]; + + var db = ugly.open(":memory:"); + db.manual_close_v2(); + try + { + var sql = u.to_utf8("SELECT 1;"); + + int rc = raw.sqlite3_prepare_v3(db, sql.AsSpan(), 0, out var stmt, out var tail); + + Assert.Equal(raw.SQLITE_MISUSE, rc); + Assert.True(tail.IsEmpty); + stmt?.Dispose(); + } + finally + { + db.Dispose(); + } + } + GC.KeepAlive(rolling); + } + [Fact] public void test_bind_parameter_index() { diff --git a/src/providers/provider.tt b/src/providers/provider.tt index e7f3c92b..7b2e745c 100644 --- a/src/providers/provider.tt +++ b/src/providers/provider.tt @@ -528,11 +528,11 @@ namespace SQLitePCL fixed (byte* p_sql = sql) { var rc = NativeMethods.sqlite3_prepare_v2(db, p_sql, sql.Length, out stm, out var p_tail); - var len_consumed = (int) (p_tail - p_sql); - int len_remain = sql.Length - len_consumed; - if (len_remain > 0) + if (p_tail != null && p_tail >= p_sql && p_tail <= p_sql + sql.Length) { - tail = sql.Slice(len_consumed, len_remain); + var len_consumed = (int) (p_tail - p_sql); + int len_remain = sql.Length - len_consumed; + tail = len_remain > 0 ? sql.Slice(len_consumed, len_remain) : ReadOnlySpan.Empty; } else { @@ -559,11 +559,11 @@ namespace SQLitePCL fixed (byte* p_sql = sql) { var rc = NativeMethods.sqlite3_prepare_v3(db, p_sql, sql.Length, flags, out stm, out var p_tail); - var len_consumed = (int) (p_tail - p_sql); - int len_remain = sql.Length - len_consumed; - if (len_remain > 0) + if (p_tail != null && p_tail >= p_sql && p_tail <= p_sql + sql.Length) { - tail = sql.Slice(len_consumed, len_remain); + var len_consumed = (int) (p_tail - p_sql); + int len_remain = sql.Length - len_consumed; + tail = len_remain > 0 ? sql.Slice(len_consumed, len_remain) : ReadOnlySpan.Empty; } else { diff --git a/src/tests/my_batteries_v2.cs b/src/tests/my_batteries_v2.cs index aadb8e26..2e06b6e3 100644 --- a/src/tests/my_batteries_v2.cs +++ b/src/tests/my_batteries_v2.cs @@ -1,5 +1,5 @@ -/* - Copyright 2014-2019 SourceGear, LLC +/* + Copyright 2014-2026 SourceGear, LLC Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -14,55 +14,13 @@ You may obtain a copy of the License at limitations under the License. */ -using System; -using System.Collections.Generic; -using System.Reflection; -using System.IO; - namespace SQLitePCL { public static class Batteries_V2 { - class MyGetFunctionPointer : IGetFunctionPointer - { - readonly IntPtr _dll; - public MyGetFunctionPointer(IntPtr dll) - { - _dll = dll; - } - - public IntPtr GetFunctionPointer(string name) - { - if (NativeLibrary.TryGetExport(_dll, name, out var f)) - { - //System.Console.WriteLine("{0}.{1} : {2}", _dll, name, f); - return f; - } - else - { - return IntPtr.Zero; - } - } - } - static IGetFunctionPointer MakeDynamic(string name, int flags) - { - // TODO should this be GetExecutingAssembly()? - var assy = typeof(SQLitePCL.raw).Assembly; - var dll = SQLitePCL.NativeLibrary.Load(name, assy, flags); - var gf = new MyGetFunctionPointer(dll); - return gf; - } - static void DoDynamic_cdecl(string name, int flags) - { - var gf = MakeDynamic(name, flags); - SQLitePCL.SQLite3Provider_dynamic_cdecl.Setup(name, gf); - SQLitePCL.raw.SetProvider(new SQLite3Provider_dynamic_cdecl()); - } - public static void Init() { - DoDynamic_cdecl("e_sqlite3", NativeLibrary.WHERE_PLAIN); + raw.SetProvider(new SQLite3Provider_e_sqlite3()); } } } - diff --git a/src/tests/tests.csproj b/src/tests/tests.csproj index bdbdf27b..2a2859fa 100644 --- a/src/tests/tests.csproj +++ b/src/tests/tests.csproj @@ -1,19 +1,17 @@ - netcoreapp3.1 + $(tfm_net) false true + false - - - - - + + + - - +