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
2 changes: 2 additions & 0 deletions NuGet.config
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
<clear />
<!--Begin: Package sources managed by Dependency Flow automation. Do not edit the sources below.-->
<!-- Begin: Package sources from dotnet-runtime -->
<add key="darc-int-dotnet-runtime-544c7e6" value="https://pkgs.dev.azure.com/dnceng/internal/_packaging/darc-int-dotnet-runtime-544c7e6e/nuget/v3/index.json" />
<!-- End: Package sources from dotnet-runtime -->
<!--End: Package sources managed by Dependency Flow automation. Do not edit the sources above.-->
<add key="dotnet-eng" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json" />
Expand All @@ -20,6 +21,7 @@
<disabledPackageSources>
<!--Begin: Package sources managed by Dependency Flow automation. Do not edit the sources below.-->
<!-- Begin: Package sources from dotnet-runtime -->
<add key="darc-int-dotnet-runtime-544c7e6" value="true" />
<!-- End: Package sources from dotnet-runtime -->
<!--End: Package sources managed by Dependency Flow automation. Do not edit the sources above.-->
</disabledPackageSources>
Expand Down
20 changes: 10 additions & 10 deletions eng/Version.Details.xml
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<Dependencies>
<ProductDependencies>
<Dependency Name="Microsoft.Private.Winforms" Version="7.0.17-servicing.24116.2">
<Dependency Name="Microsoft.Private.Winforms" Version="7.0.18-servicing.24169.17">
<Uri>https://dev.azure.com/dnceng/internal/_git/dotnet-winforms</Uri>
<Sha>a21b74e7f5e96ccb8d95dcde605669feb30f6c5b</Sha>
<Sha>1a5db9acafb5677490e44ad00d91cfff0dc11ddf</Sha>
</Dependency>
<Dependency Name="Microsoft.Dotnet.WinForms.ProjectTemplates" Version="7.0.17-servicing.24116.2">
<Dependency Name="Microsoft.Dotnet.WinForms.ProjectTemplates" Version="7.0.18-servicing.24169.17">
<Uri>https://dev.azure.com/dnceng/internal/_git/dotnet-winforms</Uri>
<Sha>a21b74e7f5e96ccb8d95dcde605669feb30f6c5b</Sha>
<Sha>1a5db9acafb5677490e44ad00d91cfff0dc11ddf</Sha>
</Dependency>
<Dependency Name="System.CodeDom" Version="7.0.0" CoherentParentDependency="Microsoft.Private.Winforms">
<Uri>https://dev.azure.com/dnceng/internal/_git/dotnet-runtime</Uri>
Expand Down Expand Up @@ -61,17 +61,17 @@
<Uri>https://dev.azure.com/dnceng/internal/_git/dotnet-runtime</Uri>
<Sha>d099f075e45d2aa6007a22b71b45a08758559f80</Sha>
</Dependency>
<Dependency Name="Microsoft.NETCore.App.Ref" Version="7.0.17" CoherentParentDependency="Microsoft.Private.Winforms">
<Dependency Name="Microsoft.NETCore.App.Ref" Version="7.0.18" CoherentParentDependency="Microsoft.Private.Winforms">
<Uri>https://dev.azure.com/dnceng/internal/_git/dotnet-runtime</Uri>
<Sha>dff486f2d78d3f932d0f9bfa38043f85e358fb8c</Sha>
<Sha>544c7e6eb3d5525c6f85341f51217d81d7c8ed80</Sha>
</Dependency>
<Dependency Name="Microsoft.NETCore.App.Runtime.win-x64" Version="7.0.17" CoherentParentDependency="Microsoft.Private.Winforms">
<Dependency Name="Microsoft.NETCore.App.Runtime.win-x64" Version="7.0.18" CoherentParentDependency="Microsoft.Private.Winforms">
<Uri>https://dev.azure.com/dnceng/internal/_git/dotnet-runtime</Uri>
<Sha>dff486f2d78d3f932d0f9bfa38043f85e358fb8c</Sha>
<Sha>544c7e6eb3d5525c6f85341f51217d81d7c8ed80</Sha>
</Dependency>
<Dependency Name="VS.Redist.Common.NetCore.SharedFramework.x64.7.0" Version="7.0.17-servicing.24115.8" CoherentParentDependency="Microsoft.Private.Winforms">
<Dependency Name="VS.Redist.Common.NetCore.SharedFramework.x64.7.0" Version="7.0.18-servicing.24169.14" CoherentParentDependency="Microsoft.Private.Winforms">
<Uri>https://dev.azure.com/dnceng/internal/_git/dotnet-runtime</Uri>
<Sha>dff486f2d78d3f932d0f9bfa38043f85e358fb8c</Sha>
<Sha>544c7e6eb3d5525c6f85341f51217d81d7c8ed80</Sha>
</Dependency>
</ProductDependencies>
<ToolsetDependencies>
Expand Down
8 changes: 4 additions & 4 deletions eng/Versions.props
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,13 @@
</PropertyGroup>
<!-- Packages that come from https://github.com/dotnet/winforms -->
<PropertyGroup>
<MicrosoftPrivateWinformsVersion>7.0.17-servicing.24116.2</MicrosoftPrivateWinformsVersion>
<MicrosoftPrivateWinformsVersion>7.0.18-servicing.24169.17</MicrosoftPrivateWinformsVersion>
</PropertyGroup>
<!-- Packages that come from https://github.com/dotnet/runtime -->
<PropertyGroup>
<VSRedistCommonNetCoreSharedFrameworkx6470PackageVersion>7.0.17-servicing.24115.8</VSRedistCommonNetCoreSharedFrameworkx6470PackageVersion>
<MicrosoftNETCoreAppRefVersion>7.0.17</MicrosoftNETCoreAppRefVersion>
<MicrosoftNETCoreAppRuntimewinx64Version>7.0.17</MicrosoftNETCoreAppRuntimewinx64Version>
<VSRedistCommonNetCoreSharedFrameworkx6470PackageVersion>7.0.18-servicing.24169.14</VSRedistCommonNetCoreSharedFrameworkx6470PackageVersion>
<MicrosoftNETCoreAppRefVersion>7.0.18</MicrosoftNETCoreAppRefVersion>
<MicrosoftNETCoreAppRuntimewinx64Version>7.0.18</MicrosoftNETCoreAppRuntimewinx64Version>
<MicrosoftNETCorePlatformsVersion>7.0.4</MicrosoftNETCorePlatformsVersion>
<SystemCodeDomPackageVersion>7.0.0</SystemCodeDomPackageVersion>
<SystemConfigurationConfigurationManagerPackageVersion>7.0.0</SystemConfigurationConfigurationManagerPackageVersion>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ namespace ComUtils
// The apartment is verified during this call.
HRESULT Unlock();

