/* * PROJECT: ReactOS Kernel * LICENSE: GPL - See COPYING in the top level directory * FILE: ntoskrnl/config/cmparse.c * PURPOSE: Configuration Manager - Object Manager Parse Interface * PROGRAMMERS: Alex Ionescu (alex.ionescu@reactos.org) */ /* INCLUDES ******************************************************************/ #include "ntoskrnl.h" #define NDEBUG #include "debug.h" /* GLOBALS *******************************************************************/ /* FUNCTIONS *****************************************************************/ BOOLEAN NTAPI CmpGetNextName(IN OUT PUNICODE_STRING RemainingName, OUT PUNICODE_STRING NextName, OUT PBOOLEAN LastName) { BOOLEAN NameValid = TRUE; ASSERT(RemainingName->Length % sizeof(WCHAR) == 0); /* Check if there's nothing left in the name */ if (!(RemainingName->Buffer) || (!RemainingName->Length) || !(*RemainingName->Buffer)) { /* Clear the next name and set this as last */ *LastName = TRUE; NextName->Buffer = NULL; NextName->Length = 0; return TRUE; } /* Check if we have a path separator */ while (RemainingName->Length && (*RemainingName->Buffer == OBJ_NAME_PATH_SEPARATOR)) { /* Skip it */ RemainingName->Buffer++; RemainingName->Length -= sizeof(WCHAR); RemainingName->MaximumLength -= sizeof(WCHAR); } /* Start loop at where the current buffer is */ NextName->Buffer = RemainingName->Buffer; while (RemainingName->Length && (*RemainingName->Buffer != OBJ_NAME_PATH_SEPARATOR)) { /* Move to the next character */ RemainingName->Buffer++; RemainingName->Length -= sizeof(WCHAR); RemainingName->MaximumLength -= sizeof(WCHAR); } /* See how many chars we parsed and validate the length */ NextName->Length = (USHORT)((ULONG_PTR)RemainingName->Buffer - (ULONG_PTR)NextName->Buffer); if (NextName->Length > 512) NameValid = FALSE; NextName->MaximumLength = NextName->Length; /* If there's nothing left, we're last */ *LastName = !RemainingName->Length; return NameValid; } BOOLEAN NTAPI CmpGetSymbolicLink(IN PHHIVE Hive, IN OUT PUNICODE_STRING ObjectName, IN OUT PCM_KEY_CONTROL_BLOCK SymbolicKcb, IN PUNICODE_STRING RemainingName OPTIONAL) { HCELL_INDEX LinkCell = HCELL_NIL; PCM_KEY_VALUE LinkValue = NULL; PWSTR LinkName = NULL; BOOLEAN LinkNameAllocated = FALSE; PWSTR NewBuffer; ULONG Length = 0; ULONG ValueLength = 0; BOOLEAN Result = FALSE; HCELL_INDEX CellToRelease = HCELL_NIL; PCM_KEY_NODE Node; UNICODE_STRING NewObjectName; /* Make sure we're not being deleted */ if (SymbolicKcb->Delete) return FALSE; /* Get the key node */ Node = (PCM_KEY_NODE)HvGetCell(SymbolicKcb->KeyHive, SymbolicKcb->KeyCell); if (!Node) goto Exit; /* Find the symbolic link key */ LinkCell = CmpFindValueByName(Hive, Node, &CmSymbolicLinkValueName); HvReleaseCell(SymbolicKcb->KeyHive, SymbolicKcb->KeyCell); if (LinkCell == HCELL_NIL) goto Exit; /* Get the value cell */ LinkValue = (PCM_KEY_VALUE)HvGetCell(Hive, LinkCell); if (!LinkValue) goto Exit; /* Make sure it's a registry link */ if (LinkValue->Type != REG_LINK) goto Exit; /* Now read the value data */ if (!CmpGetValueData(Hive, LinkValue, &ValueLength, (PVOID*)&LinkName, &LinkNameAllocated, &CellToRelease)) { /* Fail */ goto Exit; } /* Get the length */ Length = ValueLength + sizeof(WCHAR); /* Make sure we start with a slash */ if (*LinkName != OBJ_NAME_PATH_SEPARATOR) goto Exit; /* Add the remaining name if needed */ if (RemainingName) Length += RemainingName->Length + sizeof(WCHAR); /* Check for overflow */ if (Length > 0xFFFF) goto Exit; /* Check if we need a new buffer */ if (Length > ObjectName->MaximumLength) { /* We do -- allocate one */ NewBuffer = ExAllocatePoolWithTag(PagedPool, Length, TAG_CM); if (!NewBuffer) goto Exit; /* Setup the new string and copy the symbolic target */ NewObjectName.Buffer = NewBuffer; NewObjectName.MaximumLength = (USHORT)Length; NewObjectName.Length = (USHORT)ValueLength; RtlCopyMemory(NewBuffer, LinkName, ValueLength); /* Check if we need to add anything else */ if (RemainingName) { /* Add the remaining name */ NewBuffer[ValueLength / sizeof(WCHAR)] = OBJ_NAME_PATH_SEPARATOR; NewObjectName.Length += sizeof(WCHAR); RtlAppendUnicodeStringToString(&NewObjectName, RemainingName); } /* Free the old buffer */ ExFreePool(ObjectName->Buffer); *ObjectName = NewObjectName; } else { /* The old name is large enough -- update the length */ ObjectName->Length = (USHORT)ValueLength; if (RemainingName) { /* Copy the remaining name inside */ RtlMoveMemory(&ObjectName->Buffer[(ValueLength / sizeof(WCHAR)) + 1], RemainingName->Buffer, RemainingName->Length); /* Add the slash and update the length */ ObjectName->Buffer[ValueLength / sizeof(WCHAR)] = OBJ_NAME_PATH_SEPARATOR; ObjectName->Length += RemainingName->Length + sizeof(WCHAR); } /* Copy the symbolic link target name */ RtlCopyMemory(ObjectName->Buffer, LinkName, ValueLength); } /* Null-terminate the whole thing */ ObjectName->Buffer[ObjectName->Length / sizeof(WCHAR)] = UNICODE_NULL; Result = TRUE; Exit: /* Free the link name */ if (LinkNameAllocated) ExFreePool(LinkName); /* Check if we had a value cell */ if (LinkValue) { /* Release it */ ASSERT(LinkCell != HCELL_NIL); HvReleaseCell(Hive, LinkCell); } /* Check if we had an active cell and release it, then return the result */ if (CellToRelease != HCELL_NIL) HvReleaseCell(Hive, CellToRelease); return Result; } NTSTATUS NTAPI CmpDoCreateChild(IN PHHIVE Hive, IN HCELL_INDEX ParentCell, IN PSECURITY_DESCRIPTOR ParentDescriptor OPTIONAL, IN PACCESS_STATE AccessState, IN PUNICODE_STRING Name, IN KPROCESSOR_MODE AccessMode, IN PCM_PARSE_CONTEXT ParseContext, IN PCM_KEY_CONTROL_BLOCK ParentKcb, IN ULONG Flags, OUT PHCELL_INDEX KeyCell, OUT PVOID *Object) { NTSTATUS Status = STATUS_SUCCESS; PCM_KEY_BODY KeyBody; HCELL_INDEX ClassCell = HCELL_NIL; PCM_KEY_NODE KeyNode; PCELL_DATA CellData; ULONG StorageType; PCM_KEY_CONTROL_BLOCK Kcb; PSECURITY_DESCRIPTOR NewDescriptor; /* Get the storage type */ StorageType = Stable; if (ParseContext->CreateOptions & REG_OPTION_VOLATILE) StorageType = Volatile; /* Allocate the child */ *KeyCell = HvAllocateCell(Hive, FIELD_OFFSET(CM_KEY_NODE, Name) + CmpNameSize(Hive, Name), StorageType, HCELL_NIL); if (*KeyCell == HCELL_NIL) { /* Fail */ Status = STATUS_INSUFFICIENT_RESOURCES; goto Quickie; } /* Get the key node */ KeyNode = (PCM_KEY_NODE)HvGetCell(Hive, *KeyCell); if (!KeyNode) { /* Fail, this should never happen */ ASSERT(FALSE); Status = STATUS_INSUFFICIENT_RESOURCES; goto Quickie; } /* Release the cell */ HvReleaseCell(Hive, *KeyCell); /* Check if we have a class name */ if (ParseContext->Class.Length > 0) { /* Allocate a class cell */ ClassCell = HvAllocateCell(Hive, ParseContext->Class.Length, StorageType, HCELL_NIL); if (ClassCell == HCELL_NIL) { /* Fail */ Status = STATUS_INSUFFICIENT_RESOURCES; goto Quickie; } } /* Allocate the Cm Object */ Status = ObCreateObject(AccessMode, CmpKeyObjectType, NULL, AccessMode, NULL, sizeof(CM_KEY_BODY), 0, 0, Object); if (!NT_SUCCESS(Status)) goto Quickie; /* Setup the key body */ KeyBody = (PCM_KEY_BODY)(*Object); KeyBody->Type = CM_KEY_BODY_TYPE; KeyBody->KeyControlBlock = NULL; KeyBody->KcbLocked = FALSE; /* Check if we had a class */ if (ParseContext->Class.Length > 0) { /* Get the class cell */ CellData = HvGetCell(Hive, ClassCell); if (!CellData) { /* Fail, this should never happen */ ASSERT(FALSE); Status = STATUS_INSUFFICIENT_RESOURCES; ObDereferenceObject(*Object); goto Quickie; } /* Release the cell */ HvReleaseCell(Hive, ClassCell); /* Copy the class data */ RtlCopyMemory(&CellData->u.KeyString[0], ParseContext->Class.Buffer, ParseContext->Class.Length); } /* Fill out the key node */ KeyNode->Signature = CM_KEY_NODE_SIGNATURE; KeyNode->Flags = Flags; KeQuerySystemTime(&KeyNode->LastWriteTime); KeyNode->Spare = 0; KeyNode->Parent = ParentCell; KeyNode->SubKeyCounts[Stable] = 0; KeyNode->SubKeyCounts[Volatile] = 0; KeyNode->SubKeyLists[Stable] = HCELL_NIL; KeyNode->SubKeyLists[Volatile] = HCELL_NIL; KeyNode->ValueList.Count = 0; KeyNode->ValueList.List = HCELL_NIL; KeyNode->Security = HCELL_NIL; KeyNode->Class = ClassCell; KeyNode->ClassLength = ParseContext->Class.Length; KeyNode->MaxValueDataLen = 0; KeyNode->MaxNameLen = 0; KeyNode->MaxValueNameLen = 0; KeyNode->MaxClassLen = 0; KeyNode->NameLength = CmpCopyName(Hive, KeyNode->Name, Name); if (KeyNode->NameLength < Name->Length) KeyNode->Flags |= KEY_COMP_NAME; /* Create the KCB */ Kcb = CmpCreateKeyControlBlock(Hive, *KeyCell, KeyNode, ParentKcb, CMP_LOCK_HASHES_FOR_KCB, Name); if (!Kcb) { /* Fail */ ObDereferenceObjectDeferDelete(*Object); Status = STATUS_INSUFFICIENT_RESOURCES; goto Quickie; } /* Sanity check */ ASSERT(Kcb->RefCount == 1); /* Now fill out the Cm object */ KeyBody->NotifyBlock = NULL; KeyBody->ProcessID = PsGetCurrentProcessId(); KeyBody->KeyControlBlock = Kcb; /* Link it with the KCB */ EnlistKeyBodyWithKCB(KeyBody, CMP_ENLIST_KCB_LOCKED_EXCLUSIVE); /* Assign security */ Status = SeAssignSecurity(ParentDescriptor, AccessState->SecurityDescriptor, &NewDescriptor, TRUE, &AccessState->SubjectSecurityContext, &CmpKeyObjectType->TypeInfo.GenericMapping, CmpKeyObjectType->TypeInfo.PoolType); if (NT_SUCCESS(Status)) { /* * FIXME: We must acquire a security lock when assigning * a security descriptor to this hive but since the * CmpAssignSecurityDescriptor function does nothing * (we lack the necessary security management implementations * anyway), do not do anything for now. */ Status = CmpAssignSecurityDescriptor(Kcb, NewDescriptor); } /* Now that the security descriptor is copied in the hive, we can free the original */ SeDeassignSecurity(&NewDescriptor); if (NT_SUCCESS(Status)) { /* Send notification to registered callbacks */ CmpReportNotify(Kcb, Hive, Kcb->KeyCell, REG_NOTIFY_CHANGE_NAME); } Quickie: /* Check if we got here because of failure */ if (!NT_SUCCESS(Status)) { /* Free any cells we might've allocated */ if (ParseContext->Class.Length > 0) HvFreeCell(Hive, ClassCell); HvFreeCell(Hive, *KeyCell); } /* Return status */ return Status; } NTSTATUS NTAPI CmpDoCreate(IN PHHIVE Hive, IN HCELL_INDEX Cell, IN PACCESS_STATE AccessState, IN PUNICODE_STRING Name, IN KPROCESSOR_MODE AccessMode, IN PCM_PARSE_CONTEXT ParseContext, IN PCM_KEY_CONTROL_BLOCK ParentKcb, OUT PVOID *Object) { NTSTATUS Status; PCELL_DATA CellData; HCELL_INDEX KeyCell; ULONG ParentType; PCM_KEY_BODY KeyBody; PSECURITY_DESCRIPTOR SecurityDescriptor = NULL; 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) { /* It has, quit */ ASSERT(FALSE); Status = STATUS_OBJECT_NAME_NOT_FOUND; goto Exit; } /* Get the parent node */ KeyNode = (PCM_KEY_NODE)HvGetCell(Hive, Cell); if (!KeyNode) { /* Fail */ ASSERT(FALSE); Status = STATUS_INSUFFICIENT_RESOURCES; goto Exit; } /* Make sure nobody added us yet */ if (CmpFindSubKeyByName(Hive, KeyNode, Name) != HCELL_NIL) { /* Fail */ ASSERT(FALSE); Status = STATUS_REPARSE; goto Exit; } /* Sanity check */ ASSERT(Cell == ParentKcb->KeyCell); /* Get the parent type */ ParentType = HvGetCellType(Cell); if ((ParentType == Volatile) && !(ParseContext->CreateOptions & REG_OPTION_VOLATILE)) { /* Children of volatile parents must also be volatile */ //ASSERT(FALSE); Status = STATUS_CHILD_MUST_BE_VOLATILE; goto Exit; } /* Don't allow children under symlinks */ if (ParentKcb->Flags & KEY_SYM_LINK) { /* Fail */ ASSERT(FALSE); Status = STATUS_ACCESS_DENIED; goto Exit; } /* Make the cell dirty for now */ HvMarkCellDirty(Hive, Cell, FALSE); /* Do the actual create operation */ Status = CmpDoCreateChild(Hive, Cell, SecurityDescriptor, AccessState, Name, AccessMode, ParseContext, ParentKcb, 0, &KeyCell, Object); if (NT_SUCCESS(Status)) { /* Get the key body */ KeyBody = (PCM_KEY_BODY)(*Object); /* Now add the subkey */ if (!CmpAddSubKey(Hive, Cell, KeyCell)) { /* 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 */ KeyNode = (PCM_KEY_NODE)HvGetCell(Hive, Cell); if (!KeyNode) { /* Fail, this shouldn't happen */ 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); ASSERT(KeyBody->KeyControlBlock->ParentKcb == ParentKcb); ASSERT(KeyBody->KeyControlBlock->ParentKcb->KcbMaxNameLen == KeyNode->MaxNameLen); /* Update the timestamp */ KeQuerySystemTime(&TimeStamp); KeyNode->LastWriteTime = TimeStamp; KeyBody->KeyControlBlock->ParentKcb->KcbLastWriteTime = TimeStamp; /* Check if we need to update name maximum */ if (KeyNode->MaxNameLen < Name->Length) { /* Do it */ KeyNode->MaxNameLen = Name->Length; KeyBody->KeyControlBlock->ParentKcb->KcbMaxNameLen = Name->Length; } /* Check if we need to update class length maximum */ if (KeyNode->MaxClassLen < ParseContext->Class.Length) { /* Update it */ KeyNode->MaxClassLen = ParseContext->Class.Length; } /* Check if we're creating a symbolic link */ if (ParseContext->CreateOptions & REG_OPTION_CREATE_LINK) { /* Get the cell data */ CellData = HvGetCell(Hive, KeyCell); if (!CellData) { /* This shouldn't happen */ 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 */ CellData->u.KeyNode.Flags |= KEY_SYM_LINK; KeyBody->KeyControlBlock->Flags = CellData->u.KeyNode.Flags; HvReleaseCell(Hive, KeyCell); } } Exit: /* Release the flusher lock and return status */ CmpUnlockHiveFlusher((PCMHIVE)Hive); return Status; } NTSTATUS NTAPI CmpDoOpen(IN PHHIVE Hive, IN HCELL_INDEX Cell, IN PCM_KEY_NODE Node, IN PACCESS_STATE AccessState, IN KPROCESSOR_MODE AccessMode, IN ULONG Attributes, 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; /* Make sure the hive isn't locked */ if ((Hive->HiveFlags & HIVE_IS_UNLOADING) && (((PCMHIVE)Hive)->CreatorOwner != KeGetCurrentThread())) { /* It is, don't touch it */ return STATUS_OBJECT_NAME_NOT_FOUND; } /* Check if we have a context */ if (Context) { /* Check if this is a link create (which shouldn't be an open) */ if (Context->CreateLink) { return STATUS_ACCESS_DENIED; } /* Check if this is symlink create attempt */ if (Context->CreateOptions & REG_OPTION_CREATE_LINK) { /* Key already exists */ return STATUS_OBJECT_NAME_COLLISION; } /* Set the disposition */ Context->Disposition = REG_OPENED_EXISTING_KEY; } /* 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 (((*CachedKcb)->Flags & KEY_SYM_LINK) && !(Attributes & OBJ_OPENLINK)) { /* 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(Kcb)) { /* Return failure code */ return STATUS_INSUFFICIENT_RESOURCES; } } 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)) { /* Create the KCB for the symlink */ Kcb = CmpCreateKeyControlBlock(Hive, Cell, Node, *CachedKcb, LockKcb ? CMP_LOCK_HASHES_FOR_KCB : 0, KeyName); if (!Kcb) { /* Return failure */ return STATUS_INSUFFICIENT_RESOURCES; } /* Make sure it's also locked, and set the pointer */ ASSERT(CmpIsKcbLockedExclusive(Kcb)); *CachedKcb = Kcb; /* Return reparse required */ return STATUS_REPARSE; } /* Create the KCB */ Kcb = CmpCreateKeyControlBlock(Hive, Cell, Node, *CachedKcb, LockKcb ? CMP_LOCK_HASHES_FOR_KCB : 0, KeyName); if (!Kcb) { /* Return failure */ return STATUS_INSUFFICIENT_RESOURCES; } /* Make sure it's also locked, and set the pointer */ ASSERT(CmpIsKcbLockedExclusive(Kcb)); *CachedKcb = Kcb; } /* Allocate the key object */ Status = ObCreateObject(AccessMode, CmpKeyObjectType, NULL, AccessMode, NULL, sizeof(CM_KEY_BODY), 0, 0, Object); if (NT_SUCCESS(Status)) { /* Get the key body and fill it out */ KeyBody = (PCM_KEY_BODY)(*Object); KeyBody->KeyControlBlock = Kcb; KeyBody->Type = CM_KEY_BODY_TYPE; KeyBody->ProcessID = PsGetCurrentProcessId(); KeyBody->NotifyBlock = NULL; /* Link to the KCB */ 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 * to this key body. This is to prevent a potential deadlock on * CmpSecurityMethod as ObCheckObjectAccess will invoke the Object * Manager to call that method, of which CmpSecurityMethod would * attempt to acquire a lock again. */ KeyBody->KcbLocked = TRUE; if (!ObCheckObjectAccess(*Object, AccessState, FALSE, AccessMode, &Status)) { /* Access check failed */ ObDereferenceObject(*Object); } /* * We are done, the lock we are holding will be released * once the registry parsing is done. */ KeyBody->KcbLocked = FALSE; } else { /* Failed, dereference the KCB */ CmpDereferenceKeyControlBlockWithLock(Kcb, FALSE); } /* Return status */ return Status; } NTSTATUS NTAPI CmpCreateLinkNode(IN PHHIVE Hive, IN HCELL_INDEX Cell, IN PACCESS_STATE AccessState, 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) { NTSTATUS Status; HCELL_INDEX KeyCell, LinkCell, ChildCell; PCM_KEY_BODY KeyBody; LARGE_INTEGER TimeStamp; PCM_KEY_NODE KeyNode; PCM_KEY_CONTROL_BLOCK Kcb = ParentKcb; /* Link nodes only allowed on the master */ if (Hive != &CmiVolatileHive->Hive) { /* Fail */ DPRINT1("Invalid link node attempt\n"); 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) { /* It is, quit */ ASSERT(FALSE); Status = STATUS_OBJECT_NAME_NOT_FOUND; goto Exit; } /* Allocate a link node */ LinkCell = HvAllocateCell(Hive, FIELD_OFFSET(CM_KEY_NODE, Name) + CmpNameSize(Hive, &Name), Stable, HCELL_NIL); if (LinkCell == HCELL_NIL) { /* Fail */ Status = STATUS_INSUFFICIENT_RESOURCES; goto Exit; } /* Get the key cell */ KeyCell = Context->ChildHive.KeyCell; if (KeyCell != HCELL_NIL) { /* Hive exists! */ ChildCell = KeyCell; /* Get the node data */ KeyNode = (PCM_KEY_NODE)HvGetCell(Context->ChildHive.KeyHive, ChildCell); if (!KeyNode) { /* Fail */ ASSERT(FALSE); Status = STATUS_INSUFFICIENT_RESOURCES; goto Exit; } /* Fill out the data */ KeyNode->Parent = LinkCell; KeyNode->Flags |= KEY_HIVE_ENTRY | KEY_NO_DELETE; HvReleaseCell(Context->ChildHive.KeyHive, ChildCell); /* Now open the key cell */ KeyNode = (PCM_KEY_NODE)HvGetCell(Context->ChildHive.KeyHive, KeyCell); if (!KeyNode) { /* Fail */ ASSERT(FALSE); Status = STATUS_INSUFFICIENT_RESOURCES; goto Exit; } /* Open the parent */ Status = CmpDoOpen(Context->ChildHive.KeyHive, KeyCell, KeyNode, AccessState, AccessMode, CreateOptions, NULL, CMP_CREATE_KCB_KCB_LOCKED, &Kcb, KcbsLocked, &Name, Object); HvReleaseCell(Context->ChildHive.KeyHive, KeyCell); } else { /* Do the actual create operation */ Status = CmpDoCreateChild(Context->ChildHive.KeyHive, Cell, NULL, AccessState, &Name, AccessMode, Context, ParentKcb, KEY_HIVE_ENTRY | KEY_NO_DELETE, &ChildCell, Object); if (NT_SUCCESS(Status)) { /* Setup root pointer */ Context->ChildHive.KeyHive->BaseBlock->RootCell = ChildCell; } } /* Check if open or create suceeded */ if (NT_SUCCESS(Status)) { /* Mark the cell dirty */ HvMarkCellDirty(Context->ChildHive.KeyHive, ChildCell, FALSE); /* Get the key node */ KeyNode = (PCM_KEY_NODE)HvGetCell(Context->ChildHive.KeyHive, ChildCell); if (!KeyNode) { /* Fail */ ASSERT(FALSE); Status = STATUS_INSUFFICIENT_RESOURCES; goto Exit; } /* Release it */ HvReleaseCell(Context->ChildHive.KeyHive, ChildCell); /* Set the parent and flags */ KeyNode->Parent = LinkCell; KeyNode->Flags |= KEY_HIVE_ENTRY | KEY_NO_DELETE; /* Get the link node */ KeyNode = (PCM_KEY_NODE)HvGetCell(Hive, LinkCell); if (!KeyNode) { /* Fail */ ASSERT(FALSE); Status = STATUS_INSUFFICIENT_RESOURCES; goto Exit; } /* Set it up */ KeyNode->Signature = CM_LINK_NODE_SIGNATURE; KeyNode->Flags = KEY_HIVE_EXIT | KEY_NO_DELETE; KeyNode->Parent = Cell; KeyNode->NameLength = CmpCopyName(Hive, KeyNode->Name, &Name); if (KeyNode->NameLength < Name.Length) KeyNode->Flags |= KEY_COMP_NAME; KeQuerySystemTime(&TimeStamp); KeyNode->LastWriteTime = TimeStamp; /* Clear out the rest */ KeyNode->SubKeyCounts[Stable] = 0; KeyNode->SubKeyCounts[Volatile] = 0; KeyNode->SubKeyLists[Stable] = HCELL_NIL; KeyNode->SubKeyLists[Volatile] = HCELL_NIL; KeyNode->ValueList.Count = 0; KeyNode->ValueList.List = HCELL_NIL; KeyNode->ClassLength = 0; /* Reference the root node */ KeyNode->ChildHiveReference.KeyHive = Context->ChildHive.KeyHive; KeyNode->ChildHiveReference.KeyCell = ChildCell; HvReleaseCell(Hive, LinkCell); /* Get the parent node */ KeyNode = (PCM_KEY_NODE)HvGetCell(Hive, Cell); if (!KeyNode) { /* Fail */ ASSERT(FALSE); Status = STATUS_INSUFFICIENT_RESOURCES; goto Exit; } /* Now add the subkey */ if (!CmpAddSubKey(Hive, Cell, LinkCell)) { /* Failure! We don't handle this yet! */ ASSERT(FALSE); } /* 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); ASSERT(KeyBody->KeyControlBlock->ParentKcb->KcbMaxNameLen == KeyNode->MaxNameLen); /* Update the timestamp */ KeQuerySystemTime(&TimeStamp); KeyNode->LastWriteTime = TimeStamp; KeyBody->KeyControlBlock->ParentKcb->KcbLastWriteTime = TimeStamp; /* Check if we need to update name maximum */ if (KeyNode->MaxNameLen < Name.Length) { /* Do it */ KeyNode->MaxNameLen = Name.Length; KeyBody->KeyControlBlock->ParentKcb->KcbMaxNameLen = Name.Length; } /* Check if we need to update class length maximum */ if (KeyNode->MaxClassLen < Context->Class.Length) { /* Update it */ KeyNode->MaxClassLen = Context->Class.Length; } /* Release the cell */ HvReleaseCell(Hive, Cell); } else { /* Release the link cell */ HvReleaseCell(Hive, LinkCell); } Exit: /* Release the flusher locks and return status */ CmpUnlockHiveFlusher((PCMHIVE)Context->ChildHive.KeyHive); CmpUnlockHiveFlusher((PCMHIVE)Hive); return Status; } VOID NTAPI CmpHandleExitNode(IN OUT PHHIVE *Hive, IN OUT HCELL_INDEX *Cell, IN OUT PCM_KEY_NODE *KeyNode, IN OUT PHHIVE *ReleaseHive, IN OUT HCELL_INDEX *ReleaseCell) { /* Check if we have anything to release */ if (*ReleaseCell != HCELL_NIL) { /* Release it */ ASSERT(*ReleaseHive != NULL); HvReleaseCell(*ReleaseHive, *ReleaseCell); } /* Get the link references */ *Hive = (*KeyNode)->ChildHiveReference.KeyHive; *Cell = (*KeyNode)->ChildHiveReference.KeyCell; /* Get the new node */ *KeyNode = (PCM_KEY_NODE)HvGetCell(*Hive, *Cell); if (*KeyNode) { /* Set the new release values */ *ReleaseCell = *Cell; *ReleaseHive = *Hive; } else { /* Nothing to release */ *ReleaseCell = HCELL_NIL; *ReleaseHive = NULL; } } /** * @brief * Computes the hashes of each subkey in key path name * and stores them in a hash stack for cache lookup. * * @param[in] RemainingName * A Unicode string structure consisting of the remaining * registry key path name. * * @param[in] ConvKey * The hash convkey of the current KCB to be supplied. * * @param[in,out] HashCacheStack * An array stack. This function uses this array to store * all the computed hashes of a key pathname. * * @param[out] TotalSubKeys * The number of total subkeys that have been found, returned * by this function to the caller. If no subkey levels are found * the function returns 0. * * @return * Returns the number of remaining subkey levels to caller. * If no subkey levels are found then this function returns 0. */ 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; } /** * @brief * Compares each subkey's hash and name with those * captured in the hash cache stack. * * @param[in] HashCacheStack * A pointer to a hash cache stack array filled with * subkey hashes and names for comparison. * * @param[in] CurrentKcb * A pointer to the currently given KCB. * * @param[in] RemainingSubkeys * The remaining subkey levels to be supplied. * * @param[out] ParentKcb * A pointer to the parent KCB returned to the caller. * This parameter points to the parent of the current * KCB if all the subkeys match, otherwise it points * to the actual current KCB. * * @return * Returns TRUE if all the subkey levels match, otherwise * FALSE is returned. */ 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; } /** * @brief * Removes the subkeys on a remaining key pathname. * * @param[in] HashCacheStack * A pointer to a hash cache stack array filled with * subkey hashes and names. * * @param[in] RemainingSubkeys * The remaining subkey levels to be supplied. * * @param[in,out] RemainingName * A Unicode string structure consisting of the remaining * registry key path name, where the subkeys of such path * are to be removed. */ 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++; } } /** * @brief * Looks up in the pool cache for key pathname that matches * with one in the said cache and returns a KCB pointing * to that name. This function performs locking of KCBs * during cache lookup. * * @param[in] HashCacheStack * A pointer to a hash cache stack array filled with * subkey hashes and names. * * @param[in] LockKcbsExclusive * If set to TRUE, the KCBs are locked exclusively by the * calling thread, otherwise they are locked in shared mode. * See Remarks for further information. * * @param[in] TotalRemainingSubkeys * The total remaining subkey levels to be supplied. * * @param[in,out] RemainingName * A Unicode string structure consisting of the remaining * registry key path name. The remaining name is updated * by the function if a key pathname is found in cache. * * @param[in,out] OuterStackArray * A pointer to an array that lives on the caller's stack. * The expected size of the array is up to 32 elements, * which is the imposed limit by CMP_HASH_STACK_LIMIT. * This limit also corresponds to the maximum depth of * subkey levels. * * @param[in,out] Kcb * A pointer to a KCB, this KCB is changed if the key pathname * is found in cache. * * @param[out] Hive * A pointer to a hive, this hive is changed if the key pathname * is found in cache. * * @param[out] Cell * A pointer to a cell, this cell is changed if the key pathname * is found in cache. * * @param[out] MatchRemainSubkeyLevel * A pointer to match subkey levels returned by the function. * If no match levels are found, this is 0. * * @return * Returns STATUS_SUCCESS if cache lookup has completed successfully. * STATUS_OBJECT_NAME_NOT_FOUND is returned if the current KCB of * the key pathname has been deleted. STATUS_RETRY is returned if * at least the current KCB or its parent have been deleted * and a cache lookup must be retried again. STATUS_UNSUCCESSFUL is * returned if a KCB is referenced too many times. * * @remarks * The function attempts to do a cache lookup with a shared lock * on KCBs so that other threads can simultaneously get access * to these KCBs. When the captured KCB is being deleted on us * we have to retry a lookup with exclusive look so that no other * threads will mess with the KCBs and perform appropriate actions * if a KCB is deleted. */ 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; } /** * @brief * Builds a hash stack cache and looks up in the * pool cache for a matching key pathname. * * @param[in] ParseObject * A pointer to a parse object, acting as a key * body. This parameter is unused. * * @param[in,out] Kcb * A pointer to a KCB. This KCB is used by the * registry parser after hash stack and cache * lookup are done. This KCB might change if the * key is found to be cached in the cache pool. * * @param[in] Current * The current remaining key pathname. * * @param[out] Hive * A pointer to a registry hive, returned by the caller. * * @param[out] Cell * A pointer to a hive cell, returned by the caller. * * @param[out] TotalRemainingSubkeys * A pointer to a number of total remaining subkey levels, * returned by the caller. This can be 0 if no subkey levels * have been found. * * @param[out] MatchRemainSubkeyLevel * A pointer to a number of remaining subkey levels that match, * returned by the caller. This can be 0 if no matching levels * are found. * * @param[out] TotalSubkeys * A pointer to a number of total subkeys. This can be 0 if no * subkey levels are found. By definition, both MatchRemainSubkeyLevel * and TotalRemainingSubkeys are 0 as well. * * @param[in,out] OuterStackArray * A pointer to an array that lives on the caller's stack. * The expected size of the array is up to 32 elements, * which is the imposed limit by CMP_HASH_STACK_LIMIT. * This limit also corresponds to the maximum depth of * subkey levels. * * @param[out] LockedKcbs * A pointer to an array of locked KCBs, returned by the caller. * * @return * Returns STATUS_SUCCESS if all the operations have succeeded without * problems. STATUS_NAME_TOO_LONG is returned if the key pathname has * too many subkey levels (more than 32 levels deep). A failure NTSTATUS * code is returned otherwise. Refer to CmpLookInCache documentation * for more information about other returned status codes. * STATUS_UNSUCCESSFUL is returned if a KCB is referenced too many times. */ NTSTATUS NTAPI 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) { NTSTATUS Status; ULONG ConvKey; ULONG SubkeysInTotal, RemainingSubkeysInTotal, MatchRemainingSubkeys; CM_HASH_CACHE_STACK HashCacheStack[CMP_SUBKEY_LEVELS_DEPTH_LIMIT]; /* 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; /* 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; } CmpAcquireKcbLockSharedByIndex(GET_HASH_INDEX(ConvKey)); /* 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 NTAPI CmpParseKey(IN PVOID ParseObject, IN PVOID ObjectType, IN OUT PACCESS_STATE AccessState, IN KPROCESSOR_MODE AccessMode, IN ULONG Attributes, IN OUT PUNICODE_STRING CompleteName, IN OUT PUNICODE_STRING RemainingName, IN OUT PVOID Context OPTIONAL, IN PSECURITY_QUALITY_OF_SERVICE SecurityQos OPTIONAL, OUT PVOID *Object) { NTSTATUS Status; PCM_KEY_CONTROL_BLOCK Kcb, ParentKcb; PHHIVE Hive = NULL; PCM_KEY_NODE Node = NULL; HCELL_INDEX Cell = HCELL_NIL, NextCell; PHHIVE HiveToRelease = NULL; HCELL_INDEX CellToRelease = HCELL_NIL; UNICODE_STRING Current, NextName; PCM_PARSE_CONTEXT ParseContext = Context; ULONG TotalRemainingSubkeys = 0, MatchRemainSubkeyLevel = 0, TotalSubkeys = 0; ULONG LockedKcbArray[CMP_KCBS_IN_ARRAY_LIMIT]; PULONG LockedKcbs; BOOLEAN IsKeyCached = FALSE; BOOLEAN Result, Last; PAGED_CODE(); /* Loop path separators at the end */ while (RemainingName->Length && (RemainingName->Buffer[(RemainingName->Length / sizeof(WCHAR)) - 1] == OBJ_NAME_PATH_SEPARATOR)) { /* Remove path separator */ RemainingName->Length -= sizeof(WCHAR); } /* Fail if this isn't a key object */ if (ObjectType != CmpKeyObjectType) return STATUS_OBJECT_TYPE_MISMATCH; /* Copy the remaining name */ Current = *RemainingName; /* Check if this is a create */ if (!ParseContext || !ParseContext->CreateOperation) { /* It isn't, so no context */ ParseContext = NULL; } /* Grab the KCB */ Kcb = ((PCM_KEY_BODY)ParseObject)->KeyControlBlock; /* Sanity check */ ASSERT(Kcb != NULL); /* Fail if the key was marked as deleted */ if (Kcb->Delete) return STATUS_KEY_DELETED; /* Lookup in the cache */ Status = CmpBuildHashStackAndLookupCache(ParseObject, &Kcb, &Current, &Hive, &Cell, &TotalRemainingSubkeys, &MatchRemainSubkeyLevel, &TotalSubkeys, 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; /* Sanity check */ ASSERT(ParentKcb != NULL); /* Don't do anything if we're being deleted */ if (Kcb->Delete) { Status = STATUS_OBJECT_NAME_NOT_FOUND; 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) { /* Get the next name */ Result = CmpGetNextName(&Current, &NextName, &Last); Current.Buffer = NextName.Buffer; /* Validate the current name string length */ if (Current.Length + NextName.Length > MAXUSHORT) { /* too long */ Status = STATUS_NAME_TOO_LONG; goto Quickie; } Current.Length += NextName.Length; /* Validate the current name string maximum length */ if (Current.MaximumLength + NextName.MaximumLength > MAXUSHORT) { /* too long */ Status = STATUS_NAME_TOO_LONG; goto Quickie; } Current.MaximumLength += NextName.MaximumLength; /* CmpGetSymbolicLink doesn't want a lock */ CmpUnLockKcbArray(LockedKcbs); LockedKcbs = NULL; /* Parse the symlink */ if (CmpGetSymbolicLink(Hive, CompleteName, Kcb, &Current)) { /* Symlink parse succeeded */ Status = STATUS_REPARSE; } else { /* Couldn't find symlink */ Status = STATUS_OBJECT_NAME_NOT_FOUND; } /* We're done */ goto Quickie; } /* Get the key node */ Node = (PCM_KEY_NODE)HvGetCell(Hive, Cell); if (!Node) { Status = STATUS_INSUFFICIENT_RESOURCES; goto Quickie; } /* Start parsing */ Status = STATUS_NOT_IMPLEMENTED; while (TRUE) { /* Get the next component */ Result = CmpGetNextName(&Current, &NextName, &Last); if (Result && NextName.Length) { /* See if this is a sym link */ if (!(Kcb->Flags & KEY_SYM_LINK)) { /* Find the subkey */ NextCell = CmpFindSubKeyByName(Hive, Node, &NextName); if (NextCell != HCELL_NIL) { /* Get the new node */ Cell = NextCell; Node = (PCM_KEY_NODE)HvGetCell(Hive, Cell); ASSERT(Node); /* Check if this was the last key */ if (Last) { /* Is this an exit node */ if (Node->Flags & KEY_HIVE_EXIT) { /* Handle it */ CmpHandleExitNode(&Hive, &Cell, &Node, &HiveToRelease, &CellToRelease); if (!Node) { /* Fail */ Status = STATUS_INSUFFICIENT_RESOURCES; break; } } KeyCachedOpenNow: /* Do the open */ Status = CmpDoOpen(Hive, Cell, Node, AccessState, AccessMode, Attributes, ParseContext, 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, Kcb, NULL)) { /* Symlink parse failed */ Status = STATUS_OBJECT_NAME_NOT_FOUND; } } /* We are done */ break; } /* Is this an exit node */ if (Node->Flags & KEY_HIVE_EXIT) { /* Handle it */ CmpHandleExitNode(&Hive, &Cell, &Node, &HiveToRelease, &CellToRelease); if (!Node) { /* Fail */ Status = STATUS_INSUFFICIENT_RESOURCES; break; } } /* Create a KCB for this key */ Kcb = CmpCreateKeyControlBlock(Hive, Cell, Node, ParentKcb, CMP_LOCK_HASHES_FOR_KCB, &NextName); if (!Kcb) { /* Fail */ Status = STATUS_INSUFFICIENT_RESOURCES; break; } /* Dereference the parent and set the new one */ CmpDereferenceKeyControlBlockWithLock(ParentKcb, FALSE); ParentKcb = Kcb; } else { /* Check if this was the last key for a create */ if (Last && ParseContext) { /* Check if we're doing a link node */ if (ParseContext->CreateLink) { /* The only thing we should see */ Status = CmpCreateLinkNode(Hive, Cell, AccessState, NextName, AccessMode, Attributes, ParseContext, ParentKcb, LockedKcbs, Object); } else if (Hive == &CmiVolatileHive->Hive && CmpNoVolatileCreates) { /* Creating keys in the master hive is not allowed */ Status = STATUS_INVALID_PARAMETER; } else { /* Do the create */ Status = CmpDoCreate(Hive, Cell, AccessState, &NextName, AccessMode, ParseContext, ParentKcb, Object); } /* Check for reparse (in this case, someone beat us) */ if (Status == STATUS_REPARSE) break; /* Update disposition */ ParseContext->Disposition = REG_CREATED_NEW_KEY; break; } else { /* Key not found */ Status = STATUS_OBJECT_NAME_NOT_FOUND; break; } } } else { /* Save the next name */ Current.Buffer = NextName.Buffer; /* Validate the current name string length */ if (Current.Length + NextName.Length > MAXUSHORT) { /* too long */ Status = STATUS_NAME_TOO_LONG; break; } Current.Length += NextName.Length; /* Validate the current name string maximum length */ if (Current.MaximumLength + NextName.MaximumLength > MAXUSHORT) { /* too long */ Status = STATUS_NAME_TOO_LONG; break; } Current.MaximumLength += NextName.MaximumLength; /* CmpGetSymbolicLink doesn't want a lock */ CmpUnLockKcbArray(LockedKcbs); LockedKcbs = NULL; /* Parse the symlink */ if (CmpGetSymbolicLink(Hive, CompleteName, Kcb, &Current)) { /* Symlink parse succeeded */ Status = STATUS_REPARSE; } else { /* Couldn't find symlink */ Status = STATUS_OBJECT_NAME_NOT_FOUND; } /* We're done */ break; } } else if (Result && Last) { /* Opening the root. Is this an exit node? */ if (Node->Flags & KEY_HIVE_EXIT) { /* Handle it */ CmpHandleExitNode(&Hive, &Cell, &Node, &HiveToRelease, &CellToRelease); if (!Node) { /* Fail */ Status = STATUS_INSUFFICIENT_RESOURCES; break; } } /* Do the open */ Status = CmpDoOpen(Hive, Cell, Node, AccessState, AccessMode, Attributes, ParseContext, CMP_OPEN_KCB_NO_CREATE, &Kcb, LockedKcbs, &NextName, Object); if (Status == STATUS_REPARSE) { /* Nothing to do */ } /* We're done */ break; } else { /* Bogus */ Status = STATUS_INVALID_PARAMETER; break; } } Quickie: /* Unlock all the KCBs */ if (LockedKcbs != NULL) { CmpUnLockKcbArray(LockedKcbs); } /* Dereference the parent if it exists */ if (ParentKcb) CmpDereferenceKeyControlBlock(ParentKcb); /* Unlock the registry */ CmpUnlockRegistry(); return Status; }