-
Notifications
You must be signed in to change notification settings - Fork 4.9k
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
Undefined behavior should not be invoked when a ComObject marshaled by UniqueComInterfaceMarshaller<T> is used after calling FinalRelease #96901
Comments
Tagging subscribers to this area: @dotnet/interop-contrib Issue DetailsDescriptionIn an API review about ComObject.FinalRelease, there is a comment that the second call should be a no-op and Reproduction Steps
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<LangVersion>preview</LangVersion>
<ImplicitUsings>disable</ImplicitUsings>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
</Project>
using System;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.Marshalling;
using System.Runtime.Versioning;
[SupportedOSPlatform("windows")]
internal partial class C
{
static void Main()
{
Bad1_UseAfterRelease();
Bad2_DoubleReleaseExplicitly();
Bad3_DoubleReleaseImplicitlyWhenFinalize();
Ok_ButVeryUrgy();
}
static void Bad1_UseAfterRelease()
{
int hr = CoCreateInstance(
CLSID_GraphBuilder,
IntPtr.Zero,
CLSCTX_INPROC_SERVER,
typeof(IFilterGraph).GUID,
out object? obj);
Marshal.ThrowExceptionForHR(hr);
((ComObject)obj).FinalRelease();
((IFilterGraph)obj).EnumFilters(out _); // In my environment, the process will crash with code -1073740791, which means STATUS_STACK_BUFFER_OVERRUN
}
static void Bad2_DoubleReleaseExplicitly()
{
int hr = CoCreateInstance(
CLSID_GraphBuilder,
IntPtr.Zero,
CLSCTX_INPROC_SERVER,
typeof(IFilterGraph).GUID,
out object? obj);
Marshal.ThrowExceptionForHR(hr);
((ComObject)obj).FinalRelease();
((ComObject)obj).FinalRelease(); // In my environment, the process will crash with code -1073740940, which means "A heap has been corrupted"
}
static void Bad3_DoubleReleaseImplicitlyWhenFinalize()
{
int hr = CoCreateInstance(
CLSID_GraphBuilder,
IntPtr.Zero,
CLSCTX_INPROC_SERVER,
typeof(IFilterGraph).GUID,
out object? obj);
Marshal.ThrowExceptionForHR(hr);
((ComObject)obj).FinalRelease();
obj = null;
GC.Collect();
GC.WaitForPendingFinalizers(); // In my environment, the process will crash with code -1073740940, which means "A heap has been corrupted"
}
static void Ok_ButVeryUrgy()
{
int hr = CoCreateInstance(
CLSID_GraphBuilder,
IntPtr.Zero,
CLSCTX_INPROC_SERVER,
typeof(IFilterGraph).GUID,
out object? obj);
Marshal.ThrowExceptionForHR(hr);
((ComObject)obj).FinalRelease();
GC.SuppressFinalize(obj); // Very important suppression even if "CA1816: Call GC.SuppressFinalize correctly" occur
obj = null;
GC.Collect();
GC.WaitForPendingFinalizers(); // no-op
}
[LibraryImport("ole32.dll")]
internal static partial int CoCreateInstance(
in Guid rclsid,
IntPtr pUnkOuter, // Actual type is IUnknown*, but change it for simplify
uint dwClsContext,
in Guid riid,
[MarshalUsing(typeof(UniqueComInterfaceMarshaller<object>))] out object ppv);
internal const uint CLSCTX_INPROC_SERVER = 0x1;
internal static readonly Guid CLSID_GraphBuilder = new("e436ebb3-524f-11ce-9f53-0020af0ba770");
}
[GeneratedComInterface]
[Guid("56a8689f-0ad4-11ce-b03a-0020af0ba770")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
internal partial interface IFilterGraph
{
void AddFilter(
[MarshalAs(UnmanagedType.Interface)] object pFilter,
[MarshalAs(UnmanagedType.LPWStr)] string pName);
void RemoveFilter([MarshalAs(UnmanagedType.Interface)] object pFilter);
void EnumFilters([MarshalAs(UnmanagedType.Interface)] out object ppEnum);
// Remaining members are omitted
}
Expected behaviorUndefined behaviors are not invoked. The process never crash. (Methods may throw a documented exception) Actual behaviorUndefined behaviors are invoked. For example, in the above code, Regression?
Known WorkaroundsWe have two choises:
Configuration
Other informationIf I use using System;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.Marshalling;
using System.Runtime.Versioning;
[SupportedOSPlatform("windows")]
internal partial class C
{
static void Main()
{
Ok1();
Ok2();
Ok3();
}
static void Ok1()
{
int hr = CoCreateInstance(
CLSID_GraphBuilder,
IntPtr.Zero,
CLSCTX_INPROC_SERVER,
typeof(IFilterGraph).GUID,
out object? obj);
Marshal.ThrowExceptionForHR(hr);
Marshal.FinalReleaseComObject(obj);
((IFilterGraph)obj).EnumFilters(out _); // throw InvalidComObjectException instead of invoke an undefined behavior
}
static void Ok2()
{
int hr = CoCreateInstance(
CLSID_GraphBuilder,
IntPtr.Zero,
CLSCTX_INPROC_SERVER,
typeof(IFilterGraph).GUID,
out object? obj);
Marshal.ThrowExceptionForHR(hr);
Marshal.FinalReleaseComObject(obj);
Marshal.FinalReleaseComObject(obj); // no-op
}
static void Ok3()
{
int hr = CoCreateInstance(
CLSID_GraphBuilder,
IntPtr.Zero,
CLSCTX_INPROC_SERVER,
typeof(IFilterGraph).GUID,
out object? obj);
Marshal.ThrowExceptionForHR(hr);
Marshal.FinalReleaseComObject(obj);
obj = null;
GC.Collect();
GC.WaitForPendingFinalizers(); // no-op
}
[DllImport("ole32.dll")]
internal static extern int CoCreateInstance(
in Guid rclsid,
IntPtr pUnkOuter, // Actual type is IUnknown*, but change it for simplify
uint dwClsContext,
in Guid riid,
[MarshalAs(UnmanagedType.Interface)] out object ppv);
internal const uint CLSCTX_INPROC_SERVER = 0x1;
internal static readonly Guid CLSID_GraphBuilder = new("e436ebb3-524f-11ce-9f53-0020af0ba770");
}
[ComImport]
[Guid("56a8689f-0ad4-11ce-b03a-0020af0ba770")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
internal partial interface IFilterGraph
{
void AddFilter(
[MarshalAs(UnmanagedType.Interface)] object pFilter,
[MarshalAs(UnmanagedType.LPWStr)] string pName);
void RemoveFilter([MarshalAs(UnmanagedType.Interface)] object pFilter);
void EnumFilters([MarshalAs(UnmanagedType.Interface)] out object ppEnum);
// Remaining members are omitted
}
|
Description
In an API review about ComObject.FinalRelease, there is a comment that the second call should be a no-op and
InvalidOperationException
should be thrown the COM object is used after callingFinalRelease
. In practice, however, undefined behaviors are invoked at the second call ofFinalRelease
for aComObject
marshaled byUniqueComInterfaceMarshaller<T>
, at the use of theComObject
after theFinalRelease
call, and at the invocation of GC for theComObject
after FinalRelease. For example, in the above situation,AccessViolationException
may be thrown or the process may terminate abnormally.Reproduction Steps
.csproj
file:Expected behavior
Undefined behaviors are not invoked. The process never crash. (Methods may throw a documented exception)
Actual behavior
Undefined behaviors are invoked. For example, in the above code,
AccessViolationException
may be thrown or the process may terminate abnormally.Regression?
ComObject
type was introduced at .NET 8 so this is NOT a regression.Known Workarounds
We have two choises:
FinalRelease
, we also callGC.SupressFinalize
for aComObject
marshaled byUniqueComInterfaceMarshaller
FinalRelease
at all, and makeComObject
release at GC time.Configuration
Other information
If I use
DllImportAttribute
andComImportAttribute
attributes then undefined behaviors are not invoked:The text was updated successfully, but these errors were encountered: