Skip to content

System.Data.OleDb: AccessViolationException in ComObject.Finalize() when OleDbCommand fails on error path #125221

Description

@otykier

Description

When using System.Data.OleDb (versions 10.0+) on .NET 8, repeatedly executing invalid SQL via
OleDbCommand.ExecuteReader() causes a process-terminating AccessViolationException (or NullReferenceException) in
ComObject.Finalize() on the GC finalizer thread.

The crash does not occur with valid SQL — only when the OLE DB provider returns an error (e.g., invalid table name,
syntax error). It is intermittent, typically crashing after 3-10 failed executions when the GC runs.

Root cause hypothesis: When OleDbCommand.ExecuteReader() fails, System.Data.OleDb creates error-handling COM objects
(IErrorInfo, IErrorRecords) via the new source-generated COM interop (ComObject wrappers). After extracting the error
information, OleDb releases the COM pointers via Marshal.Release() but does not call GC.SuppressFinalize() on the
ComObject wrappers. When the GC finalizer later processes these wrappers, it calls Release() on already-freed
pointers, causing:

  • AccessViolationException (pointer points to freed memory)
  • NullReferenceException (pointer was zeroed)

Reproduction

Minimal console app — crashes reliably within 3-10 iterations:

using System.Data;
using System.Data.OleDb;

// Requires MSOLEDBSQL provider and a valid SQL Server instance
var connectionString = "Provider=MSOLEDBSQL.1;Data Source=localhost;Initial Catalog=master;Integrated Security=SSPI;";

for (int i = 1; i <= 100; i++)
{
    try
    {
        using var conn = new OleDbConnection(connectionString);
        conn.Open();
        using var cmd = conn.CreateCommand();
        cmd.CommandText = "SELECT * FROM ThisTableDoesNotExist";
        using var reader = cmd.ExecuteReader(CommandBehavior.SchemaOnly);
    }
    catch (OleDbException) { /* expected */ }

    if (i % 3 == 0)
    {
        GC.Collect();
        GC.WaitForPendingFinalizers();
        GC.Collect();
    }
}

Crash stack trace

System.AccessViolationException: Attempted to read or write protected memory.
   at System.Runtime.InteropServices.Marshal.Release(IntPtr)
   at System.Runtime.InteropServices.Marshalling.ComObject.Finalize()

Or sometimes:

System.NullReferenceException: Object reference not set to an instance of an object.
   at System.Runtime.InteropServices.Marshalling.FreeThreadedStrategy
       .System.Runtime.InteropServices.Marshalling.IIUnknownStrategy.Release(Void*)
   at System.Runtime.InteropServices.Marshalling.ComObject.Finalize()

Version info

  • .NET: 8.0.24 (CoreCLR 8.0.2426.7010)
  • OS: Windows 11 Pro (10.0.26100)
  • OLE DB Provider: MSOLEDBSQL.1 (Microsoft OLE DB Driver for SQL Server)

Tested on my end with the following versions:

Version Result
8.0.1 No crash
9.0.2 No crash
9.0.10 No crash
10.0.0 Crashes
10.0.3 Crashes

Analysis

The regression appears to be caused by the migration from [ComImport]-based RCW interop to the new source-generated
[GeneratedComInterface] / ComObject interop in System.Data.OleDb. The old RCW system properly tracks COM pointer
lifecycle and suppresses finalization on release. The new ComObject system does not, leading to double-release in the
finalizer.

Related: #96901 (fixed UniqueComInterfaceMarshaller double-release, but the fix doesn't cover System.Data.OleDb's
internal COM usage).

Also related: #100501 (OleDbException.Errors returns empty collection — possibly the same broken error-handling COM
interop path).

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    Status
    No status

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions