Skip to content

[release/8.0-staging] Fix Duplicate ownership state machine under concurrency for NCrypt handles - #135454

Merged
ViveliDuCh merged 2 commits into
release/8.0-stagingfrom
backport/pr-130804-to-release/8.0-staging
Oct 9, 2026
Merged

ViveliDuCh merged 2 commits into
release/8.0-stagingfrom
backport/pr-130804-to-release/8.0-staging

Conversation

@bartonjs

@bartonjs bartonjs commented Oct 8, 2026

Copy link
Copy Markdown
Member

Backport of #130804 to release/8.0-staging

/cc @bartonjs @vcsjones

Customer Impact

  • Customer reported
  • Found internally

The invalid state was detected by an assert in a CI run, in #130760, but customer reports of ObjectDisposedException being thrown from these classes during finalization appear to be the same problem.

In the worst case, the customer impact is the aforementioned exception during finalization, which tears down the process.

The invalid state is entered into by a race condition that can only be encountered when two threads simultaneously use a key object for the first time. Parallel access once the first use completes is/was not subject to the race.

Regression

  • Yes
  • No

The logic hasn't changed since .NET Core 1.0.

Testing

The change includes a test that does aggressive parallelization.

Risk

Low. It is a simple lock addition around the code susceptible to the race.

…ndles

Duplicating NCrypt safe handles is not currently thread safe. This is
observable through `CngKey.Handle` which calls `Duplicate` on a
`SafeNCryptKeyHandle`. The crux of the issue is that two threads can
both observe `_ownershipState == Owner` and enter
`DuplicateOwnerHandle()`. Each creates a different holder for the same
native handle. The first transitions the original handle to `Duplicate`,
the second then hits the assertion when it attempts the same transition.

This assert was observed in
#130760.

In a release build, this can result in an invalid `NCryptKeyHandle`
getting passed to native functions.

1. Both threads create separate holders for the same raw NCrypt handle.
2. The later thread overwrites the original handle's  _holder .
3. Managed reference counts no longer represent the native handle's
actual users.
4. The winning holder can call `NCryptFreeObject` while duplicates
associated with an overwritten holder remain open.

This simplest solution is to take a lock, which will unfortunately ding
performance a little bit when accessing `Handle`.

A unit test was added to attempt racing the handles. Before the fix,
this test would fail quickly with

```
Error Message:
Assert.All() Failure: 7 out of 8 items in the collection did not pass.
[0]: Item:  Microsoft.VisualStudio.TestPlatform.TestHost.DebugAssertException: Method Debug.Fail failed with '_ownershipState == OwnershipState.Owner
', and was translated to Microsoft.VisualStudio.TestPlatform.TestHost.DebugAssertException to avoid terminating the process hosting the test.
    at Microsoft.Win32.SafeHandles.SafeNCryptHandle.DuplicateOwnerHandle[T]() in E:\code\runtime\src\libraries\System.Security.Cryptography\src\Microsoft\Win32\SafeHandles\NCryptSafeHandles.cs:line 263
    at Microsoft.Win32.SafeHandles.SafeNCryptHandle.Duplicate[T]() in E:\code\runtime\src\libraries\System.Security.Cryptography\src\Microsoft\Win32\SafeHandles\NCryptSafeHandles.cs:line 219
    at Microsoft.Win32.SafeHandles.SafeNCryptKeyHandle.Duplicate() in E:\code\runtime\src\libraries\System.Security.Cryptography\src\Microsoft\Win32\SafeHandles\NCryptSafeHandles.cs:line 364
    at System.Security.Cryptography.CngKey.get_Handle() in E:\code\runtime\src\libraries\System.Security.Cryptography\src\System\Security\Cryptography\CngKey.StandardProperties.cs:line 93
    at System.Security.Cryptography.Tests.CngKeyTests.<>c__DisplayClass3_1.<Handle_ConcurrentFirstAccess>b__0() in E:\code\runtime\src\libraries\System.Security.Cryptography\tests\CngKeyTests.cs:line 86
    at System.Threading.Thread.StartCallback(Thread* pThread) in E:\code\runtime\src\coreclr\System.Private.CoreLib\src\System\Threading\Thread.CoreCLR.cs:line 117
```

After the lock the test consistently passes.

Doing this lock-free is non-trivial. There are multiple fields we need
to ensure get published atomically, namely `_ownershipState`, `_holder`,
and `_parentHandle`. However we avoid the lock entirely unless the
current state is `OwnershipState.Owner`.
@bartonjs bartonjs added this to the 8.0.x milestone Oct 8, 2026
@bartonjs bartonjs self-assigned this Oct 8, 2026
@bartonjs bartonjs added Servicing-consider Issue for next servicing release review area-System.Security labels Oct 8, 2026
@bartonjs
bartonjs requested a review from vcsjones October 8, 2026 22:49
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 3 pipeline(s).
13 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @bartonjs, @vcsjones, @dotnet/area-system-security
See info in area-owners.md if you want to be subscribed.

@bartonjs bartonjs added Servicing-approved Approved for servicing release and removed Servicing-consider Issue for next servicing release review labels Oct 9, 2026
@bartonjs

bartonjs commented Oct 9, 2026

Copy link
Copy Markdown
Member Author

Servicing-approved via email

@ViveliDuCh

Copy link
Copy Markdown
Member

Hi,

Friendly reminder that: The code complete date for 8.0.33 (the November 2026 release) is Monday 12 October. Make sure to merge this PR on that date at the latest, or it won't make it into that release.

@ViveliDuCh
ViveliDuCh merged commit 8bcdcaa into release/8.0-staging Oct 9, 2026
110 of 112 checks passed
@ViveliDuCh
ViveliDuCh deleted the backport/pr-130804-to-release/8.0-staging branch October 9, 2026 21:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-System.Security Servicing-approved Approved for servicing release

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants