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).
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:
Reproduction
Minimal console app — crashes reliably within 3-10 iterations:
Crash stack trace
Or sometimes:
Version info
Tested on my end with the following versions:
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).