diff --git a/ntoskrnl/config/cmparse.c b/ntoskrnl/config/cmparse.c index 38346c2501d..dec2fd72cda 100644 --- a/ntoskrnl/config/cmparse.c +++ b/ntoskrnl/config/cmparse.c @@ -336,7 +336,7 @@ CmpDoCreateChild(IN PHHIVE Hive, *KeyCell, KeyNode, ParentKcb, - 0, // CMP_LOCK_HASHES_FOR_KCB, + CMP_LOCK_HASHES_FOR_KCB, Name); if (!Kcb) { @@ -355,7 +355,7 @@ CmpDoCreateChild(IN PHHIVE Hive, KeyBody->KeyControlBlock = Kcb; /* Link it with the KCB */ - EnlistKeyBodyWithKCB(KeyBody, 0); + EnlistKeyBodyWithKCB(KeyBody, CMP_ENLIST_KCB_LOCKED_EXCLUSIVE); /* Assign security */ Status = SeAssignSecurity(ParentDescriptor, @@ -419,6 +419,17 @@ CmpDoCreate(IN PHHIVE Hive, LARGE_INTEGER TimeStamp; PCM_KEY_NODE KeyNode; + /* Make sure the KCB is locked and lock the flusher */ + CMP_ASSERT_KCB_LOCK(ParentKcb); + CmpLockHiveFlusherShared((PCMHIVE)Hive); + + /* Bail out on read-only KCBs */ + if (ParentKcb->ExtFlags & CM_KCB_READ_ONLY_KEY) + { + Status = STATUS_ACCESS_DENIED; + goto Exit; + } + /* Check if the parent is being deleted */ if (ParentKcb->Delete) { @@ -493,8 +504,17 @@ CmpDoCreate(IN PHHIVE Hive, /* Now add the subkey */ if (!CmpAddSubKey(Hive, Cell, KeyCell)) { - /* Failure! We don't handle this yet! */ - ASSERT(FALSE); + /* Free the created child */ + CmpFreeKeyByCell(Hive, KeyCell, FALSE); + + /* Purge out this KCB */ + KeyBody->KeyControlBlock->Delete = TRUE; + CmpRemoveKeyControlBlock(KeyBody->KeyControlBlock); + + /* And cleanup the key body object */ + ObDereferenceObjectDeferDelete(*Object); + Status = STATUS_INSUFFICIENT_RESOURCES; + goto Exit; } /* Get the key node */ @@ -502,9 +522,21 @@ CmpDoCreate(IN PHHIVE Hive, if (!KeyNode) { /* Fail, this shouldn't happen */ - ASSERT(FALSE); + CmpFreeKeyByCell(Hive, KeyCell, TRUE); // Subkey linked above + + /* Purge out this KCB */ + KeyBody->KeyControlBlock->Delete = TRUE; + CmpRemoveKeyControlBlock(KeyBody->KeyControlBlock); + + /* And cleanup the key body object */ + ObDereferenceObjectDeferDelete(*Object); + Status = STATUS_INSUFFICIENT_RESOURCES; + goto Exit; } + /* Clean up information on this subkey */ + CmpCleanUpSubKeyInfo(KeyBody->KeyControlBlock->ParentKcb); + /* Sanity checks */ ASSERT(KeyBody->KeyControlBlock->ParentKcb->KeyCell == Cell); ASSERT(KeyBody->KeyControlBlock->ParentKcb->KeyHive == Hive); @@ -539,7 +571,16 @@ CmpDoCreate(IN PHHIVE Hive, if (!CellData) { /* This shouldn't happen */ - ASSERT(FALSE); + CmpFreeKeyByCell(Hive, KeyCell, TRUE); // Subkey linked above + + /* Purge out this KCB */ + KeyBody->KeyControlBlock->Delete = TRUE; + CmpRemoveKeyControlBlock(KeyBody->KeyControlBlock); + + /* And cleanup the key body object */ + ObDereferenceObjectDeferDelete(*Object); + Status = STATUS_INSUFFICIENT_RESOURCES; + goto Exit; } /* Update the flags */ @@ -551,6 +592,7 @@ CmpDoCreate(IN PHHIVE Hive, Exit: /* Release the flusher lock and return status */ + CmpUnlockHiveFlusher((PCMHIVE)Hive); return Status; } @@ -565,10 +607,13 @@ CmpDoOpen(IN PHHIVE Hive, IN PCM_PARSE_CONTEXT Context OPTIONAL, IN ULONG ControlFlags, IN OUT PCM_KEY_CONTROL_BLOCK *CachedKcb, + IN PULONG KcbsLocked, IN PUNICODE_STRING KeyName, OUT PVOID *Object) { NTSTATUS Status; + BOOLEAN LockKcb = FALSE; + BOOLEAN IsLockShared = FALSE; PCM_KEY_BODY KeyBody = NULL; PCM_KEY_CONTROL_BLOCK Kcb = NULL; @@ -600,37 +645,87 @@ CmpDoOpen(IN PHHIVE Hive, Context->Disposition = REG_OPENED_EXISTING_KEY; } - /* Do this in the registry lock */ - CmpLockRegistry(); - - /* If we have a KCB, make sure it's locked */ - //ASSERT(CmpIsKcbLockedExclusive(*CachedKcb)); + /* Lock the KCB on creation if asked */ + if (ControlFlags & CMP_CREATE_KCB_KCB_LOCKED) + { + LockKcb = TRUE; + } /* Check if caller doesn't want to create a KCB */ if (ControlFlags & CMP_OPEN_KCB_NO_CREATE) { + /* + * The caller doesn't want to create a KCB. This means the KCB + * is already in cache and other threads may take use of it + * so it has to be locked in share mode. + */ + IsLockShared = TRUE; + /* Check if this is a symlink */ - if ((Node->Flags & KEY_SYM_LINK) && !(Attributes & OBJ_OPENLINK)) + if (((*CachedKcb)->Flags & KEY_SYM_LINK) && !(Attributes & OBJ_OPENLINK)) { - /* This case for a cached KCB is not implemented yet */ - ASSERT(FALSE); + /* Is this symlink found? */ + if ((*CachedKcb)->ExtFlags & CM_KCB_SYM_LINK_FOUND) + { + /* Get the real KCB, is this deleted? */ + Kcb = (*CachedKcb)->ValueCache.RealKcb; + if (Kcb->Delete) + { + /* + * The real KCB is gone, do a reparse. We used to lock the KCB in + * shared mode as others may have taken use of it but since we + * must do a reparse of the key the only thing that matter is us. + * Lock the KCB exclusively so nobody is going to mess with the KCB. + */ + DPRINT1("The real KCB is deleted, attempt a reparse\n"); + CmpUnLockKcbArray(KcbsLocked); + CmpAcquireKcbLockExclusiveByIndex(GET_HASH_INDEX((*CachedKcb)->ConvKey)); + CmpCleanUpKcbValueCache(*CachedKcb); + KcbsLocked[0] = 1; + KcbsLocked[1] = GET_HASH_INDEX((*CachedKcb)->ConvKey); + return STATUS_REPARSE; + } + + /* + * The symlink has been found. As in the similar case above, + * the KCB of the symlink exclusively, we don't want anybody + * to mess it up. + */ + CmpUnLockKcbArray(KcbsLocked); + CmpAcquireKcbLockExclusiveByIndex(GET_HASH_INDEX((*CachedKcb)->ConvKey)); + KcbsLocked[0] = 1; + KcbsLocked[1] = GET_HASH_INDEX((*CachedKcb)->ConvKey); + } + else + { + /* We must do a reparse */ + DPRINT("The symlink is not found, attempt a reparse\n"); + return STATUS_REPARSE; + } + } + else + { + /* This is not a symlink, just give the cached KCB already */ + Kcb = *CachedKcb; } /* The caller wants to open a cached KCB */ - if (!CmpReferenceKeyControlBlock(*CachedKcb)) + if (!CmpReferenceKeyControlBlock(Kcb)) { - /* Release the registry lock */ - CmpUnlockRegistry(); - /* Return failure code */ return STATUS_INSUFFICIENT_RESOURCES; } - - /* Our kcb is that one */ - Kcb = *CachedKcb; } else { + /* + * The caller wants to create a new KCB. Unlike the code path above, here + * we must check if the lock is exclusively held because in the scenario + * where the caller doesn't want to create a KCB is because it is already + * in the cache and it must have a shared lock instead. + */ + ASSERT(CmpIsKcbLockedExclusive(*CachedKcb)); + /* Check if this is a symlink */ if ((Node->Flags & KEY_SYM_LINK) && !(Attributes & OBJ_OPENLINK)) { @@ -639,48 +734,40 @@ CmpDoOpen(IN PHHIVE Hive, Cell, Node, *CachedKcb, - 0, + LockKcb ? CMP_LOCK_HASHES_FOR_KCB : 0, KeyName); if (!Kcb) { - /* Release registry lock and return failure */ - CmpUnlockRegistry(); + /* Return failure */ return STATUS_INSUFFICIENT_RESOURCES; } /* Make sure it's also locked, and set the pointer */ - //ASSERT(CmpIsKcbLockedExclusive(Kcb)); + ASSERT(CmpIsKcbLockedExclusive(Kcb)); *CachedKcb = Kcb; - /* Release the registry lock */ - CmpUnlockRegistry(); - /* Return reparse required */ return STATUS_REPARSE; } - /* Create the KCB. FIXME: Use lock flag */ + /* Create the KCB */ Kcb = CmpCreateKeyControlBlock(Hive, Cell, Node, *CachedKcb, - 0, + LockKcb ? CMP_LOCK_HASHES_FOR_KCB : 0, KeyName); if (!Kcb) { - /* Release registry lock and return failure */ - CmpUnlockRegistry(); + /* Return failure */ return STATUS_INSUFFICIENT_RESOURCES; } + + /* Make sure it's also locked, and set the pointer */ + ASSERT(CmpIsKcbLockedExclusive(Kcb)); + *CachedKcb = Kcb; } - /* Make sure it's also locked, and set the pointer */ - //ASSERT(CmpIsKcbLockedExclusive(Kcb)); - *CachedKcb = Kcb; - - /* Release the registry lock */ - CmpUnlockRegistry(); - /* Allocate the key object */ Status = ObCreateObject(AccessMode, CmpKeyObjectType, @@ -701,7 +788,7 @@ CmpDoOpen(IN PHHIVE Hive, KeyBody->NotifyBlock = NULL; /* Link to the KCB */ - EnlistKeyBodyWithKCB(KeyBody, 0); + EnlistKeyBodyWithKCB(KeyBody, IsLockShared ? CMP_ENLIST_KCB_LOCKED_SHARED : CMP_ENLIST_KCB_LOCKED_EXCLUSIVE); /* * We are already holding a lock against the KCB that is assigned @@ -748,6 +835,7 @@ CmpCreateLinkNode(IN PHHIVE Hive, IN ULONG CreateOptions, IN PCM_PARSE_CONTEXT Context, IN PCM_KEY_CONTROL_BLOCK ParentKcb, + IN PULONG KcbsLocked, OUT PVOID *Object) { NTSTATUS Status; @@ -765,6 +853,18 @@ CmpCreateLinkNode(IN PHHIVE Hive, return STATUS_ACCESS_DENIED; } + /* Make sure the KCB is locked and lock the flusher */ + CMP_ASSERT_KCB_LOCK(ParentKcb); + CmpLockHiveFlusherShared((PCMHIVE)Hive); + CmpLockHiveFlusherShared((PCMHIVE)Context->ChildHive.KeyHive); + + /* Bail out on read-only KCBs */ + if (ParentKcb->ExtFlags & CM_KCB_READ_ONLY_KEY) + { + Status = STATUS_ACCESS_DENIED; + goto Exit; + } + /* Check if the parent is being deleted */ if (ParentKcb->Delete) { @@ -827,8 +927,9 @@ CmpCreateLinkNode(IN PHHIVE Hive, AccessMode, CreateOptions, NULL, - 0, + CMP_CREATE_KCB_KCB_LOCKED, &Kcb, + KcbsLocked, &Name, Object); HvReleaseCell(Context->ChildHive.KeyHive, KeyCell); @@ -930,6 +1031,9 @@ CmpCreateLinkNode(IN PHHIVE Hive, /* Get the key body */ KeyBody = (PCM_KEY_BODY)*Object; + /* Clean up information on this subkey */ + CmpCleanUpSubKeyInfo(KeyBody->KeyControlBlock->ParentKcb); + /* Sanity checks */ ASSERT(KeyBody->KeyControlBlock->ParentKcb->KeyCell == Cell); ASSERT(KeyBody->KeyControlBlock->ParentKcb->KeyHive == Hive); @@ -966,6 +1070,8 @@ CmpCreateLinkNode(IN PHHIVE Hive, Exit: /* Release the flusher locks and return status */ + CmpUnlockHiveFlusher((PCMHIVE)Context->ChildHive.KeyHive); + CmpUnlockHiveFlusher((PCMHIVE)Hive); return Status; } @@ -1005,40 +1111,550 @@ CmpHandleExitNode(IN OUT PHHIVE *Hive, } } +static +ULONG +CmpComputeHashValue( + _In_ PUNICODE_STRING RemainingName, + _In_ ULONG ConvKey, + _Inout_ PCM_HASH_CACHE_STACK HashCacheStack, + _Out_ PULONG TotalSubKeys) +{ + ULONG CopyConvKey; + ULONG SubkeysInTotal; + ULONG RemainingSubkeysInTotal; + PWCHAR RemainingNameBuffer; + USHORT RemainingNameLength; + USHORT KeyNameLength; + + /* Don't compute the hashes on a NULL remaining name */ + RemainingNameBuffer = RemainingName->Buffer; + RemainingNameLength = RemainingName->Length; + if (RemainingNameLength == 0) + { + *TotalSubKeys = 0; + return 0; + } + + /* Skip any leading separator */ + while (RemainingNameLength >= sizeof(WCHAR) && + *RemainingNameBuffer == OBJ_NAME_PATH_SEPARATOR) + { + RemainingNameBuffer++; + RemainingNameLength -= sizeof(WCHAR); + } + + /* Now set up the hash stack entries and compute the hashes */ + SubkeysInTotal = 0; + RemainingSubkeysInTotal = 0; + KeyNameLength = 0; + CopyConvKey = ConvKey; + HashCacheStack[RemainingSubkeysInTotal].NameOfKey.Buffer = RemainingNameBuffer; + while (RemainingNameLength > 0) + { + /* Is this character a separator? */ + if (*RemainingNameBuffer != OBJ_NAME_PATH_SEPARATOR) + { + /* It's not, add it to the hash */ + CopyConvKey = COMPUTE_HASH_CHAR(CopyConvKey, *RemainingNameBuffer); + + /* Go to the next character (add up the length of the character as well) */ + RemainingNameBuffer++; + KeyNameLength += sizeof(WCHAR); + RemainingNameLength -= sizeof(WCHAR); + + /* + * We are at the end of the key name path. Take into account + * the last character and if we still have space in the hash + * stack, add it up in the remaining list. + */ + if (RemainingNameLength == 0) + { + if (RemainingSubkeysInTotal < CMP_SUBKEY_LEVELS_DEPTH_LIMIT) + { + HashCacheStack[RemainingSubkeysInTotal].NameOfKey.Length = KeyNameLength; + HashCacheStack[RemainingSubkeysInTotal].NameOfKey.MaximumLength = KeyNameLength; + HashCacheStack[RemainingSubkeysInTotal].ConvKey = CopyConvKey; + RemainingSubkeysInTotal++; + } + + SubkeysInTotal++; + } + } + else + { + /* Skip any leading separator */ + while (RemainingNameLength >= sizeof(WCHAR) && + *RemainingNameBuffer == OBJ_NAME_PATH_SEPARATOR) + { + RemainingNameBuffer++; + RemainingNameLength -= sizeof(WCHAR); + } + + /* + * It would be possible that a malformed key pathname may be passed + * to the registry parser such as a path with only separators like + * "\\\\" for example. This would trick the function into believing + * the key path has subkeys albeit that is not the case. + */ + ASSERT(RemainingNameLength != 0); + + /* Take into account this subkey */ + SubkeysInTotal++; + + /* And add it up to the hash stack */ + if (RemainingSubkeysInTotal < CMP_SUBKEY_LEVELS_DEPTH_LIMIT) + { + HashCacheStack[RemainingSubkeysInTotal].NameOfKey.Length = KeyNameLength; + HashCacheStack[RemainingSubkeysInTotal].NameOfKey.MaximumLength = KeyNameLength; + HashCacheStack[RemainingSubkeysInTotal].ConvKey = CopyConvKey; + + RemainingSubkeysInTotal++; + KeyNameLength = 0; + + /* + * Precaution check -- we have added up a remaining + * subkey above but we must ensure we still have space + * to hold up the new subkey for which we will compute + * the hashes, so that we don't blow up the hash stack. + */ + if (RemainingSubkeysInTotal < CMP_SUBKEY_LEVELS_DEPTH_LIMIT) + { + HashCacheStack[RemainingSubkeysInTotal].NameOfKey.Buffer = RemainingNameBuffer; + } + } + } + } + + *TotalSubKeys = SubkeysInTotal; + return RemainingSubkeysInTotal; +} + +static +BOOLEAN +CmpCompareSubkeys( + _In_ PCM_HASH_CACHE_STACK HashCacheStack, + _In_ PCM_KEY_CONTROL_BLOCK CurrentKcb, + _In_ ULONG RemainingSubkeys, + _Out_ PCM_KEY_CONTROL_BLOCK *ParentKcb) +{ + LONG HashStackIndex; + LONG Result; + PCM_NAME_CONTROL_BLOCK NameBlock; + UNICODE_STRING CurrentNameBlock; + + ASSERT(CurrentKcb != NULL); + + /* Loop each hash and check that they match */ + HashStackIndex = RemainingSubkeys; + while (HashStackIndex >= 0) + { + /* Does the subkey hash match? */ + if (CurrentKcb->ConvKey != HashCacheStack[HashStackIndex].ConvKey) + { + *ParentKcb = CurrentKcb; + return FALSE; + } + + /* Compare the subkey string, is the name compressed? */ + NameBlock = CurrentKcb->NameBlock; + if (NameBlock->Compressed) + { + Result = CmpCompareCompressedName(&HashCacheStack[HashStackIndex].NameOfKey, + NameBlock->Name, + NameBlock->NameLength); + } + else + { + CurrentNameBlock.Buffer = NameBlock->Name; + CurrentNameBlock.Length = NameBlock->NameLength; + CurrentNameBlock.MaximumLength = NameBlock->NameLength; + + Result = RtlCompareUnicodeString(&HashCacheStack[HashStackIndex].NameOfKey, + &CurrentNameBlock, + TRUE); + } + + /* Do the subkey names match? */ + if (Result) + { + *ParentKcb = CurrentKcb; + return FALSE; + } + + /* Go to the next subkey hash */ + HashStackIndex--; + } + + /* All the subkeys match */ + *ParentKcb = CurrentKcb->ParentKcb; + return TRUE; +} + +static +VOID +CmpRemoveSubkeysInRemainingName( + _In_ PCM_HASH_CACHE_STACK HashCacheStack, + _In_ ULONG RemainingSubkeys, + _Inout_ PUNICODE_STRING RemainingName) +{ + ULONG HashStackIndex = 0; + + /* Skip any leading separator on matching name */ + while (RemainingName->Length >= sizeof(WCHAR) && + RemainingName->Buffer[0] == OBJ_NAME_PATH_SEPARATOR) + { + RemainingName->Buffer++; + RemainingName->Length -= sizeof(WCHAR); + } + + /* Skip the subkeys as well */ + while (HashStackIndex <= RemainingSubkeys) + { + RemainingName->Buffer += HashCacheStack[HashStackIndex].NameOfKey.Length / sizeof(WCHAR); + RemainingName->Length -= HashCacheStack[HashStackIndex].NameOfKey.Length; + + /* Skip any leading separator */ + while (RemainingName->Length >= sizeof(WCHAR) && + RemainingName->Buffer[0] == OBJ_NAME_PATH_SEPARATOR) + { + RemainingName->Buffer++; + RemainingName->Length -= sizeof(WCHAR); + } + + /* Go to the next hash */ + HashStackIndex++; + } +} + +static +NTSTATUS +CmpLookInCache( + _In_ PCM_HASH_CACHE_STACK HashCacheStack, + _In_ BOOLEAN LockKcbsExclusive, + _In_ ULONG TotalRemainingSubkeys, + _Inout_ PUNICODE_STRING RemainingName, + _Inout_ PULONG OuterStackArray, + _Inout_ PCM_KEY_CONTROL_BLOCK *Kcb, + _Out_ PHHIVE *Hive, + _Out_ PHCELL_INDEX Cell, + _Out_ PULONG MatchRemainSubkeyLevel) +{ + LONG RemainingSubkeys; + ULONG TotalLevels; + BOOLEAN SubkeysMatch; + PCM_KEY_CONTROL_BLOCK CurrentKcb, ParentKcb; + PCM_KEY_HASH HashEntry = NULL; + BOOLEAN KeyFoundInCache = FALSE; + PULONG LockedKcbs = NULL; + + /* Reference the KCB */ + if (!CmpReferenceKeyControlBlock(*Kcb)) + { + /* This key is opened too many times, bail out */ + DPRINT1("Could not reference the KCB, too many references (KCB 0x%p)\n", Kcb); + return STATUS_UNSUCCESSFUL; + } + + /* Prepare to lock the KCBs */ + LockedKcbs = CmpBuildAndLockKcbArray(HashCacheStack, + LockKcbsExclusive ? CMP_LOCK_KCB_ARRAY_EXCLUSIVE : CMP_LOCK_KCB_ARRAY_SHARED, + *Kcb, + OuterStackArray, + TotalRemainingSubkeys, + 0); + NT_ASSERT(LockedKcbs); + + /* Lookup in the cache */ + RemainingSubkeys = TotalRemainingSubkeys - 1; + TotalLevels = TotalRemainingSubkeys + (*Kcb)->TotalLevels + 1; + while (RemainingSubkeys >= 0) + { + /* Get the hash entry from the cache */ + HashEntry = GET_HASH_ENTRY(CmpCacheTable, HashCacheStack[RemainingSubkeys].ConvKey)->Entry; + + /* Take one level down as we are processing this hash entry */ + TotalLevels--; + + while (HashEntry != NULL) + { + /* Validate this hash and obtain the current KCB */ + ASSERT_VALID_HASH(HashEntry); + CurrentKcb = CONTAINING_RECORD(HashEntry, CM_KEY_CONTROL_BLOCK, KeyHash); + + /* Does this KCB have matching levels? */ + if (TotalLevels == CurrentKcb->TotalLevels) + { + /* + * We have matching subkey levels but don't directly assume we have + * a matching key path in cache. Start comparing each subkey. + */ + SubkeysMatch = CmpCompareSubkeys(HashCacheStack, + CurrentKcb, + RemainingSubkeys, + &ParentKcb); + if (SubkeysMatch) + { + /* All subkeys match, now check if the base KCB matches with parent */ + if (*Kcb == ParentKcb) + { + /* Is the KCB marked as deleted? */ + if (CurrentKcb->Delete || + CurrentKcb->ParentKcb->Delete) + { + /* + * Either the current or its parent KCB is marked + * but we had a shared lock so probably a naughty + * thread was deleting it. Retry doing a cache + * lookup again with exclusive lock. + */ + if (!LockKcbsExclusive) + { + CmpUnLockKcbArray(LockedKcbs); + CmpDereferenceKeyControlBlock(*Kcb); + DPRINT1("The current KCB or its parent is deleted, retrying looking in the cache\n"); + return STATUS_RETRY; + } + + /* We're under an exclusive lock, is the KCB deleted yet? */ + if (CurrentKcb->Delete) + { + /* The KCB is gone, the key should no longer belong in the cache */ + CmpRemoveKeyControlBlock(CurrentKcb); + CmpUnLockKcbArray(LockedKcbs); + CmpDereferenceKeyControlBlock(*Kcb); + DPRINT1("The current KCB is deleted (KCB 0x%p)\n", CurrentKcb); + return STATUS_OBJECT_NAME_NOT_FOUND; + } + + /* + * The parent is deleted so it must be that somebody created + * a fake key. Assert ourselves that is the case. + */ + ASSERT(CurrentKcb->ExtFlags & CM_KCB_KEY_NON_EXIST); + + /* Remove this KCB out of cache if someone still uses it */ + if (CurrentKcb->RefCount != 0) + { + CurrentKcb->Delete = TRUE; + CmpRemoveKeyControlBlock(CurrentKcb); + } + else + { + /* Otherwise expunge it */ + CmpRemoveFromDelayedClose(CurrentKcb); + CmpCleanUpKcbCacheWithLock(CurrentKcb, FALSE); + } + + /* Stop looking for next hashes as the KCB is kaput */ + break; + } + + /* We finally found the key in cache, acknowledge it */ + KeyFoundInCache = TRUE; + + /* Remove the subkeys in the remaining name and stop looking in the cache */ + CmpRemoveSubkeysInRemainingName(HashCacheStack, RemainingSubkeys, RemainingName); + break; + } + } + } + + /* Go to the next hash */ + HashEntry = HashEntry->NextHash; + } + + /* Stop looking in cache if we found the matching key */ + if (KeyFoundInCache) + { + DPRINT("Key found in cache, stop looking\n"); + break; + } + + /* Keep looking in the cache until we run out of remaining subkeys */ + RemainingSubkeys--; + } + + /* Return the matching subkey levels */ + *MatchRemainSubkeyLevel = RemainingSubkeys + 1; + + /* We have to update the KCB if the key was found in cache */ + if (KeyFoundInCache) + { + /* + * Before we change the KCB we must dereference the prior + * KCB that we no longer need it. + */ + CmpDereferenceKeyControlBlock(*Kcb); + *Kcb = CurrentKcb; + + /* Reference the new KCB now */ + if (!CmpReferenceKeyControlBlock(*Kcb)) + { + /* This key is opened too many times, bail out */ + DPRINT1("Could not reference the KCB, too many references (KCB 0x%p)\n", Kcb); + return STATUS_UNSUCCESSFUL; + } + + /* Update hive and cell data from current KCB */ + *Hive = CurrentKcb->KeyHive; + *Cell = CurrentKcb->KeyCell; + } + + /* Unlock the KCBs */ + CmpUnLockKcbArray(LockedKcbs); + return STATUS_SUCCESS; +} + NTSTATUS NTAPI -CmpBuildHashStackAndLookupCache(IN PCM_KEY_BODY ParseObject, - IN OUT PCM_KEY_CONTROL_BLOCK *Kcb, - IN PUNICODE_STRING Current, - OUT PHHIVE *Hive, - OUT HCELL_INDEX *Cell, - OUT PULONG TotalRemainingSubkeys, - OUT PULONG MatchRemainSubkeyLevel, - OUT PULONG TotalSubkeys, - OUT PULONG OuterStackArray, - OUT PULONG *LockedKcbs) +CmpBuildHashStackAndLookupCache( + _In_ PCM_KEY_BODY ParseObject, + _Inout_ PCM_KEY_CONTROL_BLOCK *Kcb, + _In_ PUNICODE_STRING Current, + _Out_ PHHIVE *Hive, + _Out_ PHCELL_INDEX Cell, + _Out_ PULONG TotalRemainingSubkeys, + _Out_ PULONG MatchRemainSubkeyLevel, + _Out_ PULONG TotalSubkeys, + _Inout_ PULONG OuterStackArray, + _Out_ PULONG *LockedKcbs) { - /* We don't lock anything for now */ - *LockedKcbs = NULL; + NTSTATUS Status; + ULONG ConvKey; + ULONG SubkeysInTotal, RemainingSubkeysInTotal, MatchRemainingSubkeys; + CM_HASH_CACHE_STACK HashCacheStack[CMP_SUBKEY_LEVELS_DEPTH_LIMIT]; - /* Calculate hash values */ - *TotalRemainingSubkeys = 0xBAADF00D; + /* Make sure it's not a dead KCB */ + ASSERT((*Kcb)->RefCount > 0); /* Lock the registry */ CmpLockRegistry(); + /* Calculate hash values for every subkey this key path has */ + ConvKey = (*Kcb)->ConvKey; + RemainingSubkeysInTotal = CmpComputeHashValue(Current, + ConvKey, + HashCacheStack, + &SubkeysInTotal); + + /* This key path has too many subkeys */ + if (SubkeysInTotal > CMP_SUBKEY_LEVELS_DEPTH_LIMIT) + { + DPRINT1("The key path has too many subkeys - %lu\n", SubkeysInTotal); + *LockedKcbs = NULL; + return STATUS_NAME_TOO_LONG; + } + /* Return hive and cell data */ *Hive = (*Kcb)->KeyHive; *Cell = (*Kcb)->KeyCell; - /* Make sure it's not a dead KCB */ - ASSERT((*Kcb)->RefCount > 0); + /* Do we have any subkeys? */ + if (!RemainingSubkeysInTotal && !SubkeysInTotal) + { + /* + * We don't have any subkeys nor remaining levels, the + * KCB points to the actual key. Lock it. + */ + if (!CmpReferenceKeyControlBlock(*Kcb)) + { + /* This key is opened too many times, bail out */ + DPRINT1("Could not reference the KCB, too many references (KCB 0x%p)\n", Kcb); + return STATUS_UNSUCCESSFUL; + } - /* Reference it */ - (VOID)CmpReferenceKeyControlBlock(*Kcb); + CmpAcquireKcbLockSharedByIndex(GET_HASH_INDEX(ConvKey)); - /* Return success for now */ - return STATUS_SUCCESS; + /* Add this KCB in the array of locked KCBs */ + OuterStackArray[0] = 1; + OuterStackArray[1] = GET_HASH_INDEX(ConvKey); + *LockedKcbs = OuterStackArray; + + /* And return all the subkey level counters */ + *TotalRemainingSubkeys = RemainingSubkeysInTotal; + *MatchRemainSubkeyLevel = 0; + *TotalSubkeys = SubkeysInTotal; + return STATUS_SUCCESS; + } + + /* Lookup in the cache */ + Status = CmpLookInCache(HashCacheStack, + FALSE, + RemainingSubkeysInTotal, + Current, + OuterStackArray, + Kcb, + Hive, + Cell, + &MatchRemainingSubkeys); + if (!NT_SUCCESS(Status)) + { + /* Bail out if cache lookup failed for other reasons */ + if (Status != STATUS_RETRY) + { + DPRINT1("CmpLookInCache() failed (Status 0x%lx)\n", Status); + *LockedKcbs = NULL; + return Status; + } + + /* Retry looking in the cache but with KCBs locked exclusively */ + Status = CmpLookInCache(HashCacheStack, + TRUE, + RemainingSubkeysInTotal, + Current, + OuterStackArray, + Kcb, + Hive, + Cell, + &MatchRemainingSubkeys); + if (!NT_SUCCESS(Status)) + { + DPRINT1("CmpLookInCache() failed after retry (Status 0x%lx)\n", Status); + *LockedKcbs = NULL; + return Status; + } + } + + /* + * Check if we have a full match of remaining levels. + * + * FIXME: It is possible we can catch a fake key from the cache + * when we did the lookup, in such case we should not do any + * locking as such KCB does not point to any real information. + * Currently ReactOS doesn't create fake KCBs so we are good + * for now. + */ + if (RemainingSubkeysInTotal == MatchRemainingSubkeys) + { + /* + * Just simply lock this KCB as it points to the full + * subkey levels in cache. + */ + CmpAcquireKcbLockSharedByIndex(GET_HASH_INDEX((*Kcb)->ConvKey)); + OuterStackArray[0] = 1; + OuterStackArray[1] = GET_HASH_INDEX((*Kcb)->ConvKey); + *LockedKcbs = OuterStackArray; + } + else + { + /* + * We only have a partial match so other subkey levels + * have each KCB. Simply just lock them. + */ + *LockedKcbs = CmpBuildAndLockKcbArray(HashCacheStack, + CMP_LOCK_KCB_ARRAY_EXCLUSIVE, + *Kcb, + OuterStackArray, + RemainingSubkeysInTotal, + MatchRemainingSubkeys); + NT_ASSERT(*LockedKcbs); + } + + /* Return all the subkey level counters */ + *TotalRemainingSubkeys = RemainingSubkeysInTotal; + *MatchRemainSubkeyLevel = MatchRemainingSubkeys; + *TotalSubkeys = SubkeysInTotal; + return Status; } NTSTATUS @@ -1064,7 +1680,9 @@ CmpParseKey(IN PVOID ParseObject, UNICODE_STRING Current, NextName; PCM_PARSE_CONTEXT ParseContext = Context; ULONG TotalRemainingSubkeys = 0, MatchRemainSubkeyLevel = 0, TotalSubkeys = 0; - PULONG LockedKcbs = NULL; + ULONG LockedKcbArray[CMP_KCBS_IN_ARRAY_LIMIT]; + PULONG LockedKcbs; + BOOLEAN IsKeyCached = FALSE; BOOLEAN Result, Last; PAGED_CODE(); @@ -1109,8 +1727,15 @@ CmpParseKey(IN PVOID ParseObject, &TotalRemainingSubkeys, &MatchRemainSubkeyLevel, &TotalSubkeys, - NULL, + LockedKcbArray, &LockedKcbs); + CMP_ASSERT_REGISTRY_LOCK(); + if (!NT_SUCCESS(Status)) + { + DPRINT1("Failed to look in cache, stop parsing (Status 0x%lx)\n", Status); + ParentKcb = NULL; + goto Quickie; + } /* This is now the parent */ ParentKcb = Kcb; @@ -1118,10 +1743,6 @@ CmpParseKey(IN PVOID ParseObject, /* Sanity check */ ASSERT(ParentKcb != NULL); - /* Check if everything was found cached */ - if (!TotalRemainingSubkeys) - ASSERTMSG("Caching not implemented\n", FALSE); - /* Don't do anything if we're being deleted */ if (Kcb->Delete) { @@ -1129,6 +1750,51 @@ CmpParseKey(IN PVOID ParseObject, goto Quickie; } + /* Check if everything was found cached */ + if (!TotalRemainingSubkeys) + { + /* + * We don't have any remaining subkey levels so we're good + * that we have an already perfect candidate for a KCB, just + * do the open directly. + */ + DPRINT("No remaining subkeys, the KCB points to the actual key\n"); + IsKeyCached = TRUE; + goto KeyCachedOpenNow; + } + + /* Check if we have a matching level */ + if (MatchRemainSubkeyLevel) + { + /* + * We have a matching level, check if that matches + * with the total levels of subkeys. Do the open directly + * if that is the case, because the whole subkeys levels + * is cached. + */ + if (MatchRemainSubkeyLevel == TotalSubkeys) + { + DPRINT("We have a full matching level, open the key now\n"); + IsKeyCached = TRUE; + goto KeyCachedOpenNow; + } + + /* + * We only have a partial match, make sure we did not + * get mismatched hive data. + */ + ASSERT(Hive == Kcb->KeyHive); + ASSERT(Cell == Kcb->KeyCell); + } + + /* + * FIXME: Currently the registry parser doesn't check for fake + * KCBs. CmpCreateKeyControlBlock does have the necessary implementation + * to create such fake keys but we don't create these fake keys anywhere. + * When we will do, we must improve the registry parser routine to handle + * fake keys a bit differently here. + */ + /* Check if this is a symlink */ if (Kcb->Flags & KEY_SYM_LINK) { @@ -1154,6 +1820,10 @@ CmpParseKey(IN PVOID ParseObject, } Current.MaximumLength += NextName.MaximumLength; + /* CmpGetSymbolicLink doesn't want a lock */ + CmpUnLockKcbArray(LockedKcbs); + LockedKcbs = NULL; + /* Parse the symlink */ if (CmpGetSymbolicLink(Hive, CompleteName, @@ -1221,6 +1891,7 @@ CmpParseKey(IN PVOID ParseObject, } } +KeyCachedOpenNow: /* Do the open */ Status = CmpDoOpen(Hive, Cell, @@ -1229,12 +1900,17 @@ CmpParseKey(IN PVOID ParseObject, AccessMode, Attributes, ParseContext, - 0, + IsKeyCached ? CMP_OPEN_KCB_NO_CREATE : CMP_CREATE_KCB_KCB_LOCKED, &Kcb, + LockedKcbs, &NextName, Object); if (Status == STATUS_REPARSE) { + /* CmpGetSymbolicLink doesn't want a lock */ + CmpUnLockKcbArray(LockedKcbs); + LockedKcbs = NULL; + /* Parse the symlink */ if (!CmpGetSymbolicLink(Hive, CompleteName, @@ -1272,7 +1948,7 @@ CmpParseKey(IN PVOID ParseObject, Cell, Node, ParentKcb, - 0, + CMP_LOCK_HASHES_FOR_KCB, &NextName); if (!Kcb) { @@ -1282,7 +1958,7 @@ CmpParseKey(IN PVOID ParseObject, } /* Dereference the parent and set the new one */ - CmpDereferenceKeyControlBlock(ParentKcb); + CmpDereferenceKeyControlBlockWithLock(ParentKcb, FALSE); ParentKcb = Kcb; } else @@ -1302,6 +1978,7 @@ CmpParseKey(IN PVOID ParseObject, Attributes, ParseContext, ParentKcb, + LockedKcbs, Object); } else if (Hive == &CmiVolatileHive->Hive && CmpNoVolatileCreates) @@ -1360,6 +2037,10 @@ CmpParseKey(IN PVOID ParseObject, } Current.MaximumLength += NextName.MaximumLength; + /* CmpGetSymbolicLink doesn't want a lock */ + CmpUnLockKcbArray(LockedKcbs); + LockedKcbs = NULL; + /* Parse the symlink */ if (CmpGetSymbolicLink(Hive, CompleteName, @@ -1406,8 +2087,9 @@ CmpParseKey(IN PVOID ParseObject, AccessMode, Attributes, ParseContext, - CMP_OPEN_KCB_NO_CREATE /* | CMP_CREATE_KCB_KCB_LOCKED */, + CMP_OPEN_KCB_NO_CREATE, &Kcb, + LockedKcbs, &NextName, Object); if (Status == STATUS_REPARSE) @@ -1426,8 +2108,14 @@ CmpParseKey(IN PVOID ParseObject, } } - /* Dereference the parent if it exists */ Quickie: + /* Unlock all the KCBs */ + if (LockedKcbs != NULL) + { + CmpUnLockKcbArray(LockedKcbs); + } + + /* Dereference the parent if it exists */ if (ParentKcb) CmpDereferenceKeyControlBlock(ParentKcb); diff --git a/ntoskrnl/include/internal/cm.h b/ntoskrnl/include/internal/cm.h index eab9a1c9b61..db702bd8b28 100644 --- a/ntoskrnl/include/internal/cm.h +++ b/ntoskrnl/include/internal/cm.h @@ -1133,12 +1133,12 @@ CmpCreateLinkNode( IN PHHIVE Hive, IN HCELL_INDEX Cell, IN PACCESS_STATE AccessState, - IN PULONG KcbsLocked, IN UNICODE_STRING Name, IN KPROCESSOR_MODE AccessMode, IN ULONG CreateOptions, IN PCM_PARSE_CONTEXT Context, IN PCM_KEY_CONTROL_BLOCK ParentKcb, + IN PULONG KcbsLocked, OUT PVOID *Object );