Fix BCryptImportKeyPair: phKey passed by value, and the error path frees the wrong field - #4
Open
mckuipers wants to merge 5 commits into
Conversation
BCryptDuplicateHash and BCryptDuplicateKey fetched BCRYPT_PROVIDER_HANDLE via BCryptNamedUIntegerPropertyValue, which only accepts 4-byte values. BCRYPT_PROVIDER_HANDLE is a native Handle, 8 bytes on x64, so the size check silently returned 0 and the next BCryptGetProperty call dereferenced a null handle. Both call sites now fetch the handle via raw BCryptNamedPropertyValue and copy it in only when the returned size matches SizeOfType(Handle). BCryptGetProperty's blanket 8-to-4 truncation is removed since it was corrupting the newly fetched 8-byte handle. The same tolerance is re-added scoped to BCryptNamedUIntegerPropertyValue, the one caller that legitimately wants 4 bytes. NCryptNamedUIntegerPropertyValue gets the same tolerance for symmetry, though it has no current callers.
…ge-manager Fix BCRYPT_PROVIDER_HANDLE access violation in CNG.pkg
… field BCryptImportKeyPair's fourth parameter is BCRYPT_KEY_HANDLE *phKey, an out-parameter, and bcrypt.h.pkg declares it correctly as `Pointer phKey`. The call site passed `KeyObject.hHandle` by value instead, so the provider wrote the new key handle to address 0 rather than into the struct. The function therefore returned hHandle = 0 on success, and on x64 the write itself is an access violation. The surrounding code already shows the intended idiom: BCryptImportKey immediately above passes AddressOf(KeyObject.hHandle), as do BCryptGenerateKeyPair and BCryptOpenAlgorithmProvider. The error path had the matching mistake in reverse: it freed KeyObject.hHandle, which is definitionally 0 at that point, and left the Alloc'd KeyObject.pPointer behind. It now frees pPointer, as BCryptImportKey does. Found while importing an ECDSA P-256 public key from a WebAuthn attestation to verify an assertion signature. Nothing in the library or the demo calls BCryptImportKeyPair, which is why it went unnoticed.
BCryptImportKeyPair has no pbKeyObject/cbKeyObject parameter - bcrypt.h.pkg declares seven parameters, and the two the sibling BCryptImportKey uses for a caller-supplied key object are not among them. The provider allocates the key object internally and BCryptDestroyKey releases it. The Alloc was therefore never passed to the API and never reachable: on success the block was returned in pPointer for the caller to free, and the key object the provider actually used was somewhere else entirely. cCngKey.Destroy_Object already guards its Free with `If (pbKeyObject <> 0)`, so leaving pPointer at 0 needs no change on the consuming side.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
BCryptImportKeyPaircannot succeed as written. Nothing in the library or the demo calls it,which is why it has gone unnoticed — we hit it calling it directly to verify a WebAuthn
assertion signature.
The bug
bcrypt.h.pkgdeclares the fourth parameter correctly:The call site passes a
Handleby value into it:So the provider writes the new key handle to address 0. The function returns
hHandle = 0on success, and on x64 the write itself is an access violation.
The intended idiom is established either side of it in the same file —
BCryptImportKey(line 1180),
BCryptGenerateKeyPair(1049) andBCryptOpenAlgorithmProvider(1248) all passAddressOf(...).The second defect
The error path frees
KeyObject.hHandle, which is definitionally0at that point, andleaves the
Alloc'dKeyObject.pPointerbehind:BCryptImportKeyfreespPointer, which is the correct field.The third, in the second commit
BCryptImportKeyPairhas nopbKeyObject/cbKeyObjectparameter — the two the siblingBCryptImportKeyuses for a caller-supplied key object are absent from the seven this APIdeclares. The provider allocates the key object internally and
BCryptDestroyKeyreleases it.The
Alloc(cbKeyObject)was therefore never passed to the API and never reachable: on successthe block came back in
pPointerfor the caller to free, while the key object the provideractually used was somewhere else entirely.
cCngKey.Destroy_Objectalready guards its release withIf (pbKeyObject <> 0), so leavingpPointerat0needs no change on the consuming side.The two commits are separable — take the first alone if you would rather keep the allocation.
Base branch
Raised against
26-0-package-managerso it sits on top of #3'sBCRYPT_PROVIDER_HANDLEfix,which is in the same file and the same family (an out-parameter/handle-width mistake that only
bites on x64). Both commits also cherry-pick onto
production/stableas a pair, if you wouldrather land it on the release line first — verified.
production/stableis currently tag1.0.0, which is what the package manager installs, soas things stand neither this nor #3 reaches anyone using the published packages.
How far this is verified — and how far it is not
Being precise, because the distinction matters:
Verified against real Windows CNG. We are running the corrected call pattern in a passkey
implementation on x64. Because the wrapper is unusable as shipped, we declared our own
External_Functionagainst the sameBCryptImportKeyPairexport inBcrypt.dllwithPointer phKey, and call it withAddressOf(...). With that one change and nothing else, anECDSA P-256 public key imported from a WebAuthn attestation as
BCRYPT_ECCPUBLIC_BLOByields anon-zero key handle, and
BCryptVerifySignaturereturnsSTATUS_SUCCESSfor a good assertionsignature and
STATUS_INVALID_SIGNATUREfor a tampered one. That is the evidence thatAddressOfis the fix.Not verified. I have not compiled this patched file into a program that exercises
cCryptoApiNextGen.BCryptImportKeyPairthrough the class, because nothing in the libraryreaches it — there is no caller to route through and no test for it. The change is a
by-inspection correction against your own declaration and your own
BCryptImportKey, backed bythe independent binding above.
If it would help, the natural home for a regression test is an
ImportKeyPaironcCngAlgorithmProvidermirroring the existingImportKey, which would also give the functionits first caller. Happy to add that here if you want it in the same PR.