Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -270,11 +270,11 @@ unsafe int ISQLite3Provider.sqlite3_prepare_v2(sqlite3 db, ReadOnlySpan<byte> 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<byte>.Empty;
}
else
{
Expand All @@ -301,11 +301,11 @@ unsafe int ISQLite3Provider.sqlite3_prepare_v3(sqlite3 db, ReadOnlySpan<byte> 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<byte>.Empty;
}
else
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -273,11 +273,11 @@ unsafe int ISQLite3Provider.sqlite3_prepare_v2(sqlite3 db, ReadOnlySpan<byte> 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<byte>.Empty;
}
else
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -269,11 +269,11 @@ unsafe int ISQLite3Provider.sqlite3_prepare_v2(sqlite3 db, ReadOnlySpan<byte> 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<byte>.Empty;
}
else
{
Expand All @@ -300,11 +300,11 @@ unsafe int ISQLite3Provider.sqlite3_prepare_v3(sqlite3 db, ReadOnlySpan<byte> 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<byte>.Empty;
}
else
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -272,11 +272,11 @@ unsafe int ISQLite3Provider.sqlite3_prepare_v2(sqlite3 db, ReadOnlySpan<byte> 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<byte>.Empty;
}
else
{
Expand All @@ -303,11 +303,11 @@ unsafe int ISQLite3Provider.sqlite3_prepare_v3(sqlite3 db, ReadOnlySpan<byte> 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<byte>.Empty;
}
else
{
Expand Down
84 changes: 84 additions & 0 deletions src/common/tests_xunit.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
{
Expand Down
16 changes: 8 additions & 8 deletions src/providers/provider.tt
Original file line number Diff line number Diff line change
Expand Up @@ -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<byte>.Empty;
}
else
{
Expand All @@ -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<byte>.Empty;
}
else
{
Expand Down
48 changes: 3 additions & 45 deletions src/tests/my_batteries_v2.cs
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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());
}
}
}

14 changes: 6 additions & 8 deletions src/tests/tests.csproj
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<TargetFramework>$(tfm_net)</TargetFramework>
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\SQLitePCLRaw.core\SQLitePCLRaw.core.csproj" />
<ProjectReference Include="..\SQLitePCLRaw.provider.dynamic_cdecl\SQLitePCLRaw.provider.dynamic_cdecl.csproj" />
<ProjectReference Include="..\SQLitePCLRaw.nativelibrary\SQLitePCLRaw.nativelibrary.csproj" />
<!-- <ProjectReference Include="..\SQLitePCLRaw.batteries_v2.e_sqlite3.dynamic\SQLitePCLRaw.batteries_v2.e_sqlite3.dynamic.csproj" /> -->
<ProjectReference Include="..\SQLitePCLRaw.ugly\SQLitePCLRaw.ugly.csproj" />
<ProjectReference Include="..\SQLitePCLRaw.core\SQLitePCLRaw.core.csproj" />
<ProjectReference Include="..\SQLitePCLRaw.provider.e_sqlite3\SQLitePCLRaw.provider.e_sqlite3.csproj" />
<ProjectReference Include="..\SQLitePCLRaw.ugly\SQLitePCLRaw.ugly.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="AltCover" Version="5.3.675" />
<DotNetCliToolReference Include="dotnet-reportgenerator-cli" Version="4.1.10" />
<PackageReference Include="SourceGear.sqlite3" Version="$(lib_e_sqlite3_package_reference_version)" />
<PackageReference Include="microsoft.net.test.sdk" Version="$(depversion_microsoft_net_test_sdk)" />
<PackageReference Include="xunit" Version="$(depversion_xunit)" />
<PackageReference Include="xunit.runner.visualstudio" Version="$(depversion_xunit_runner_visualstudio)" />
Expand Down
Loading