-
Notifications
You must be signed in to change notification settings - Fork 5.3k
Hold temporary keychain refs for certificates imported with X509Certificate2Collection.Import #82205
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
…ficate2Collection.Import
|
Tagging subscribers to this area: @dotnet/area-system-security, @vcsjones Issue DetailsFixes #80176
|
| // If we used temporary keychain we need to prevent deletion. | ||
| // on 10.15+ if keychain is unlinked, certain certificate operations may fail. | ||
| bool success = false; | ||
| _keychain.DangerousAddRef(ref success); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe this makes the "temporary" keychain more or less "permanent", because all calls to Dispose, collectively, only call DangerousRelease once.
You'd need to tell the newPal that it should DangerousRelease instead of Dispose in order to get it to ever tick down to 0 and actually release; and then have the collection import call Dispose() on the temp handle here (after the loop ends).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe this makes the "temporary" keychain more or less "permanent", because all calls to Dispose, collectively, only call DangerousRelease once.
You'd need to tell the newPal that it should DangerousRelease instead of Dispose in order to get it to ever tick down to 0 and actually release;
I was about to agree with you but then I went to compare it with my observations:
- The
DangerousAddRef/Disposepair is a pre-existing construct introduced in hold ref to temp keychain on OSX to avoid premature cleanup #41787. - I definitely do see
AppleCryptoNative_SecKeychainDeletegetting called and that's called only from the final release of the temporary keychain. This can be verified by taking the repro from the issue and addingcertificateToImport.Dispose()at the end of theforloop block. - If
DangerousReleaseis used instead ofDisposeinAppleCertificatePal.DisposeTempKeychainthen the same test code above actually fails with the following exception:
System.ObjectDisposedException: Safe handle has been closed.
Object name: 'SafeHandle'.
at System.Runtime.InteropServices.SafeHandle.InternalRelease(Boolean disposeOrFinalizeOperation)
at System.Runtime.InteropServices.SafeHandle.DangerousRelease()
at Internal.Cryptography.Pal.AppleCertificatePal.DisposeTempKeychain()
at Internal.Cryptography.Pal.AppleCertificatePal.Dispose()
at System.Security.Cryptography.X509Certificates.X509Certificate.Reset()
at System.Security.Cryptography.X509Certificates.X509Certificate2.Reset()
at System.Security.Cryptography.X509Certificates.X509Certificate.Dispose(Boolean disposing)
at System.Security.Cryptography.X509Certificates.X509Certificate.Dispose()
and then have the collection import call Dispose() on the temp handle here (after the loop ends).
That's already happening in ApplePkcs12CertLoader.Dispose.
(Not saying this sounds correct; I just don't fully grasp what is going on)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like the mismatched references could be caused by a missing SafeTemporaryKeychainHandle.TrackItem somewhere which causes additional release from SafeTemporaryKeychainHandle.UntrackItem (called from SafeKeychainItemHandle.ReleaseHandle which is itself called from AppleCertificatePal.Dispose).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I untangled the mess. X509MoveToKeychain takes a certificate as an input parameter but it changes its keychain. While SafeTemporaryKeychainHandle.TrackItem was previously called on the input certificate it was no-op since the item had no associated keychain. However, by the time the handle is disposed, it has the associated keychain, and thus SafeTemporaryKeychainHandle.UntrackItem would release one too many references.
It is plausible that fixing this would make the AppleCertificatePal._tempKeychain logic entirely unnecessary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I updated the PR description to explain what is going on.
…ng in X509MoveToKeychain and X509CopyWithPrivateKey
| if (newPal != pal) | ||
| { | ||
| pal.Dispose(); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note: With this object disposed in deterministic manner the existing tests should reliably trip on miscounted references without a GC being triggered.
...aries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.X509.macOS.cs
Show resolved
Hide resolved
|
The only failure is known, #81249. |
Fixes #80176
SafeSecCertificateHandleexplictly tracks the associated temporary keychain through calls toSafeTemporaryKeychainHandle.TrackItemandSafeTemporaryKeychainHandle.UntrackItem.UntrackItemis called fromReleaseHandlewhen the certificate handle is being disposed/finalized.TrackItemis called on all the places where the certificate/identity handle is created.X509MoveToKeychaintakes aSafeSecCertificateHandleas input parameter. The native API could change the keychain associated with this certificate. This was not accounted for and it resulted in mismatched tracking of reference count on the temporary keychain. For example, the handle could initially be without a keychain, so the firstSafeTemporaryKeychainHandle.TrackItemcall on it was a no-op. When the certificate is moved into temporary keychain and the handle is later disposed,SafeTemporaryKeychainHandle.UntrackItemgets called and decrements a reference count that was not previously incremented.Additionally, several temporary
AppleCertificatePalobjects were not disposed during PKCS#12 import. When they eventually get garbage collected it causes the associatedSafeSecCertificateHandleobjects to be released, and this results in the unbalanced reference counts releasing the native keychain object for the temporary keychain.This affects APIs like
new X509Certificate2(...)andX509Certificate2Collection.Importwhen importing PKCS#12 certificates.PR #41787 previously tried to fix the issue, but it did so only partially and largely by accident. It introduced an additional explicit keychain reference in the
new X509Certificate2(...)code path throughDangerousAddRef. This negated the effect of the incorrect tracking inX509MoveToKeychain. The additional keychain reference was then released throughDisposeinstead ofDangerousReleaseRefwhich silently failed since the reference count was already zero at that point. Effectively it meant thenew X509Certificate2(...)code path succeeded but only as a side-effect of the extraDangerousAddRef. The same workaround was not applied for theX509Certificate2Collection.Importthough.