// Unlocking a wrapper permanently nulls out the server object pointer, so a
// wrapper contains a non-null server object pointer if and only if it is
// bound to a server object which has never been unlocked.
bool HasNotBeenUnlocked() { return (m_serverObject != nullptr); }

private:

IUnknown *m_serverObject;
Expand Down
88 changes: 88 additions & 0 deletions src/Microsoft.DotNet.Wpf/src/PenImc/dll/PimcManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,94 @@ void CPimcManager::TerminateHookThread(__inout CHookThreadItem * pThread)
void CPimcManager::FinalRelease()
{
m_wispManagerLock.RevokeIfValid();

//
// The CComObject<CPimcManager> destructor is the only function which calls into this
// FinalRelease code.
//
// In all successful usage of CPimcManager: 1) Managed WPF code uses CoCreateInstance
// to acquire an IPimcManager2 interface to a brand-new CPimcManager instance (created by
// the ATL CComCreator<T>::CreateInstance machinery), meaning FinalConstruct by-definition
// completes successfully, meaning "m_managerLock" is therefore guaranteed to be locked;
// 2) Managed WPF code then runs through its full end-to-end usage of the CPimcManager
// object (generally managed by the code in PenThreadWorker.cs); 3) When/if the managed WPF
// code determines that the CPimcManager object is no longer needed, it sends a
// RELEASE_MANAGER_EXT message (see UnsafeNativeMethods.ReleaseManagerExternalLock()) which
// unlocks "m_managerLock"; 4) Now that it is unlocked, the CComObject<CPimcManager> object
// can be destroyed when/if its refcount drops to zero, and this FinalRelease function will
// run at that time.
//
// So in all successful usage cases, it is guaranteed that "m_managerLock" is already
// unlocked when this code runs (because if it was still locked, the lock itself would have
// prevented the refcount from reaching zero, and would have prevented this function from
// ever running).
//
// That said, in unsuccessful usage cases, the ATL CComCreator<T>::CreateInstance machinery
// can fail, meaning it will destroy the brand-new CPimcManager instance before returning
// an error back to the CreateInstance caller. Destroying the brand-new instance triggers
// the CComObject<CPimcManager> destructor and therefore calls into this function during
// the CComCreator<T>::CreateInstance operation itself.
//
// The final step in CComCreator<T>::CreateInstance is a QI which queries the newly-created
// object for whatever interface has been requested by the caller. This operation is the
// main way that CComCreator<T>::CreateInstance can fail. For example, this QI is
// guaranteed to fail whenever the CoCreateInstance caller targets the CPimcManager CLSID
// but passes in a "random" IID that has nothing to do with IPimcManager2 or anything else
// that CPimcManager implements.
//
// (In CPimcManager construction, outside of pathological cases (e.g., where a small heap
// allocation in OS code fails due to out-of-memory), there are no other known ways that
// the CComCreator<T>::CreateInstance sequence can fail; so the QI failure is the only
// failure mode that is known to be of general interest.)
//
// The QI failure can only occur after the preceding FinalConstruct call has completed
// successfully (since any FinalConstruct failure would have caused
// CComCreator<T>::CreateInstance to abort without ever trying the QI); since
// CPimcManager::FinalConstruct always locks the "m_managerLock", this implies that the
// "m_managerLock" is guaranteed to be locked when this code runs (which is exactly
// opposite to what happens in all successful usage cases as discussed above).
//
// In this case, it is crucial to unlock "m_managerLock" before allowing this CPimcManager
// object to be destroyed. Without the unlock, this CPimcManager object would be destroyed
// while the associated CStdIdentity in the OS code still holds a reference to it; during
// any future apartment unload, the OS code would release this reference, and the release
// would be a use-after-free at that point.
//
// Note that the crucial unlock causes overactive ATL debug asserts to fire if a chk build
// of this DLL is used; specifically:
//
// - The code in the CComObject<CPimcManager> destructor always stomps the refcount to
// 0xc0000001 (i.e., "-(LONG_MAX/2)"), meaning this CPimcManager object's refcount is
// always 0xc0000001 when this code runs; unlocking "m_managerLock" will cause the refcount
// to drop by one (because, as discussed above, the crucial operation which prevents
// use-after-free problems will release the associated CStdIdentity's reference to this
// CPimcManager object, and in this way releases the reference that was added when
// "managerLock" was locked during FinalConstruct); as a result, unlocking "m_managerLock"
// will move this CPimcManager object's refcount through a "0xc0000001 -> 0xc0000000"
// transition.
//
// - Both of the CComObjectRootEx<T>::InternalRelease specializations contain debug asserts
// which will fire whenever the refcount drops below 0xc0000001, so this transition always
// triggers a debug assert when using a chk build of this DLL.
//
// - That said, all evidence strongly suggests that this is just an overactive assert in
// the ATL code (probably just indicating that it is rare for FinalConstruct to add
// "self-references" like it does for CPimcManager (since these self-references generally
// prevent the server object from being destroyed unless a manual action like the
// RELEASE_MANAGER_EXT message is taken later on), meaning it is rare to have a situation
// where FinalRelease needs to release self-references that were acquired in
// FinalConstruct, meaning this is a rare enough case that the ATL authors either didn't
// test it or didn't think it was common enough to warrant adjusting the assert).
//
// Since this change is being made in servicing, attempt to change behavior as little as
// possible in the "successful usage" cases where "m_managerLock" is already unlocked,
// while still ensuring that FinalRelease will always run the crucial unlock in all
// "unsuccessful usage" cases.
//
if (m_managerLock.HasNotBeenUnlocked())
{
m_managerLock.Unlock();
}
}

/////////////////////////////////////////////////////////////////////////////
Expand Down