You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
3350 lines
97 KiB
3350 lines
97 KiB
/*++
|
|
|
|
Copyright (c) 1989 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
obdir.c
|
|
|
|
Abstract:
|
|
|
|
Directory Object routines
|
|
|
|
Author:
|
|
|
|
Steve Wood (stevewo) 31-Mar-1989
|
|
|
|
Revision History:
|
|
|
|
--*/
|
|
|
|
#include "obp.h"
|
|
#include <stdio.h>
|
|
|
|
POBJECT_DIRECTORY
|
|
ObpGetShadowDirectory(
|
|
POBJECT_DIRECTORY Dir
|
|
);
|
|
|
|
//
|
|
// Defined in ntos\se\rmlogon.c
|
|
// private kernel function to obtain the LUID device map's directory
|
|
// object
|
|
// returns a kernel handle
|
|
//
|
|
NTSTATUS
|
|
SeGetLogonIdDeviceMap(
|
|
IN PLUID pLogonId,
|
|
OUT PDEVICE_MAP* ppDevMap
|
|
);
|
|
|
|
NTSTATUS
|
|
ObpSetCurrentProcessDeviceMap(
|
|
);
|
|
|
|
POBJECT_HEADER_NAME_INFO
|
|
ObpTryReferenceNameInfoExclusive(
|
|
IN POBJECT_HEADER ObjectHeader
|
|
);
|
|
|
|
VOID
|
|
ObpReleaseExclusiveNameLock(
|
|
IN POBJECT_HEADER ObjectHeader
|
|
);
|
|
|
|
POBJECT_DIRECTORY_ENTRY
|
|
ObpUnlinkDirectoryEntry (
|
|
IN POBJECT_DIRECTORY Directory,
|
|
IN ULONG HashIndex
|
|
);
|
|
|
|
VOID
|
|
ObpLinkDirectoryEntry (
|
|
IN POBJECT_DIRECTORY Directory,
|
|
IN ULONG HashIndex,
|
|
IN POBJECT_DIRECTORY_ENTRY NewDirectoryEntry
|
|
);
|
|
|
|
VOID
|
|
ObpReleaseLookupContextObject (
|
|
IN POBP_LOOKUP_CONTEXT LookupContext
|
|
);
|
|
|
|
#if defined(ALLOC_PRAGMA)
|
|
#pragma alloc_text(PAGE,NtCreateDirectoryObject)
|
|
#pragma alloc_text(PAGE,NtOpenDirectoryObject)
|
|
#pragma alloc_text(PAGE,NtQueryDirectoryObject)
|
|
#pragma alloc_text(PAGE,ObpLookupDirectoryEntry)
|
|
#pragma alloc_text(PAGE,ObpInsertDirectoryEntry)
|
|
#pragma alloc_text(PAGE,ObpDeleteDirectoryEntry)
|
|
#pragma alloc_text(PAGE,ObpLookupObjectName)
|
|
#pragma alloc_text(PAGE,NtMakePermanentObject)
|
|
|
|
#ifdef OBP_PAGEDPOOL_NAMESPACE
|
|
#pragma alloc_text(PAGE,ObpGetShadowDirectory)
|
|
#pragma alloc_text(PAGE,ObpSetCurrentProcessDeviceMap)
|
|
#pragma alloc_text(PAGE,ObpReferenceDeviceMap)
|
|
#pragma alloc_text(PAGE,ObfDereferenceDeviceMap)
|
|
#pragma alloc_text(PAGE,ObSwapObjectNames)
|
|
#pragma alloc_text(PAGE,ObpReleaseLookupContextObject)
|
|
#pragma alloc_text(PAGE,ObpLinkDirectoryEntry)
|
|
#pragma alloc_text(PAGE,ObpUnlinkDirectoryEntry)
|
|
#pragma alloc_text(PAGE,ObpReleaseExclusiveNameLock)
|
|
#pragma alloc_text(PAGE,ObpTryReferenceNameInfoExclusive)
|
|
|
|
#endif // OBP_PAGEDPOOL_NAMESPACE
|
|
|
|
#endif
|
|
|
|
//
|
|
// Global Object manager flags to control the case sensitivity lookup
|
|
// and the LUID devicemap lookup
|
|
//
|
|
|
|
ULONG ObpCaseInsensitive = 1;
|
|
extern ULONG ObpLUIDDeviceMapsEnabled;
|
|
|
|
WCHAR ObpUnsecureGlobalNamesBuffer[128] = { 0 };
|
|
ULONG ObpUnsecureGlobalNamesLength = sizeof(ObpUnsecureGlobalNamesBuffer);
|
|
|
|
BOOLEAN
|
|
ObpIsUnsecureName(
|
|
IN PUNICODE_STRING ObjectName,
|
|
IN BOOLEAN CaseInsensitive
|
|
)
|
|
{
|
|
PWCHAR CrtName;
|
|
UNICODE_STRING UnsecurePrefix;
|
|
|
|
if (ObpUnsecureGlobalNamesBuffer[0] == 0) {
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
CrtName = ObpUnsecureGlobalNamesBuffer;
|
|
|
|
do {
|
|
|
|
RtlInitUnicodeString(&UnsecurePrefix, CrtName);
|
|
|
|
if (UnsecurePrefix.Length) {
|
|
|
|
if (RtlPrefixUnicodeString( &UnsecurePrefix, ObjectName, CaseInsensitive)) {
|
|
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
CrtName += (UnsecurePrefix.Length + sizeof(UNICODE_NULL)) / sizeof(WCHAR);
|
|
|
|
} while ( UnsecurePrefix.Length );
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
NTSTATUS
|
|
NtCreateDirectoryObject (
|
|
OUT PHANDLE DirectoryHandle,
|
|
IN ACCESS_MASK DesiredAccess,
|
|
IN POBJECT_ATTRIBUTES ObjectAttributes
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine creates a new directory object according to user
|
|
specified object attributes
|
|
|
|
Arguments:
|
|
|
|
DirectoryHandle - Receives the handle for the newly created
|
|
directory object
|
|
|
|
DesiredAccess - Supplies the access being requested for this
|
|
new directory object
|
|
|
|
ObjectAttributes - Supplies caller specified attributes for new
|
|
directory object
|
|
|
|
Return Value:
|
|
|
|
An appropriate status value.
|
|
|
|
--*/
|
|
|
|
{
|
|
POBJECT_DIRECTORY Directory;
|
|
HANDLE Handle;
|
|
KPROCESSOR_MODE PreviousMode;
|
|
NTSTATUS Status;
|
|
|
|
PAGED_CODE();
|
|
|
|
ObpValidateIrql( "NtCreateDirectoryObject" );
|
|
|
|
//
|
|
// Get previous processor mode and probe output arguments if necessary.
|
|
//
|
|
|
|
PreviousMode = KeGetPreviousMode();
|
|
|
|
if (PreviousMode != KernelMode) {
|
|
|
|
try {
|
|
|
|
ProbeForWriteHandle( DirectoryHandle );
|
|
|
|
} except( EXCEPTION_EXECUTE_HANDLER ) {
|
|
|
|
return( GetExceptionCode() );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Allocate and initialize a new Directory Object. We don't need
|
|
// to specify a parse context or charge any quota. The size of
|
|
// the object body is simply a directory object. This call gets
|
|
// us a new referenced object.
|
|
//
|
|
|
|
Status = ObCreateObject( PreviousMode,
|
|
ObpDirectoryObjectType,
|
|
ObjectAttributes,
|
|
PreviousMode,
|
|
NULL,
|
|
sizeof( *Directory ),
|
|
0,
|
|
0,
|
|
(PVOID *)&Directory );
|
|
|
|
if (!NT_SUCCESS( Status )) {
|
|
|
|
return( Status );
|
|
}
|
|
|
|
RtlZeroMemory( Directory, sizeof( *Directory ) );
|
|
|
|
ExInitializePushLock( &Directory->Lock );
|
|
Directory->SessionId = OBJ_INVALID_SESSION_ID;
|
|
|
|
//
|
|
// Insert directory object in the current processes handle table,
|
|
// set directory handle value and return status.
|
|
//
|
|
// ObInsertObject will delete the object in the case of failure
|
|
//
|
|
|
|
Status = ObInsertObject( Directory,
|
|
NULL,
|
|
DesiredAccess,
|
|
0,
|
|
(PVOID *)NULL,
|
|
&Handle );
|
|
|
|
try {
|
|
|
|
*DirectoryHandle = Handle;
|
|
|
|
} except( EXCEPTION_EXECUTE_HANDLER ) {
|
|
|
|
//
|
|
// Fall through, since we do not want to undo what we have done.
|
|
//
|
|
}
|
|
|
|
return( Status );
|
|
}
|
|
|
|
|
|
NTSTATUS
|
|
NtOpenDirectoryObject (
|
|
OUT PHANDLE DirectoryHandle,
|
|
IN ACCESS_MASK DesiredAccess,
|
|
IN POBJECT_ATTRIBUTES ObjectAttributes
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine opens an existing directory object.
|
|
|
|
Arguments:
|
|
|
|
DirectoryHandle - Receives the handle for the newly opened directory
|
|
object
|
|
|
|
DesiredAccess - Supplies the access being requested for this
|
|
directory object
|
|
|
|
ObjectAttributes - Supplies caller specified attributes for the
|
|
directory object
|
|
|
|
Return Value:
|
|
|
|
An appropriate status value.
|
|
|
|
--*/
|
|
|
|
{
|
|
KPROCESSOR_MODE PreviousMode;
|
|
NTSTATUS Status;
|
|
HANDLE Handle;
|
|
|
|
PAGED_CODE();
|
|
|
|
ObpValidateIrql( "NtOpenDirectoryObject" );
|
|
|
|
//
|
|
// Get previous processor mode and probe output arguments if necessary.
|
|
//
|
|
|
|
PreviousMode = KeGetPreviousMode();
|
|
|
|
if (PreviousMode != KernelMode) {
|
|
|
|
try {
|
|
|
|
ProbeForWriteHandle( DirectoryHandle );
|
|
|
|
} except( EXCEPTION_EXECUTE_HANDLER ) {
|
|
|
|
return( GetExceptionCode() );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Open handle to the directory object with the specified desired access,
|
|
// set directory handle value, and return service completion status.
|
|
//
|
|
|
|
Status = ObOpenObjectByName( ObjectAttributes,
|
|
ObpDirectoryObjectType,
|
|
PreviousMode,
|
|
NULL,
|
|
DesiredAccess,
|
|
NULL,
|
|
&Handle );
|
|
|
|
try {
|
|
|
|
*DirectoryHandle = Handle;
|
|
|
|
} except( EXCEPTION_EXECUTE_HANDLER ) {
|
|
|
|
//
|
|
// Fall through, since we do not want to undo what we have done.
|
|
//
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
NTSTATUS
|
|
NtQueryDirectoryObject (
|
|
IN HANDLE DirectoryHandle,
|
|
OUT PVOID Buffer,
|
|
IN ULONG Length,
|
|
IN BOOLEAN ReturnSingleEntry,
|
|
IN BOOLEAN RestartScan,
|
|
IN OUT PULONG Context,
|
|
OUT PULONG ReturnLength OPTIONAL
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function returns information regarding a specified object
|
|
directory.
|
|
|
|
Arguments:
|
|
|
|
DirectoryHandle - Supplies a handle to the directory being queried
|
|
|
|
Buffer - Supplies the output buffer to receive the directory
|
|
information. On return this contains one or more OBJECT DIRECTORY
|
|
INFORMATION structures, the last one being null. And then this is
|
|
followed by the string names for the directory entries.
|
|
|
|
Length - Supplies the length, in bytes, of the user supplied output
|
|
buffer
|
|
|
|
ReturnSingleEntry - Indicates if this routine should just return
|
|
one entry in the directory
|
|
|
|
RestartScan - Indicates if we are to restart the scan or continue
|
|
relative to the enumeration context passed in as the next
|
|
parameter
|
|
|
|
Context - Supplies an enumeration context that must be resupplied
|
|
to this routine on subsequent calls to keep the enumeration
|
|
in sync
|
|
|
|
ReturnLength - Optionally receives the length, in bytes, that this
|
|
routine has stuffed into the output buffer
|
|
|
|
Return Value:
|
|
|
|
An appropriate status value.
|
|
|
|
--*/
|
|
|
|
{
|
|
POBJECT_DIRECTORY Directory;
|
|
POBJECT_DIRECTORY_ENTRY DirectoryEntry;
|
|
POBJECT_HEADER ObjectHeader;
|
|
POBJECT_HEADER_NAME_INFO NameInfo;
|
|
UNICODE_STRING ObjectName;
|
|
POBJECT_DIRECTORY_INFORMATION DirInfo;
|
|
PWCH NameBuffer;
|
|
KPROCESSOR_MODE PreviousMode;
|
|
NTSTATUS Status;
|
|
ULONG Bucket, EntryNumber, CapturedContext;
|
|
ULONG TotalLengthNeeded, LengthNeeded, EntriesFound;
|
|
PCHAR TempBuffer;
|
|
OBP_LOOKUP_CONTEXT LookupContext;
|
|
|
|
PAGED_CODE();
|
|
|
|
ObpValidateIrql( "NtQueryDirectoryObject" );
|
|
|
|
ObpInitializeLookupContext( &LookupContext );
|
|
|
|
//
|
|
// Get previous processor mode and probe output arguments if necessary.
|
|
//
|
|
|
|
PreviousMode = KeGetPreviousMode();
|
|
|
|
if (PreviousMode != KernelMode) {
|
|
|
|
try {
|
|
|
|
ProbeForWrite( Buffer, Length, sizeof( WCHAR ) );
|
|
ProbeForWriteUlong( Context );
|
|
|
|
if (ARGUMENT_PRESENT( ReturnLength )) {
|
|
|
|
ProbeForWriteUlong( ReturnLength );
|
|
}
|
|
|
|
if (RestartScan) {
|
|
|
|
CapturedContext = 0;
|
|
|
|
} else {
|
|
|
|
CapturedContext = *Context;
|
|
}
|
|
|
|
} except( EXCEPTION_EXECUTE_HANDLER ) {
|
|
|
|
return( GetExceptionCode() );
|
|
}
|
|
|
|
} else {
|
|
|
|
if (RestartScan) {
|
|
|
|
CapturedContext = 0;
|
|
|
|
} else {
|
|
|
|
CapturedContext = *Context;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Test for 64 bit if Length + sizeof( OBJECT_DIRECTORY_INFORMATION ) is less than Length
|
|
// Return STATUS_INVALID_PARAMETER if there is an overflow
|
|
//
|
|
|
|
if (ObpIsOverflow( Length, sizeof( OBJECT_DIRECTORY_INFORMATION ))) {
|
|
|
|
return( STATUS_INVALID_PARAMETER );
|
|
}
|
|
|
|
//
|
|
// Allocate space for a temporary work buffer, make sure we got it,
|
|
// and then zero it out. Make sure the buffer is large enough to
|
|
// hold at least one dir info record. This will make the logic work
|
|
// better when the a bad length is passed in.
|
|
//
|
|
|
|
TempBuffer = ExAllocatePoolWithQuotaTag( PagedPool | POOL_QUOTA_FAIL_INSTEAD_OF_RAISE,
|
|
Length + sizeof( OBJECT_DIRECTORY_INFORMATION ),
|
|
'mNbO' );
|
|
|
|
if (TempBuffer == NULL) {
|
|
|
|
return( STATUS_INSUFFICIENT_RESOURCES );
|
|
}
|
|
|
|
RtlZeroMemory( TempBuffer, Length );
|
|
|
|
//
|
|
// Reference the directory object
|
|
//
|
|
|
|
Status = ObReferenceObjectByHandle( DirectoryHandle,
|
|
DIRECTORY_QUERY,
|
|
ObpDirectoryObjectType,
|
|
PreviousMode,
|
|
(PVOID *)&Directory,
|
|
NULL );
|
|
|
|
if (!NT_SUCCESS( Status )) {
|
|
|
|
ExFreePool( TempBuffer );
|
|
|
|
return( Status );
|
|
}
|
|
|
|
//
|
|
// Lock down the directory structures for the life of this
|
|
// procedure
|
|
//
|
|
|
|
ObpLockDirectoryShared(Directory, &LookupContext);
|
|
|
|
//
|
|
// DirInfo is used to march through the output buffer filling
|
|
// in directory information. We'll start off by making sure
|
|
// there is room for a NULL entry at end.
|
|
//
|
|
|
|
DirInfo = (POBJECT_DIRECTORY_INFORMATION)TempBuffer;
|
|
|
|
TotalLengthNeeded = sizeof( *DirInfo );
|
|
|
|
//
|
|
// Keep track of the number of entries found and actual
|
|
// entry that we are processing
|
|
//
|
|
|
|
EntryNumber = 0;
|
|
EntriesFound = 0;
|
|
|
|
//
|
|
// By default we'll say there are no more entries until the
|
|
// following loop put in some data
|
|
//
|
|
|
|
Status = STATUS_NO_MORE_ENTRIES;
|
|
|
|
//
|
|
// Our outer loop processes each hash bucket in the directory object
|
|
//
|
|
|
|
for (Bucket=0; Bucket<NUMBER_HASH_BUCKETS; Bucket++) {
|
|
|
|
DirectoryEntry = Directory->HashBuckets[ Bucket ];
|
|
|
|
//
|
|
// For this hash bucket we'll zip through its list of entries.
|
|
// This is a singly linked list so when the next pointer is null
|
|
// (i.e., false) we at the end of the hash list
|
|
//
|
|
|
|
while (DirectoryEntry) {
|
|
|
|
//
|
|
// The captured context is simply the entry count unless the
|
|
// user specified otherwise we start at zero, which means
|
|
// the first entry is always returned in the enumeration.
|
|
// If we have an match based on the entry index then we
|
|
// process this entry. We bump the captured context further
|
|
// done in the code.
|
|
//
|
|
|
|
if (CapturedContext == EntryNumber++) {
|
|
|
|
//
|
|
// For this directory entry we'll get a pointer to the
|
|
// object body and see if it has an object name. If it
|
|
// doesn't have a name then we'll give it an empty name.
|
|
//
|
|
|
|
ObjectHeader = OBJECT_TO_OBJECT_HEADER( DirectoryEntry->Object );
|
|
NameInfo = OBJECT_HEADER_TO_NAME_INFO( ObjectHeader );
|
|
|
|
if (NameInfo != NULL) {
|
|
|
|
ObjectName = NameInfo->Name;
|
|
|
|
} else {
|
|
|
|
RtlInitUnicodeString( &ObjectName, NULL );
|
|
}
|
|
|
|
//
|
|
// Now compute the length needed for this entry. This would
|
|
// be the size of the object directory information record,
|
|
// plus the size of the object name and object type name both
|
|
// null terminated.
|
|
//
|
|
|
|
LengthNeeded = sizeof( *DirInfo ) +
|
|
ObjectName.Length + sizeof( UNICODE_NULL ) +
|
|
ObjectHeader->Type->Name.Length + sizeof( UNICODE_NULL );
|
|
|
|
//
|
|
// If there isn't enough room then take the following error
|
|
// path. If the user wanted a single entry then tell the
|
|
// caller what length is really needed and say the buffer was
|
|
// too small. Otherwise the user wanted multiple entries,
|
|
// so we'll just say there are more entries in the directory.
|
|
// In both cases we drop down the entry number because we
|
|
// weren't able to fit it in on this call
|
|
//
|
|
|
|
if ((TotalLengthNeeded + LengthNeeded) > Length) {
|
|
|
|
if (ReturnSingleEntry) {
|
|
|
|
TotalLengthNeeded += LengthNeeded;
|
|
|
|
Status = STATUS_BUFFER_TOO_SMALL;
|
|
|
|
} else {
|
|
|
|
Status = STATUS_MORE_ENTRIES;
|
|
}
|
|
|
|
EntryNumber -= 1;
|
|
goto querydone;
|
|
}
|
|
|
|
//
|
|
// The information will fit in the buffer. So now fill
|
|
// in the output buffer. We temporarily put in pointers
|
|
// to the name buffer as stored in the object and object
|
|
// type. We copy the data buffer to the user buffer
|
|
// right before we return to the caller
|
|
//
|
|
|
|
try {
|
|
|
|
DirInfo->Name.Length = ObjectName.Length;
|
|
DirInfo->Name.MaximumLength = (USHORT)(ObjectName.Length+sizeof( UNICODE_NULL ));
|
|
DirInfo->Name.Buffer = ObjectName.Buffer;
|
|
|
|
DirInfo->TypeName.Length = ObjectHeader->Type->Name.Length;
|
|
DirInfo->TypeName.MaximumLength = (USHORT)(ObjectHeader->Type->Name.Length+sizeof( UNICODE_NULL ));
|
|
DirInfo->TypeName.Buffer = ObjectHeader->Type->Name.Buffer;
|
|
|
|
Status = STATUS_SUCCESS;
|
|
|
|
} except( EXCEPTION_EXECUTE_HANDLER ) {
|
|
|
|
Status = GetExceptionCode();
|
|
}
|
|
|
|
if (!NT_SUCCESS( Status )) {
|
|
|
|
goto querydone;
|
|
}
|
|
|
|
//
|
|
// Update the total number of bytes needed in this query.
|
|
// Push the dir info pointer to the next output location,
|
|
// and indicate how many entries we've processed
|
|
//
|
|
//
|
|
|
|
TotalLengthNeeded += LengthNeeded;
|
|
|
|
DirInfo++;
|
|
EntriesFound++;
|
|
|
|
//
|
|
// If we are to return only one entry then move on to the
|
|
// post processing phase, otherwise indicate that we're
|
|
// processing the next entry and go back to the top of
|
|
// the inner loop
|
|
//
|
|
|
|
if (ReturnSingleEntry) {
|
|
|
|
goto querydone;
|
|
|
|
} else {
|
|
|
|
//
|
|
// Bump the captured context by one entry.
|
|
//
|
|
|
|
CapturedContext++;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Get the next directory entry from the singly linked hash
|
|
// bucket chain
|
|
//
|
|
|
|
DirectoryEntry = DirectoryEntry->ChainLink;
|
|
}
|
|
}
|
|
|
|
//
|
|
// At this point we've processed the directory entries and the first
|
|
// part of the output buffer now contains a bunch of object directory
|
|
// information records, but the pointers in them refer to the wrong
|
|
// copies. So now we have some fixup to do.
|
|
//
|
|
|
|
querydone:
|
|
|
|
try {
|
|
|
|
//
|
|
// We'll only do this post processing if we've been successful
|
|
// so far. Note that this means we could be returning in the
|
|
// user's output buffer system address that are meaningless, but
|
|
// then getting back an error status should tell the caller to
|
|
// forget about everything in the output buffer. Given back
|
|
// a system address also isn't harmful because there is nothing
|
|
// that the user can really do with it.
|
|
//
|
|
|
|
if (NT_SUCCESS( Status )) {
|
|
|
|
//
|
|
// Null terminate the string of object directory information
|
|
// records and point to where the actual names will go
|
|
//
|
|
|
|
RtlZeroMemory( DirInfo, sizeof( *DirInfo ));
|
|
|
|
DirInfo++;
|
|
|
|
NameBuffer = (PWCH)DirInfo;
|
|
|
|
//
|
|
// Now for every entry that we've put in the output buffer
|
|
// DirInfo will point to the entry and EntriesFound kept the
|
|
// count. Note that we are guaranteed space because of
|
|
// the math we did earlier in computing TotalLengthNeeded.
|
|
//
|
|
|
|
DirInfo = (POBJECT_DIRECTORY_INFORMATION)TempBuffer;
|
|
|
|
while (EntriesFound--) {
|
|
|
|
//
|
|
// Copy over the object name, set the dir info pointer into
|
|
// the user's buffer, then null terminate the string. Note
|
|
// that we are really copying the data into our temp buffer
|
|
// but the pointer fix up is for the user's buffer which
|
|
// we'll copy into right after this loop.
|
|
//
|
|
|
|
RtlCopyMemory( NameBuffer,
|
|
DirInfo->Name.Buffer,
|
|
DirInfo->Name.Length );
|
|
|
|
DirInfo->Name.Buffer = (PVOID)((ULONG_PTR)Buffer + ((ULONG_PTR)NameBuffer - (ULONG_PTR)TempBuffer));
|
|
NameBuffer = (PWCH)((ULONG_PTR)NameBuffer + DirInfo->Name.Length);
|
|
*NameBuffer++ = UNICODE_NULL;
|
|
|
|
//
|
|
// Do the same copy with the object type name
|
|
//
|
|
|
|
RtlCopyMemory( NameBuffer,
|
|
DirInfo->TypeName.Buffer,
|
|
DirInfo->TypeName.Length );
|
|
|
|
DirInfo->TypeName.Buffer = (PVOID)((ULONG_PTR)Buffer + ((ULONG_PTR)NameBuffer - (ULONG_PTR)TempBuffer));
|
|
NameBuffer = (PWCH)((ULONG_PTR)NameBuffer + DirInfo->TypeName.Length);
|
|
*NameBuffer++ = UNICODE_NULL;
|
|
|
|
//
|
|
// Move on to the next dir info record
|
|
//
|
|
|
|
DirInfo++;
|
|
}
|
|
|
|
//
|
|
// Set the enumeration context to the entry number of the next
|
|
// entry to return.
|
|
//
|
|
|
|
*Context = EntryNumber;
|
|
}
|
|
|
|
//
|
|
// Copy over the results from our temp buffer to the users buffer.
|
|
// But adjust the amount copied just in case the total length needed
|
|
// exceeds the length we allocated.
|
|
//
|
|
|
|
RtlCopyMemory( Buffer,
|
|
TempBuffer,
|
|
(TotalLengthNeeded <= Length ? TotalLengthNeeded : Length) );
|
|
|
|
//
|
|
// In all cases we'll tell the caller how much space if really needed
|
|
// provided the user asked for this information
|
|
//
|
|
|
|
if (ARGUMENT_PRESENT( ReturnLength )) {
|
|
|
|
*ReturnLength = TotalLengthNeeded;
|
|
}
|
|
|
|
} except( EXCEPTION_EXECUTE_HANDLER ) {
|
|
|
|
//
|
|
// Fall through, since we do not want to undo what we have done.
|
|
//
|
|
}
|
|
|
|
//
|
|
// Unlock the directroy structures, dereference the directory object,
|
|
// free up our temp buffer, and return to our caller
|
|
//
|
|
|
|
ObpUnlockDirectory( Directory, &LookupContext);
|
|
|
|
ObDereferenceObject( Directory );
|
|
|
|
ExFreePool( TempBuffer );
|
|
|
|
return( Status );
|
|
}
|
|
|
|
|
|
PVOID
|
|
ObpLookupDirectoryEntry (
|
|
IN POBJECT_DIRECTORY Directory,
|
|
IN PUNICODE_STRING Name,
|
|
IN ULONG Attributes,
|
|
IN BOOLEAN SearchShadow,
|
|
OUT POBP_LOOKUP_CONTEXT LookupContext
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine will lookup a single directory entry in a given directory.
|
|
If it founds an object into that directory with the give name,
|
|
that object will be referenced and the name will be referenced too, in order
|
|
to prevent them going away when the directory is unlocked.
|
|
The referenced object is saved in the LookupContext, and the references will be released
|
|
at the next lookup, or when ObpReleaseLookupContext is called.
|
|
|
|
Arguments:
|
|
|
|
Directory - Supplies the directory being searched
|
|
|
|
Name - Supplies the name of entry we're looking for
|
|
|
|
Attributes - Indicates if the lookup should be case insensitive
|
|
or not
|
|
|
|
SearchShadow - If TRUE, and the object name is not found in the current directory,
|
|
it will search the object into the shadow directory.
|
|
|
|
LookupContext - The lookup context for this call. This structure must be initialized
|
|
before calling first time ObpLookupDirectoryEntry.
|
|
|
|
Return Value:
|
|
|
|
Returns a pointer to the corresponding object body if found and NULL
|
|
otherwise.
|
|
--*/
|
|
|
|
{
|
|
POBJECT_DIRECTORY_ENTRY *HeadDirectoryEntry;
|
|
POBJECT_DIRECTORY_ENTRY DirectoryEntry;
|
|
POBJECT_HEADER ObjectHeader;
|
|
POBJECT_HEADER_NAME_INFO NameInfo;
|
|
PWCH Buffer;
|
|
WCHAR Wchar;
|
|
ULONG HashIndex;
|
|
ULONG WcharLength;
|
|
BOOLEAN CaseInSensitive;
|
|
POBJECT_DIRECTORY_ENTRY *LookupBucket;
|
|
PVOID Object = NULL;
|
|
|
|
PAGED_CODE();
|
|
|
|
if (ObpLUIDDeviceMapsEnabled == 0) {
|
|
|
|
SearchShadow = FALSE; // Disable global devmap search
|
|
}
|
|
|
|
//
|
|
// The caller needs to specify both a directory and a name otherwise
|
|
// we can't process the request
|
|
//
|
|
|
|
if (!Directory || !Name) {
|
|
|
|
goto UPDATECONTEXT;
|
|
}
|
|
|
|
//
|
|
// Set a local variable to tell us if the search is case sensitive
|
|
//
|
|
|
|
if (Attributes & OBJ_CASE_INSENSITIVE) {
|
|
|
|
CaseInSensitive = TRUE;
|
|
|
|
} else {
|
|
|
|
CaseInSensitive = FALSE;
|
|
}
|
|
|
|
//
|
|
// Establish our local pointer to the input name buffer and get the
|
|
// number of unicode characters in the input name. Also make sure
|
|
// the caller gave us a non null name
|
|
//
|
|
|
|
Buffer = Name->Buffer;
|
|
WcharLength = Name->Length / sizeof( *Buffer );
|
|
|
|
if (!WcharLength || !Buffer) {
|
|
|
|
goto UPDATECONTEXT;
|
|
}
|
|
|
|
//
|
|
// Compute the address of the head of the bucket chain for this name.
|
|
//
|
|
|
|
HashIndex = 0;
|
|
while (WcharLength--) {
|
|
|
|
Wchar = *Buffer++;
|
|
HashIndex += (HashIndex << 1) + (HashIndex >> 1);
|
|
|
|
if (Wchar < 'a') {
|
|
|
|
HashIndex += Wchar;
|
|
|
|
} else if (Wchar > 'z') {
|
|
|
|
HashIndex += RtlUpcaseUnicodeChar( Wchar );
|
|
|
|
} else {
|
|
|
|
HashIndex += (Wchar - ('a'-'A'));
|
|
}
|
|
}
|
|
|
|
HashIndex %= NUMBER_HASH_BUCKETS;
|
|
|
|
LookupContext->HashIndex = (USHORT)HashIndex;
|
|
|
|
while (1) {
|
|
|
|
HeadDirectoryEntry = (POBJECT_DIRECTORY_ENTRY *)&Directory->HashBuckets[ HashIndex ];
|
|
|
|
LookupBucket = HeadDirectoryEntry;
|
|
|
|
//
|
|
// Lock the directory for read access, if the context was not previously locked
|
|
// exclusively
|
|
//
|
|
|
|
if (!LookupContext->DirectoryLocked) {
|
|
|
|
ObpLockDirectoryShared( Directory, LookupContext);
|
|
}
|
|
|
|
//
|
|
// Walk the chain of directory entries for this hash bucket, looking
|
|
// for either a match, or the insertion point if no match in the chain.
|
|
//
|
|
|
|
while ((DirectoryEntry = *HeadDirectoryEntry) != NULL) {
|
|
|
|
//
|
|
// Get the object header and name from the object body
|
|
//
|
|
// This function assumes the name must exist, otherwise it
|
|
// wouldn't be in a directory
|
|
//
|
|
|
|
ObjectHeader = OBJECT_TO_OBJECT_HEADER( DirectoryEntry->Object );
|
|
NameInfo = OBJECT_HEADER_TO_NAME_INFO( ObjectHeader );
|
|
|
|
//
|
|
// Compare strings using appropriate function.
|
|
//
|
|
|
|
if ((Name->Length == NameInfo->Name.Length) &&
|
|
RtlEqualUnicodeString( Name,
|
|
&NameInfo->Name,
|
|
CaseInSensitive )) {
|
|
|
|
//
|
|
// If name matches, then exit loop with DirectoryEntry
|
|
// pointing to matching entry.
|
|
//
|
|
|
|
break;
|
|
}
|
|
|
|
HeadDirectoryEntry = &DirectoryEntry->ChainLink;
|
|
}
|
|
|
|
//
|
|
// At this point, there are two possiblilities:
|
|
//
|
|
// - we found an entry that matched and DirectoryEntry points to that
|
|
// entry. Update the bucket chain so that the entry found is at the
|
|
// head of the bucket chain. This is so the ObpDeleteDirectoryEntry
|
|
// and ObpInsertDirectoryEntry functions will work. Also repeated
|
|
// lookups of the same name will succeed quickly.
|
|
//
|
|
// - we did not find an entry that matched and DirectoryEntry is NULL.
|
|
//
|
|
|
|
if (DirectoryEntry) {
|
|
|
|
//
|
|
// The following convoluted piece of code moves a directory entry
|
|
// we've found to the front of the hash list.
|
|
//
|
|
|
|
if (HeadDirectoryEntry != LookupBucket) {
|
|
|
|
if ( LookupContext->DirectoryLocked
|
|
||
|
|
ExTryConvertPushLockSharedToExclusive(&Directory->Lock)) {
|
|
|
|
*HeadDirectoryEntry = DirectoryEntry->ChainLink;
|
|
DirectoryEntry->ChainLink = *LookupBucket;
|
|
*LookupBucket = DirectoryEntry;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Now return the object to our caller
|
|
//
|
|
|
|
Object = DirectoryEntry->Object;
|
|
|
|
goto UPDATECONTEXT;
|
|
|
|
} else {
|
|
|
|
if (!LookupContext->DirectoryLocked) {
|
|
|
|
ObpUnlockDirectory( Directory, LookupContext );
|
|
}
|
|
|
|
//
|
|
// If this is a directory with a device map then search the second directory for an entry.
|
|
//
|
|
|
|
if (SearchShadow && Directory->DeviceMap != NULL) {
|
|
POBJECT_DIRECTORY NewDirectory;
|
|
|
|
NewDirectory = ObpGetShadowDirectory (Directory);
|
|
if (NewDirectory != NULL) {
|
|
Directory = NewDirectory;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
goto UPDATECONTEXT;
|
|
}
|
|
}
|
|
|
|
UPDATECONTEXT:
|
|
|
|
if (Object) {
|
|
|
|
//
|
|
// Reference the name to keep it's directory alive and the object
|
|
// before returning from the lookup
|
|
//
|
|
|
|
ObpReferenceNameInfo( OBJECT_TO_OBJECT_HEADER(Object) );
|
|
ObReferenceObject( Object );
|
|
|
|
//
|
|
// We can safetly drop the lock now
|
|
//
|
|
|
|
if (!LookupContext->DirectoryLocked) {
|
|
|
|
ObpUnlockDirectory( Directory, LookupContext );
|
|
}
|
|
}
|
|
|
|
//
|
|
// If we have a previously referenced object we can dereference it
|
|
//
|
|
|
|
if (LookupContext->Object) {
|
|
|
|
POBJECT_HEADER_NAME_INFO PreviousNameInfo;
|
|
|
|
PreviousNameInfo = OBJECT_HEADER_TO_NAME_INFO(OBJECT_TO_OBJECT_HEADER(LookupContext->Object));
|
|
|
|
ObpDereferenceNameInfo(PreviousNameInfo);
|
|
ObDereferenceObject(LookupContext->Object);
|
|
}
|
|
|
|
LookupContext->Object = Object;
|
|
|
|
return Object;
|
|
}
|
|
|
|
|
|
BOOLEAN
|
|
ObpInsertDirectoryEntry (
|
|
IN POBJECT_DIRECTORY Directory,
|
|
IN POBP_LOOKUP_CONTEXT LookupContext,
|
|
IN POBJECT_HEADER ObjectHeader
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine will insert a new directory entry into a directory
|
|
object. The directory must have already have been searched using
|
|
ObpLookupDirectoryEntry because that routine sets the LookupContext.
|
|
|
|
N.B. The ObpLookupDirectoryEntry before should be done with the LookupContext
|
|
locked.
|
|
|
|
Arguments:
|
|
|
|
Directory - Supplies the directory object being modified. This
|
|
function assumes that we earlier did a lookup on the name
|
|
that was successful or we just did an insertion
|
|
|
|
Object - Supplies the object to insert into the directory
|
|
|
|
LookupContext - The lookupContext passed in previously to ObpLookupDirectoryEntry
|
|
|
|
Return Value:
|
|
|
|
TRUE if the object is inserted successfully and FALSE otherwise
|
|
|
|
--*/
|
|
|
|
{
|
|
POBJECT_DIRECTORY_ENTRY *HeadDirectoryEntry;
|
|
POBJECT_DIRECTORY_ENTRY NewDirectoryEntry;
|
|
POBJECT_HEADER_NAME_INFO NameInfo = OBJECT_HEADER_TO_NAME_INFO( ObjectHeader );
|
|
|
|
#if DBG
|
|
|
|
//
|
|
// This function should be always called with a valid Directory
|
|
// and LookupContext. Test this on checked builds
|
|
//
|
|
|
|
if ((LookupContext->Object != NULL) ||
|
|
!LookupContext->DirectoryLocked ||
|
|
(Directory != LookupContext->Directory)) {
|
|
|
|
DbgPrint("OB: ObpInsertDirectoryEntry - invalid context %p %ld\n",
|
|
LookupContext->Object,
|
|
(ULONG)LookupContext->DirectoryLocked );
|
|
DbgBreakPoint();
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
#endif // DBG
|
|
|
|
//
|
|
// Allocate memory for a new entry, and fail if not enough memory.
|
|
//
|
|
|
|
NewDirectoryEntry = (POBJECT_DIRECTORY_ENTRY)ExAllocatePoolWithTag( PagedPool,
|
|
sizeof( OBJECT_DIRECTORY_ENTRY ),
|
|
'iDbO' );
|
|
|
|
if (NewDirectoryEntry == NULL) {
|
|
|
|
return( FALSE );
|
|
}
|
|
|
|
//
|
|
// Get the right lookup bucket based on the HashIndex
|
|
//
|
|
|
|
HeadDirectoryEntry = (POBJECT_DIRECTORY_ENTRY *)&Directory->HashBuckets[ LookupContext->HashIndex ];
|
|
|
|
//
|
|
// Link the new entry into the chain at the insertion point.
|
|
// This puts the new object right at the head of the current
|
|
// hash bucket chain
|
|
//
|
|
|
|
NewDirectoryEntry->ChainLink = *HeadDirectoryEntry;
|
|
*HeadDirectoryEntry = NewDirectoryEntry;
|
|
NewDirectoryEntry->Object = &ObjectHeader->Body;
|
|
|
|
//
|
|
// Point the object header back to the directory we just inserted
|
|
// it into.
|
|
//
|
|
|
|
NameInfo->Directory = Directory;
|
|
|
|
//
|
|
// Return success.
|
|
//
|
|
|
|
return( TRUE );
|
|
}
|
|
|
|
|
|
BOOLEAN
|
|
ObpDeleteDirectoryEntry (
|
|
IN POBP_LOOKUP_CONTEXT LookupContext
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine deletes the most recently found directory entry from
|
|
the specified directory object. It will only succeed after a
|
|
successful ObpLookupDirectoryEntry call.
|
|
|
|
Arguments:
|
|
|
|
Directory - Supplies the directory being modified
|
|
|
|
Return Value:
|
|
|
|
TRUE if the deletion succeeded and FALSE otherwise
|
|
|
|
--*/
|
|
|
|
{
|
|
POBJECT_DIRECTORY_ENTRY *HeadDirectoryEntry;
|
|
POBJECT_DIRECTORY_ENTRY DirectoryEntry;
|
|
IN POBJECT_DIRECTORY Directory = LookupContext->Directory;
|
|
|
|
//
|
|
// Make sure we have a directory and that it has a found entry
|
|
//
|
|
|
|
if (!Directory ) {
|
|
|
|
return( FALSE );
|
|
}
|
|
|
|
//
|
|
// The lookup path places the object in the front of the list, so basically
|
|
// we find the object immediately
|
|
//
|
|
|
|
HeadDirectoryEntry = (POBJECT_DIRECTORY_ENTRY *)&Directory->HashBuckets[ LookupContext->HashIndex ];
|
|
|
|
DirectoryEntry = *HeadDirectoryEntry;
|
|
|
|
//
|
|
// Unlink the entry from the head of the bucket chain and free the
|
|
// memory for the entry.
|
|
//
|
|
|
|
*HeadDirectoryEntry = DirectoryEntry->ChainLink;
|
|
DirectoryEntry->ChainLink = NULL;
|
|
|
|
ExFreePool( DirectoryEntry );
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
POBJECT_DIRECTORY
|
|
ObpGetShadowDirectory(
|
|
POBJECT_DIRECTORY Dir
|
|
)
|
|
{
|
|
POBJECT_DIRECTORY NewDir;
|
|
|
|
NewDir = NULL;
|
|
|
|
ObpLockDeviceMap();
|
|
|
|
if (Dir->DeviceMap != NULL) {
|
|
NewDir = Dir->DeviceMap->GlobalDosDevicesDirectory;
|
|
}
|
|
|
|
ObpUnlockDeviceMap();
|
|
|
|
return NewDir;
|
|
}
|
|
|
|
|
|
NTSTATUS
|
|
ObpSetCurrentProcessDeviceMap(
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function sets the process' device map to the device map associated
|
|
with the process token's LUID.
|
|
|
|
Arguments:
|
|
|
|
none
|
|
|
|
Return Values:
|
|
|
|
STATUS_NO_TOKEN - process does not have a primary token
|
|
|
|
STATUS_OBJECT_PATH_INVALID - could not obtain the device map associated
|
|
with the process token's LUID
|
|
|
|
An appropriate status - unexcepted error occurred
|
|
|
|
--*/
|
|
{
|
|
PEPROCESS pProcess;
|
|
PACCESS_TOKEN pToken = NULL;
|
|
LUID userLuid;
|
|
LUID SystemAuthenticationId = SYSTEM_LUID; // Local_System's LUID
|
|
PDEVICE_MAP DeviceMap = NULL;
|
|
PDEVICE_MAP DerefDeviceMap = NULL;
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
|
|
pProcess = PsGetCurrentProcess();
|
|
|
|
pToken = PsReferencePrimaryToken( pProcess );
|
|
|
|
if (pToken == NULL) {
|
|
return (STATUS_NO_TOKEN);
|
|
}
|
|
|
|
Status = SeQueryAuthenticationIdToken( pToken, &userLuid );
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
PsDereferencePrimaryToken( pToken );
|
|
return (Status);
|
|
}
|
|
|
|
if (!RtlEqualLuid( &userLuid, &SystemAuthenticationId )) {
|
|
PDEVICE_MAP pDevMap;
|
|
|
|
//
|
|
// Get a handle to the Device Map for the user's LUID
|
|
//
|
|
Status = SeGetLogonIdDeviceMap( &userLuid,
|
|
&pDevMap );
|
|
|
|
if (NT_SUCCESS(Status)) {
|
|
DeviceMap = pDevMap;
|
|
}
|
|
}
|
|
else {
|
|
//
|
|
// Process is Local_System, so use the System's device map
|
|
//
|
|
DeviceMap = ObSystemDeviceMap;
|
|
}
|
|
|
|
if (DeviceMap != NULL) {
|
|
//
|
|
// set the process' device map
|
|
//
|
|
|
|
ObpLockDeviceMap();
|
|
|
|
DerefDeviceMap = pProcess->DeviceMap;
|
|
|
|
pProcess->DeviceMap = DeviceMap;
|
|
|
|
DeviceMap->ReferenceCount++;
|
|
|
|
ObpUnlockDeviceMap();
|
|
}
|
|
else {
|
|
Status = STATUS_OBJECT_PATH_INVALID;
|
|
}
|
|
|
|
PsDereferencePrimaryToken( pToken );
|
|
|
|
//
|
|
// If the process already had a device map, then deref it now
|
|
//
|
|
if (DerefDeviceMap != NULL) {
|
|
ObfDereferenceDeviceMap (DerefDeviceMap);
|
|
}
|
|
|
|
return (Status);
|
|
}
|
|
|
|
|
|
PDEVICE_MAP
|
|
ObpReferenceDeviceMap(
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function obtains the correct device map associated with the
|
|
caller.
|
|
If LUID device maps are enabled,
|
|
then we obtain the LUID's device map.
|
|
|
|
We use the existing process device map field as a cache for the
|
|
process token's LUID device map.
|
|
|
|
If LUID device maps are disabled,
|
|
then use the device map associated with the process
|
|
|
|
Arguments:
|
|
|
|
none
|
|
|
|
Return Values:
|
|
|
|
A pointer to the caller's device map
|
|
NULL - could not obtain the user's device map
|
|
|
|
--*/
|
|
{
|
|
PDEVICE_MAP DeviceMap = NULL;
|
|
BOOLEAN LocalSystemRequest = FALSE;
|
|
LOGICAL LUIDDeviceMapsEnabled;
|
|
PACCESS_TOKEN pToken = NULL;
|
|
NTSTATUS Status;
|
|
|
|
LUIDDeviceMapsEnabled = (ObpLUIDDeviceMapsEnabled != 0);
|
|
|
|
if (LUIDDeviceMapsEnabled == TRUE) {
|
|
|
|
PETHREAD Thread = NULL;
|
|
|
|
//
|
|
// Separate device maps for each user LUID
|
|
// if (thread is impersonating)
|
|
// then get user's LUID to retrieve their device map
|
|
// else use the process' device map
|
|
// if (LUID is the Local_System)
|
|
// then use the system's device map
|
|
// if (unable to retrieve LUID's device map),
|
|
// then use the process' device map
|
|
//
|
|
|
|
//
|
|
// Get the current thread & check if the thread is impersonating
|
|
//
|
|
// if impersonating,
|
|
// then take the long path
|
|
// - get thread's access token
|
|
// - read the caller's LUID from the token
|
|
// - get the device map associate with this LUID
|
|
// if not impersonating,
|
|
// then use the device map associated with the process
|
|
//
|
|
Thread = PsGetCurrentThread();
|
|
|
|
if ( PS_IS_THREAD_IMPERSONATING (Thread) ) {
|
|
BOOLEAN fCopyOnOpen;
|
|
BOOLEAN fEffectiveOnly;
|
|
SECURITY_IMPERSONATION_LEVEL ImpersonationLevel;
|
|
LUID userLuid;
|
|
|
|
|
|
//
|
|
// Get the caller's access token from the thread
|
|
//
|
|
pToken = PsReferenceImpersonationToken( Thread,
|
|
&fCopyOnOpen,
|
|
&fEffectiveOnly,
|
|
&ImpersonationLevel);
|
|
|
|
if (pToken != NULL) {
|
|
|
|
//
|
|
// query the token for the LUID
|
|
//
|
|
Status = SeQueryAuthenticationIdToken( pToken, &userLuid );
|
|
}
|
|
else {
|
|
Status = STATUS_NO_TOKEN;
|
|
userLuid.LowPart = 0;
|
|
userLuid.HighPart = 0;
|
|
}
|
|
|
|
if (NT_SUCCESS(Status)) {
|
|
LUID SystemAuthenticationId = SYSTEM_LUID; // Local_System's LUID
|
|
|
|
//
|
|
// Verify the caller is not Local_System by
|
|
// comparing the LUID with Local_System's LUID
|
|
//
|
|
if (!RtlEqualLuid( &userLuid, &SystemAuthenticationId )) {
|
|
PDEVICE_MAP pDevMap;
|
|
|
|
//
|
|
// Get a handle to the Device Map for the LUID
|
|
//
|
|
Status = SeGetLogonIdDeviceMap( &userLuid,
|
|
&pDevMap );
|
|
|
|
if (NT_SUCCESS(Status)) {
|
|
|
|
//
|
|
// Use the device map associated with the LUID
|
|
//
|
|
|
|
DeviceMap = pDevMap;
|
|
|
|
ObpLockDeviceMap();
|
|
|
|
if (DeviceMap != NULL) {
|
|
DeviceMap->ReferenceCount++;
|
|
}
|
|
|
|
ObpUnlockDeviceMap();
|
|
|
|
}
|
|
|
|
}
|
|
else {
|
|
//
|
|
// Local_System will use the system's device map
|
|
//
|
|
LocalSystemRequest = TRUE;
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (DeviceMap == NULL) {
|
|
//
|
|
// if (going to reference the process' device map and the process'
|
|
// device map is not set),
|
|
// then set the process' device map
|
|
//
|
|
if ((LUIDDeviceMapsEnabled == TRUE) &&
|
|
(LocalSystemRequest == FALSE) &&
|
|
((PsGetCurrentProcess()->DeviceMap) == NULL)) {
|
|
|
|
Status = ObpSetCurrentProcessDeviceMap();
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
goto Error_Exit;
|
|
}
|
|
}
|
|
|
|
ObpLockDeviceMap();
|
|
|
|
if (LocalSystemRequest == TRUE) {
|
|
//
|
|
// Use the system's device map
|
|
//
|
|
DeviceMap = ObSystemDeviceMap;
|
|
}
|
|
else {
|
|
//
|
|
// Use the device map from the process
|
|
//
|
|
DeviceMap = PsGetCurrentProcess()->DeviceMap;
|
|
}
|
|
|
|
if (DeviceMap != NULL) {
|
|
DeviceMap->ReferenceCount++;
|
|
}
|
|
ObpUnlockDeviceMap();
|
|
}
|
|
|
|
Error_Exit:
|
|
|
|
if( pToken != NULL ) {
|
|
PsDereferenceImpersonationToken(pToken);
|
|
}
|
|
|
|
return DeviceMap;
|
|
}
|
|
|
|
|
|
VOID
|
|
FASTCALL
|
|
ObfDereferenceDeviceMap(
|
|
IN PDEVICE_MAP DeviceMap
|
|
)
|
|
{
|
|
ObpLockDeviceMap();
|
|
|
|
DeviceMap->ReferenceCount--;
|
|
|
|
if (DeviceMap->ReferenceCount == 0) {
|
|
|
|
DeviceMap->DosDevicesDirectory->DeviceMap = NULL;
|
|
|
|
ObpUnlockDeviceMap();
|
|
|
|
//
|
|
// This devmap is dead so mark the directory temporary so its name will go away and dereference it.
|
|
//
|
|
ObMakeTemporaryObject (DeviceMap->DosDevicesDirectory);
|
|
ObDereferenceObject( DeviceMap->DosDevicesDirectory );
|
|
|
|
ExFreePool( DeviceMap );
|
|
|
|
} else {
|
|
|
|
ObpUnlockDeviceMap();
|
|
}
|
|
}
|
|
|
|
|
|
NTSTATUS
|
|
ObpLookupObjectName (
|
|
IN HANDLE RootDirectoryHandle OPTIONAL,
|
|
IN PUNICODE_STRING ObjectName,
|
|
IN ULONG Attributes,
|
|
IN POBJECT_TYPE ObjectType,
|
|
IN KPROCESSOR_MODE AccessMode,
|
|
IN PVOID ParseContext OPTIONAL,
|
|
IN PSECURITY_QUALITY_OF_SERVICE SecurityQos OPTIONAL,
|
|
IN PVOID InsertObject OPTIONAL,
|
|
IN OUT PACCESS_STATE AccessState,
|
|
OUT POBP_LOOKUP_CONTEXT LookupContext,
|
|
OUT PVOID *FoundObject
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function will search a given directoroy for a specified
|
|
object name. It will also create a new object specified by
|
|
InsertObject.
|
|
|
|
Arguments:
|
|
|
|
RootDirectoryHandle - Optionally supplies the directory being
|
|
searched. If not supplied then this routine searches
|
|
the root directory
|
|
|
|
ObjectName - Supplies the name of object to lookup
|
|
|
|
Attributes - Specifies the attributes for the lookup (e.g., case
|
|
insensitive)
|
|
|
|
ObjectType - Specifies the type of the object to lookup
|
|
|
|
AccessMode - Specifies the callers processor mode
|
|
|
|
ParseContext - Optionally supplies a parse context that is blindly
|
|
passed to the parse callback routines
|
|
|
|
SecurityQos - Optionally supplies a pointer to the passed Security
|
|
Quality of Service parameter that is blindly passed to the parse
|
|
callback routines
|
|
|
|
InsertObject - Optionally supplies the object we think will be found.
|
|
This is used if the caller did not give a root directory handle
|
|
and the object name is "\" and the root object directory hasn't
|
|
been created yet. In other cases where we wind up creating
|
|
a new directory entry this is the object inserted.
|
|
|
|
AccessState - Current access state, describing already granted access
|
|
types, the privileges used to get them, and any access types yet to
|
|
be granted. The access masks may not contain any generic access
|
|
types.
|
|
|
|
DirectoryLocked - Receives an indication if this routine has returned
|
|
with the input directory locked
|
|
|
|
FoundObject - Receives a pointer to the object body if found
|
|
|
|
Return Value:
|
|
|
|
An appropriate status value.
|
|
|
|
N.B. If the status returned is SUCCESS the caller has the
|
|
responsability to release the lookup context
|
|
|
|
--*/
|
|
|
|
{
|
|
POBJECT_DIRECTORY RootDirectory;
|
|
POBJECT_DIRECTORY Directory = NULL;
|
|
POBJECT_DIRECTORY ParentDirectory = NULL;
|
|
POBJECT_HEADER ObjectHeader;
|
|
POBJECT_HEADER_NAME_INFO NameInfo;
|
|
PDEVICE_MAP DeviceMap = NULL;
|
|
PVOID Object;
|
|
UNICODE_STRING RemainingName;
|
|
UNICODE_STRING ComponentName;
|
|
PWCH NewName;
|
|
NTSTATUS Status;
|
|
BOOLEAN Reparse = FALSE; // BUGBUG - remove initialization & validate
|
|
BOOLEAN ReparsedSymbolicLink = FALSE;
|
|
ULONG MaxReparse = OBJ_MAX_REPARSE_ATTEMPTS;
|
|
OB_PARSE_METHOD ParseProcedure;
|
|
extern POBJECT_TYPE IoFileObjectType;
|
|
KPROCESSOR_MODE AccessCheckMode;
|
|
|
|
ObpValidateIrql( "ObpLookupObjectName" );
|
|
|
|
//
|
|
// Initialize our output variables to say we haven't lock or found
|
|
// anything but we were successful at it
|
|
//
|
|
|
|
ObpInitializeLookupContext(LookupContext);
|
|
|
|
*FoundObject = NULL;
|
|
Status = STATUS_SUCCESS;
|
|
|
|
Object = NULL;
|
|
|
|
//
|
|
// If the global flag says that we need to perform a case-insensitive check
|
|
// we'll force the OBJ_CASE_INSENSITIVE flag in attributes at lookup
|
|
//
|
|
|
|
if ( ObpCaseInsensitive ) {
|
|
|
|
if ( (ObjectType == NULL) ||
|
|
(ObjectType->TypeInfo.CaseInsensitive)
|
|
) {
|
|
|
|
Attributes |= OBJ_CASE_INSENSITIVE;
|
|
}
|
|
}
|
|
|
|
if (Attributes & OBJ_FORCE_ACCESS_CHECK) {
|
|
AccessCheckMode = UserMode;
|
|
} else {
|
|
AccessCheckMode = AccessMode;
|
|
}
|
|
|
|
//
|
|
// Check if the caller has given us a directory to search. Otherwise
|
|
// we'll search the root object directory
|
|
//
|
|
|
|
if (ARGUMENT_PRESENT( RootDirectoryHandle )) {
|
|
|
|
//
|
|
// Otherwise reference the directory object and make sure
|
|
// that we successfully got the object
|
|
//
|
|
|
|
Status = ObReferenceObjectByHandle( RootDirectoryHandle,
|
|
0,
|
|
NULL,
|
|
AccessMode,
|
|
(PVOID *)&RootDirectory,
|
|
NULL );
|
|
|
|
if (!NT_SUCCESS( Status )) {
|
|
|
|
return( Status );
|
|
}
|
|
|
|
//
|
|
// Translate the directory object to its object header
|
|
//
|
|
|
|
ObjectHeader = OBJECT_TO_OBJECT_HEADER( RootDirectory );
|
|
|
|
//
|
|
// Now if the name we're looking up starts with a "\" and it
|
|
// does not have a parse procedure then the syntax is bad
|
|
//
|
|
|
|
if ((ObjectName->Buffer != NULL) &&
|
|
(*(ObjectName->Buffer) == OBJ_NAME_PATH_SEPARATOR) &&
|
|
(ObjectHeader->Type != IoFileObjectType)) {
|
|
|
|
ObDereferenceObject( RootDirectory );
|
|
|
|
return( STATUS_OBJECT_PATH_SYNTAX_BAD );
|
|
}
|
|
|
|
//
|
|
// Now make sure that we do not have the directory of the
|
|
// object types
|
|
//
|
|
|
|
if (ObjectHeader->Type != ObpDirectoryObjectType) {
|
|
|
|
//
|
|
// We have an object directory that is not the object type
|
|
// directory. So now if it doesn't have a parse routine
|
|
// then there is nothing we can
|
|
//
|
|
|
|
if (ObjectHeader->Type->TypeInfo.ParseProcedure == NULL) {
|
|
|
|
ObDereferenceObject( RootDirectory );
|
|
|
|
return( STATUS_INVALID_HANDLE );
|
|
|
|
} else {
|
|
|
|
MaxReparse = OBJ_MAX_REPARSE_ATTEMPTS;
|
|
|
|
//
|
|
// The following loop cycles cycles through the various
|
|
// parse routine to we could encounter trying to resolve
|
|
// this name through symbolic links.
|
|
//
|
|
|
|
while (TRUE) {
|
|
|
|
#if DBG
|
|
KIRQL SaveIrql;
|
|
#endif
|
|
|
|
RemainingName = *ObjectName;
|
|
|
|
//
|
|
// Invoke the callback routine to parse the remaining
|
|
// object name
|
|
//
|
|
|
|
ObpBeginTypeSpecificCallOut( SaveIrql );
|
|
|
|
Status = (*ObjectHeader->Type->TypeInfo.ParseProcedure)( RootDirectory,
|
|
ObjectType,
|
|
AccessState,
|
|
AccessCheckMode,
|
|
Attributes,
|
|
ObjectName,
|
|
&RemainingName,
|
|
ParseContext,
|
|
SecurityQos,
|
|
&Object );
|
|
|
|
ObpEndTypeSpecificCallOut( SaveIrql, "Parse", ObjectHeader->Type, Object );
|
|
|
|
//
|
|
// If the status was not to do a reparse and the lookup
|
|
// was not successful then we found nothing so we
|
|
// dereference the directory and return the status to
|
|
// our caller. If the object we got back was null then
|
|
// we'll tell our caller that we couldn't find the name.
|
|
// Lastly if we did not get a reparse and we were
|
|
// successful and the object is not null then everything
|
|
// gets nicely returned to our caller
|
|
//
|
|
|
|
if ( ( Status != STATUS_REPARSE ) &&
|
|
( Status != STATUS_REPARSE_OBJECT )) {
|
|
|
|
if (!NT_SUCCESS( Status )) {
|
|
|
|
Object = NULL;
|
|
|
|
} else if (Object == NULL) {
|
|
|
|
Status = STATUS_OBJECT_NAME_NOT_FOUND;
|
|
}
|
|
|
|
ObDereferenceObject( RootDirectory );
|
|
|
|
*FoundObject = Object;
|
|
|
|
return( Status );
|
|
|
|
//
|
|
// We got a status reparse, which means the object
|
|
// name has been modified to have use start all over
|
|
// again. If the reparse target is now empty or it
|
|
// is a path separator then we start the parse at the
|
|
// root directory
|
|
//
|
|
|
|
} else if ((ObjectName->Length == 0) ||
|
|
(ObjectName->Buffer == NULL) ||
|
|
(*(ObjectName->Buffer) == OBJ_NAME_PATH_SEPARATOR)) {
|
|
|
|
//
|
|
// Restart the parse relative to the root directory.
|
|
//
|
|
|
|
ObDereferenceObject( RootDirectory );
|
|
|
|
RootDirectory = ObpRootDirectoryObject;
|
|
RootDirectoryHandle = NULL;
|
|
|
|
goto ParseFromRoot;
|
|
|
|
//
|
|
// We got a reparse and we actually have a new name to
|
|
// go to we if we haven't exhausted our reparse attempts
|
|
// yet then just continue to the top of this loop.
|
|
//
|
|
|
|
} else if (--MaxReparse) {
|
|
|
|
continue;
|
|
|
|
//
|
|
// We got a reparse and we've exhausted our times through
|
|
// the loop so we'll return what we found.
|
|
//
|
|
|
|
} else {
|
|
|
|
ObDereferenceObject( RootDirectory );
|
|
|
|
*FoundObject = Object;
|
|
|
|
//
|
|
// At this point we were failing in stress by
|
|
// returning to the caller with a success status but
|
|
// a null object pointer.
|
|
//
|
|
|
|
if (Object == NULL) {
|
|
|
|
Status = STATUS_OBJECT_NAME_NOT_FOUND;
|
|
}
|
|
|
|
return( Status );
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// At this point the caller has given us the directory of object
|
|
// types. If the caller didn't specify a name then we'll return
|
|
// a pointer to the root object directory.
|
|
//
|
|
|
|
} else if ((ObjectName->Length == 0) ||
|
|
(ObjectName->Buffer == NULL)) {
|
|
|
|
Status = ObReferenceObjectByPointer( RootDirectory,
|
|
0,
|
|
ObjectType,
|
|
AccessMode );
|
|
|
|
if (NT_SUCCESS( Status )) {
|
|
|
|
Object = RootDirectory;
|
|
}
|
|
|
|
ObDereferenceObject( RootDirectory );
|
|
|
|
*FoundObject = Object;
|
|
|
|
return( Status );
|
|
}
|
|
|
|
//
|
|
// Otherwise the caller did not specify a directory to search so
|
|
// we'll default to the object root directory
|
|
//
|
|
|
|
} else {
|
|
|
|
RootDirectory = ObpRootDirectoryObject;
|
|
|
|
//
|
|
// If the name we're looking for is empty then it is illformed.
|
|
// Also it has to start with a "\" or it is illformed.
|
|
//
|
|
|
|
if ((ObjectName->Length == 0) ||
|
|
(ObjectName->Buffer == NULL) ||
|
|
(*(ObjectName->Buffer) != OBJ_NAME_PATH_SEPARATOR)) {
|
|
|
|
return( STATUS_OBJECT_PATH_SYNTAX_BAD );
|
|
}
|
|
|
|
//
|
|
// Check if the name is has only one character (that is the "\")
|
|
// Which means that the caller really just wants to lookup the
|
|
// root directory.
|
|
//
|
|
|
|
if (ObjectName->Length == sizeof( OBJ_NAME_PATH_SEPARATOR )) {
|
|
|
|
//
|
|
// If there is not a root directory yet. Then we really
|
|
// can't return it, however if the caller specified
|
|
// an insert object that is the one we'll reference and
|
|
// return to our caller
|
|
//
|
|
|
|
if (!RootDirectory) {
|
|
|
|
if (InsertObject) {
|
|
|
|
Status = ObReferenceObjectByPointer( InsertObject,
|
|
0,
|
|
ObjectType,
|
|
AccessMode );
|
|
|
|
if (NT_SUCCESS( Status )) {
|
|
|
|
*FoundObject = InsertObject;
|
|
}
|
|
|
|
return( Status );
|
|
|
|
} else {
|
|
|
|
return( STATUS_INVALID_PARAMETER );
|
|
}
|
|
|
|
//
|
|
// At this point the caller did not specify a root directory,
|
|
// the name is "\" and the root object directory exists so
|
|
// we'll simply return the real root directory object
|
|
//
|
|
|
|
} else {
|
|
|
|
Status = ObReferenceObjectByPointer( RootDirectory,
|
|
0,
|
|
ObjectType,
|
|
AccessMode );
|
|
|
|
if (NT_SUCCESS( Status )) {
|
|
|
|
*FoundObject = RootDirectory;
|
|
}
|
|
|
|
return( Status );
|
|
}
|
|
|
|
//
|
|
// At this pointer the caller did not specify a root directory,
|
|
// and the name is more than just a "\"
|
|
//
|
|
// Now if the lookup is case insensitive, and the name buffer is a
|
|
// legitimate pointer (meaning that is it quadword aligned), and
|
|
// there is a dos device map for the process. Then we'll handle
|
|
// the situation here. First get the device map and make sure it
|
|
// doesn't go away while we're using it.
|
|
//
|
|
|
|
} else {
|
|
|
|
ParseFromRoot:
|
|
|
|
if (DeviceMap != NULL) {
|
|
|
|
ObfDereferenceDeviceMap(DeviceMap);
|
|
DeviceMap = NULL;
|
|
}
|
|
|
|
if (!((ULONG_PTR)(ObjectName->Buffer) & (sizeof(ULONGLONG)-1))) {
|
|
|
|
//
|
|
// Check if the object name is actually equal to the
|
|
// global dos devices short name prefix "\??\"
|
|
//
|
|
|
|
if ((ObjectName->Length >= ObpDosDevicesShortName.Length)
|
|
|
|
&&
|
|
|
|
(*(PULONGLONG)(ObjectName->Buffer) == ObpDosDevicesShortNamePrefix.Alignment.QuadPart)) {
|
|
|
|
if ((DeviceMap = ObpReferenceDeviceMap()) != NULL) {
|
|
if (DeviceMap->DosDevicesDirectory != NULL ) {
|
|
|
|
//
|
|
// The user gave us the dos short name prefix so we'll
|
|
// look down the directory, and start the search at the
|
|
// dos device directory
|
|
//
|
|
|
|
ParentDirectory = RootDirectory;
|
|
|
|
Directory = DeviceMap->DosDevicesDirectory;
|
|
|
|
RemainingName = *ObjectName;
|
|
RemainingName.Buffer += (ObpDosDevicesShortName.Length / sizeof( WCHAR ));
|
|
RemainingName.Length = (USHORT)(RemainingName.Length - ObpDosDevicesShortName.Length);
|
|
|
|
goto quickStart;
|
|
|
|
}
|
|
}
|
|
//
|
|
// The name is not equal to "\??\" but check if it is
|
|
// equal to "\??"
|
|
//
|
|
|
|
} else if ((ObjectName->Length == ObpDosDevicesShortName.Length - sizeof( WCHAR ))
|
|
|
|
&&
|
|
|
|
(*(PULONG)(ObjectName->Buffer) == ObpDosDevicesShortNameRoot.Alignment.LowPart)
|
|
|
|
&&
|
|
|
|
(*((PWCHAR)(ObjectName->Buffer)+2) == (WCHAR)(ObpDosDevicesShortNameRoot.Alignment.HighPart))) {
|
|
|
|
//
|
|
// The user specified "\??" so we return to dos devices
|
|
// directory to our caller
|
|
//
|
|
|
|
if ((DeviceMap = ObpReferenceDeviceMap()) != NULL) {
|
|
if (DeviceMap->DosDevicesDirectory != NULL ) {
|
|
|
|
Status = ObReferenceObjectByPointer( DeviceMap->DosDevicesDirectory,
|
|
0,
|
|
ObjectType,
|
|
AccessMode );
|
|
|
|
if (NT_SUCCESS( Status )) {
|
|
|
|
*FoundObject = DeviceMap->DosDevicesDirectory;
|
|
}
|
|
|
|
//
|
|
// Dereference the Device Map
|
|
//
|
|
|
|
ObfDereferenceDeviceMap(DeviceMap);
|
|
|
|
return( Status );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// At this point either
|
|
//
|
|
// the user specified a directory that is not the object
|
|
// type directory and got repase back to the root directory
|
|
//
|
|
// the user specified the object type directory and gave us
|
|
// a name to actually look up
|
|
//
|
|
// the user did not specify a search directory (default
|
|
// to root object directory) and if the name did start off
|
|
// with the dos device prefix we've munged outselves back to
|
|
// it to the dos device directory for the process
|
|
//
|
|
|
|
if( ReparsedSymbolicLink == FALSE ) {
|
|
Reparse = TRUE;
|
|
MaxReparse = OBJ_MAX_REPARSE_ATTEMPTS;
|
|
}
|
|
|
|
while (Reparse) {
|
|
|
|
RemainingName = *ObjectName;
|
|
|
|
quickStart:
|
|
|
|
Reparse = FALSE;
|
|
|
|
while (TRUE) {
|
|
|
|
Object = NULL;
|
|
|
|
//if (RemainingName.Length == 0) {
|
|
// Status = STATUS_OBJECT_NAME_INVALID;
|
|
// break;
|
|
// }
|
|
|
|
//
|
|
// If the remaining name for the object starts with a
|
|
// "\" then just gobble up the "\"
|
|
//
|
|
|
|
if ( (RemainingName.Length != 0) &&
|
|
(*(RemainingName.Buffer) == OBJ_NAME_PATH_SEPARATOR) ) {
|
|
|
|
RemainingName.Buffer++;
|
|
RemainingName.Length -= sizeof( OBJ_NAME_PATH_SEPARATOR );
|
|
}
|
|
|
|
//
|
|
// The following piece of code will calculate the first
|
|
// component of the remaining name. If there is not
|
|
// a remaining component then the object name is illformed
|
|
//
|
|
|
|
ComponentName = RemainingName;
|
|
|
|
while (RemainingName.Length != 0) {
|
|
|
|
if (*(RemainingName.Buffer) == OBJ_NAME_PATH_SEPARATOR) {
|
|
|
|
break;
|
|
}
|
|
|
|
RemainingName.Buffer++;
|
|
RemainingName.Length -= sizeof( OBJ_NAME_PATH_SEPARATOR );
|
|
}
|
|
|
|
ComponentName.Length = (USHORT)(ComponentName.Length - RemainingName.Length);
|
|
|
|
if (ComponentName.Length == 0) {
|
|
|
|
Status = STATUS_OBJECT_NAME_INVALID;
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Now we have the first component name to lookup so we'll
|
|
// look the directory is necessary
|
|
//
|
|
|
|
if ( Directory == NULL ) {
|
|
|
|
Directory = RootDirectory;
|
|
}
|
|
|
|
//
|
|
// Now if the caller does not have traverse privilege and
|
|
// there is a parent directory then we must check if the
|
|
// user has traverse access to the directory. Our local
|
|
// Reparse variable should be false at this point so we'll
|
|
// drop out of both loops
|
|
//
|
|
|
|
if ( (AccessCheckMode != KernelMode) &&
|
|
!(AccessState->Flags & TOKEN_HAS_TRAVERSE_PRIVILEGE) &&
|
|
(ParentDirectory != NULL) ) {
|
|
|
|
if (!ObpCheckTraverseAccess( ParentDirectory,
|
|
DIRECTORY_TRAVERSE,
|
|
AccessState,
|
|
FALSE,
|
|
AccessCheckMode,
|
|
&Status )) {
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
//
|
|
// If the object already exists in this directory, find it,
|
|
// else return NULL.
|
|
//
|
|
|
|
if ((RemainingName.Length == 0) && (InsertObject != NULL)) {
|
|
|
|
//
|
|
// If we are searching the last name, and we have an object
|
|
// to insert into that directory, we lock the context
|
|
// exclusively before the lookup. An insertion is likely
|
|
// to occur after this lookup if it fails, so we need to protect
|
|
// this directory to be changed until the ObpInsertDirectoryEntry call
|
|
//
|
|
|
|
ObpLockLookupContext( LookupContext, Directory );
|
|
}
|
|
|
|
Object = ObpLookupDirectoryEntry( Directory,
|
|
&ComponentName,
|
|
Attributes,
|
|
InsertObject == NULL ? TRUE : FALSE,
|
|
LookupContext );
|
|
|
|
if (!Object) {
|
|
|
|
//
|
|
// We didn't find the object. If there is some remaining
|
|
// name left (meaning the component name is a directory in
|
|
// path we trying to break) or the caller didn't specify an
|
|
// insert object then we then we'll break out here with an
|
|
// error status
|
|
//
|
|
|
|
if (RemainingName.Length != 0) {
|
|
|
|
Status = STATUS_OBJECT_PATH_NOT_FOUND;
|
|
break;
|
|
}
|
|
|
|
if (!InsertObject) {
|
|
|
|
Status = STATUS_OBJECT_NAME_NOT_FOUND;
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Check that the caller has the access to the directory
|
|
// to either create a subdirectory (in the object type
|
|
// directory) or to create an object of the given component
|
|
// name. If the call fails then we'll break out of here
|
|
// with the status value set
|
|
//
|
|
|
|
if (!ObCheckCreateObjectAccess( Directory,
|
|
ObjectType == ObpDirectoryObjectType ?
|
|
DIRECTORY_CREATE_SUBDIRECTORY :
|
|
DIRECTORY_CREATE_OBJECT,
|
|
AccessState,
|
|
&ComponentName,
|
|
FALSE,
|
|
AccessCheckMode,
|
|
&Status )) {
|
|
|
|
break;
|
|
}
|
|
|
|
ObjectHeader = OBJECT_TO_OBJECT_HEADER( InsertObject );
|
|
|
|
if ((Directory->SessionId != OBJ_INVALID_SESSION_ID)
|
|
&&
|
|
((ObjectHeader->Type == MmSectionObjectType)
|
|
||
|
|
(ObjectHeader->Type == ObpSymbolicLinkObjectType))) {
|
|
|
|
//
|
|
// This directory is restricted to session. Check whether we are
|
|
// in the same session with the directory, and if we have privilege to
|
|
// access it otherwise
|
|
//
|
|
|
|
if ((Directory->SessionId != PsGetCurrentProcessSessionId())
|
|
&&
|
|
!SeSinglePrivilegeCheck( SeCreateGlobalPrivilege, AccessCheckMode)
|
|
&&
|
|
!ObpIsUnsecureName( &ComponentName,
|
|
((Attributes & OBJ_CASE_INSENSITIVE) ? TRUE : FALSE ))) {
|
|
|
|
Status = STATUS_ACCESS_DENIED;
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
//
|
|
// The object does not exist in the directory and
|
|
// we are allowed to create one. So allocate space
|
|
// for the name and insert the name into the directory
|
|
//
|
|
|
|
NewName = ExAllocatePoolWithTag( PagedPool, ComponentName.Length, 'mNbO' );
|
|
|
|
if ((NewName == NULL) ||
|
|
!ObpInsertDirectoryEntry( Directory, LookupContext, ObjectHeader )) {
|
|
|
|
if (NewName != NULL) {
|
|
|
|
ExFreePool( NewName );
|
|
}
|
|
|
|
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
break;
|
|
}
|
|
|
|
//
|
|
// We have an insert object so now get its name info,
|
|
// because we are going to change its name and insert it
|
|
// into the directory
|
|
//
|
|
|
|
ObReferenceObject( InsertObject );
|
|
|
|
NameInfo = OBJECT_HEADER_TO_NAME_INFO( ObjectHeader );
|
|
|
|
ObReferenceObject( Directory );
|
|
|
|
RtlCopyMemory( NewName,
|
|
ComponentName.Buffer,
|
|
ComponentName.Length );
|
|
|
|
if (NameInfo->Name.Buffer) {
|
|
|
|
ExFreePool( NameInfo->Name.Buffer );
|
|
}
|
|
|
|
NameInfo->Name.Buffer = NewName;
|
|
NameInfo->Name.Length = ComponentName.Length;
|
|
NameInfo->Name.MaximumLength = ComponentName.Length;
|
|
|
|
Object = InsertObject;
|
|
|
|
Status = STATUS_SUCCESS;
|
|
|
|
break;
|
|
}
|
|
|
|
//
|
|
// At this point we've found the component name within
|
|
// the directory. So we'll now grab the components object
|
|
// header, and get its parse routine
|
|
//
|
|
|
|
ReparseObject:
|
|
|
|
ObjectHeader = OBJECT_TO_OBJECT_HEADER( Object );
|
|
ParseProcedure = ObjectHeader->Type->TypeInfo.ParseProcedure;
|
|
|
|
//
|
|
// Now if there is a parse routine for the type and we are not
|
|
// inserting a new object or the parse routine is for symbolic
|
|
// links then we'll actually call the parse routine
|
|
//
|
|
|
|
if (ParseProcedure && (!InsertObject || (ParseProcedure == ObpParseSymbolicLink))) {
|
|
|
|
#if DBG
|
|
KIRQL SaveIrql;
|
|
#endif
|
|
|
|
//
|
|
// Reference the object and then free the directory lock
|
|
// This will keep the object from going away with the
|
|
// directory unlocked
|
|
//
|
|
|
|
ObpIncrPointerCount( ObjectHeader );
|
|
|
|
Directory = NULL;
|
|
ObpReleaseLookupContext(LookupContext);
|
|
|
|
ObpBeginTypeSpecificCallOut( SaveIrql );
|
|
|
|
//
|
|
// Call the objects parse routine
|
|
//
|
|
|
|
Status = (*ParseProcedure)( Object,
|
|
(PVOID)ObjectType,
|
|
AccessState,
|
|
AccessCheckMode,
|
|
Attributes,
|
|
ObjectName,
|
|
&RemainingName,
|
|
ParseContext,
|
|
SecurityQos,
|
|
&Object );
|
|
|
|
ObpEndTypeSpecificCallOut( SaveIrql, "Parse", ObjectHeader->Type, Object );
|
|
|
|
//
|
|
// We can now decrement the object reference count
|
|
//
|
|
|
|
ObDereferenceObject( &ObjectHeader->Body );
|
|
|
|
//
|
|
// Check if we have some reparsing to do
|
|
//
|
|
|
|
if ((Status == STATUS_REPARSE) || (Status == STATUS_REPARSE_OBJECT)) {
|
|
|
|
//
|
|
// See if we've reparsed too many times already and if
|
|
// so we'll fail the request
|
|
//
|
|
|
|
if (--MaxReparse) {
|
|
|
|
//
|
|
// Tell the outer loop to continue looping
|
|
//
|
|
|
|
Reparse = TRUE;
|
|
|
|
//
|
|
// Check if we have a reparse object or the name
|
|
// starts with a "\"
|
|
//
|
|
|
|
if ((Status == STATUS_REPARSE_OBJECT) ||
|
|
(*(ObjectName->Buffer) == OBJ_NAME_PATH_SEPARATOR)) {
|
|
|
|
//
|
|
// If the user specified a start directory then
|
|
// remove this information because we're taking
|
|
// a reparse point to someplace else
|
|
//
|
|
|
|
if (ARGUMENT_PRESENT( RootDirectoryHandle )) {
|
|
|
|
ObDereferenceObject( RootDirectory );
|
|
RootDirectoryHandle = NULL;
|
|
}
|
|
|
|
//
|
|
// And where we start is the root directory
|
|
// object
|
|
//
|
|
|
|
ParentDirectory = NULL;
|
|
RootDirectory = ObpRootDirectoryObject;
|
|
|
|
//
|
|
// Now if this is a reparse object (means we have
|
|
// encountered a symbolic link that has already been
|
|
// snapped so we have an object and remaining
|
|
// name that need to be examined) and we didn't
|
|
// find an object from the parse routine object
|
|
// break out of both loops.
|
|
//
|
|
|
|
if (Status == STATUS_REPARSE_OBJECT) {
|
|
|
|
Reparse = FALSE;
|
|
|
|
if (Object == NULL) {
|
|
|
|
Status = STATUS_OBJECT_NAME_NOT_FOUND;
|
|
|
|
} else {
|
|
|
|
//
|
|
// At this point we have a reparse object
|
|
// so we'll look the directory down and
|
|
// parse the new object
|
|
//
|
|
|
|
goto ReparseObject;
|
|
}
|
|
} else {
|
|
//
|
|
// At this point Status must be equal to
|
|
// STATUS_REPARSE because [(Status equals
|
|
// (STATUS_REPARSE_OBJECT or STATUS_REPARSE))
|
|
// && (Status != STATUS_REPARSE_OBJECT)]
|
|
//
|
|
ReparsedSymbolicLink = TRUE;
|
|
goto ParseFromRoot;
|
|
}
|
|
|
|
//
|
|
// We did not have a reparse object and the name
|
|
// does not start with a "\". Meaning we got back
|
|
// STATUS_REPASE, so now check if the directory
|
|
// is the root object directory and if so then
|
|
// we didn't the name otherwise we'll drop out of
|
|
// the inner loop and reparse true to get back to
|
|
// outer loop
|
|
//
|
|
|
|
} else if (RootDirectory == ObpRootDirectoryObject) {
|
|
|
|
Object = NULL;
|
|
Status = STATUS_OBJECT_NAME_NOT_FOUND;
|
|
|
|
Reparse = FALSE;
|
|
}
|
|
|
|
} else {
|
|
|
|
//
|
|
// We return object not found if we've exhausted
|
|
// the MaxReparse time
|
|
//
|
|
|
|
Object = NULL;
|
|
Status = STATUS_OBJECT_NAME_NOT_FOUND;
|
|
}
|
|
|
|
//
|
|
// We are not reparsing and if we did not get success then
|
|
// the object is null and we'll break out of our loops
|
|
//
|
|
|
|
} else if (!NT_SUCCESS( Status )) {
|
|
|
|
Object = NULL;
|
|
|
|
//
|
|
// We are not reparsing and we got back success but check
|
|
// if the object is null because that means we really didn't
|
|
// find the object, and then break out of our loops
|
|
//
|
|
// If the object is not null then we've been successful and
|
|
// prosperous so break out with the object set.
|
|
//
|
|
|
|
} else if (Object == NULL) {
|
|
|
|
Status = STATUS_OBJECT_NAME_NOT_FOUND;
|
|
}
|
|
|
|
break;
|
|
|
|
} else {
|
|
|
|
//
|
|
// At this point we do not have a parse routine or if there
|
|
// is a parse routine it is not for symbolic links or there
|
|
// may not be a specified insert object
|
|
//
|
|
// Check to see if we have exhausted the remaining name
|
|
//
|
|
|
|
if (RemainingName.Length == 0) {
|
|
|
|
//
|
|
// Check if the caller specified an object to insert.
|
|
// If specified then we'll break out of our loops with
|
|
// the object that we've found
|
|
//
|
|
|
|
if (!InsertObject) {
|
|
|
|
//
|
|
// The user did not specify an insert object
|
|
// so we're opening an existing object. Make sure
|
|
// we have traverse access to the container
|
|
// directory.
|
|
//
|
|
|
|
if ( (AccessCheckMode != KernelMode) &&
|
|
!(AccessState->Flags & TOKEN_HAS_TRAVERSE_PRIVILEGE) ) {
|
|
|
|
if (!ObpCheckTraverseAccess( Directory,
|
|
DIRECTORY_TRAVERSE,
|
|
AccessState,
|
|
FALSE,
|
|
AccessCheckMode,
|
|
&Status )) {
|
|
|
|
Object = NULL;
|
|
break;
|
|
}
|
|
}
|
|
|
|
Status = ObReferenceObjectByPointer( Object,
|
|
0,
|
|
ObjectType,
|
|
AccessMode );
|
|
|
|
if (!NT_SUCCESS( Status )) {
|
|
|
|
Object = NULL;
|
|
}
|
|
}
|
|
|
|
break;
|
|
|
|
} else {
|
|
|
|
//
|
|
// There is some name remaining names to process
|
|
// if the directory we're looking at is the
|
|
// directory of object types and set ourselves
|
|
// up to parse it all over again.
|
|
//
|
|
|
|
if (ObjectHeader->Type == ObpDirectoryObjectType) {
|
|
|
|
ParentDirectory = Directory;
|
|
Directory = (POBJECT_DIRECTORY)Object;
|
|
|
|
} else {
|
|
|
|
//
|
|
// Otherwise there has been a mismatch so we'll
|
|
// set our error status and break out of the
|
|
// loops
|
|
//
|
|
|
|
Status = STATUS_OBJECT_TYPE_MISMATCH;
|
|
Object = NULL;
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// We can release the context if our search was unsuccesful.
|
|
// We still need the directory to be locked if a new object is
|
|
// inserted into that directory, until we finish the initialization
|
|
// (i.e SD, handle, ...). Note the object is visible now and could be accessed
|
|
// via name. We need to hold the directory lock until we are done.
|
|
//
|
|
|
|
if ( !NT_SUCCESS(Status) ) {
|
|
|
|
ObpReleaseLookupContext(LookupContext);
|
|
}
|
|
|
|
//
|
|
// If the device map has been referenced then dereference it
|
|
//
|
|
|
|
if (DeviceMap != NULL) {
|
|
|
|
ObfDereferenceDeviceMap(DeviceMap);
|
|
}
|
|
|
|
//
|
|
// At this point we've parsed the object name as much as possible
|
|
// going through symbolic links as necessary. So now set the
|
|
// output object pointer, and if we really did not find an object
|
|
// then we might need to modify the error status. If the
|
|
// status was repase or some success status then translate it
|
|
// to name not found.
|
|
//
|
|
|
|
if (!(*FoundObject = Object)) {
|
|
|
|
if (Status == STATUS_REPARSE) {
|
|
|
|
Status = STATUS_OBJECT_NAME_NOT_FOUND;
|
|
|
|
} else if (NT_SUCCESS( Status )) {
|
|
|
|
Status = STATUS_OBJECT_NAME_NOT_FOUND;
|
|
}
|
|
}
|
|
|
|
//
|
|
// If the caller gave us a root directory to search (and we didn't
|
|
// zero out this value) then free up our reference
|
|
//
|
|
|
|
if (ARGUMENT_PRESENT( RootDirectoryHandle )) {
|
|
|
|
ObDereferenceObject( RootDirectory );
|
|
RootDirectoryHandle = NULL;
|
|
}
|
|
|
|
//
|
|
// And return to our caller
|
|
//
|
|
|
|
return( Status );
|
|
}
|
|
|
|
|
|
NTSTATUS
|
|
NtMakePermanentObject (
|
|
IN HANDLE Handle
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine makes the specified object permanent.
|
|
By default, only Local_System may make this call
|
|
|
|
Arguments:
|
|
|
|
Handle - Supplies a handle to the object being modified
|
|
|
|
Return Value:
|
|
|
|
An appropriate status value.
|
|
|
|
--*/
|
|
|
|
{
|
|
KPROCESSOR_MODE PreviousMode;
|
|
NTSTATUS Status;
|
|
PVOID Object;
|
|
OBJECT_HANDLE_INFORMATION HandleInformation;
|
|
POBJECT_HEADER ObjectHeader;
|
|
|
|
|
|
PAGED_CODE();
|
|
|
|
//
|
|
// Get previous processor mode and probe output argument if necessary.
|
|
//
|
|
|
|
PreviousMode = KeGetPreviousMode();
|
|
|
|
//
|
|
// The object is being changed to permanent, check if
|
|
// the caller has the appropriate privilege.
|
|
//
|
|
if (!SeSinglePrivilegeCheck( SeCreatePermanentPrivilege,
|
|
PreviousMode)) {
|
|
|
|
Status = STATUS_PRIVILEGE_NOT_HELD;
|
|
return( Status );
|
|
}
|
|
|
|
Status = ObReferenceObjectByHandle( Handle,
|
|
0,
|
|
(POBJECT_TYPE)NULL,
|
|
PreviousMode,
|
|
&Object,
|
|
&HandleInformation );
|
|
if (!NT_SUCCESS( Status )) {
|
|
return( Status );
|
|
}
|
|
|
|
//
|
|
// Make the object permanant. Note that the object should still
|
|
// have a name and directory entry because its handle count is not
|
|
// zero
|
|
//
|
|
|
|
ObjectHeader = OBJECT_TO_OBJECT_HEADER( Object );
|
|
|
|
//
|
|
// Other bits are set in this flags field by the handle database code. Synchronize with that.
|
|
//
|
|
|
|
ObpLockObject( ObjectHeader );
|
|
|
|
ObjectHeader->Flags |= OB_FLAG_PERMANENT_OBJECT;
|
|
|
|
//
|
|
// This routine releases the type mutex
|
|
//
|
|
|
|
ObpUnlockObject( ObjectHeader );
|
|
|
|
ObDereferenceObject( Object );
|
|
|
|
return( Status );
|
|
}
|
|
|
|
POBJECT_HEADER_NAME_INFO
|
|
ObpTryReferenceNameInfoExclusive(
|
|
IN POBJECT_HEADER ObjectHeader
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
The function references exclusively the name information for a named object
|
|
Note that if there are outstanding references to the name info this function can fail
|
|
|
|
|
|
Arguments:
|
|
|
|
ObjectHeader - Object being locked
|
|
|
|
Return Value:
|
|
|
|
Returns the name information or NULL if it cannot be locked
|
|
|
|
Assumptions:
|
|
|
|
The parent directory is assumed to be exclusively locked (so that other threads
|
|
waiting to reference the name will have first to grab the directory lock).
|
|
|
|
--*/
|
|
|
|
{
|
|
POBJECT_HEADER_NAME_INFO NameInfo;
|
|
LONG References, NewReferences;
|
|
|
|
NameInfo = OBJECT_HEADER_TO_NAME_INFO( ObjectHeader );
|
|
|
|
References = NameInfo->QueryReferences;
|
|
|
|
do {
|
|
|
|
//
|
|
// If this is not the only reference to the object then we cannot lock the name
|
|
// N.B. The caller needs also to have a reference to this name
|
|
//
|
|
|
|
if ((References != 2)
|
|
||
|
|
(NameInfo->Directory == NULL)) {
|
|
|
|
return NULL;
|
|
}
|
|
|
|
NewReferences = InterlockedCompareExchange ( (PLONG) &NameInfo->QueryReferences,
|
|
OBP_NAME_LOCKED | References,
|
|
References);
|
|
|
|
//
|
|
// If the exchange compare completed ok then we did a reference so return true.
|
|
//
|
|
|
|
if (NewReferences == References) {
|
|
|
|
return NameInfo;
|
|
}
|
|
|
|
//
|
|
// We failed because somebody else got in and changed the refence count on us. Use the new value to
|
|
// prime the exchange again.
|
|
//
|
|
|
|
References = NewReferences;
|
|
|
|
} while (TRUE);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
VOID
|
|
ObpReleaseExclusiveNameLock(
|
|
IN POBJECT_HEADER ObjectHeader
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
The routine releases the exclusive lock for the name information
|
|
|
|
Arguments:
|
|
|
|
ObjectHeader - Object being locked
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
POBJECT_HEADER_NAME_INFO NameInfo;
|
|
NameInfo = OBJECT_HEADER_TO_NAME_INFO( ObjectHeader );
|
|
|
|
InterlockedExchangeAdd((PLONG)&NameInfo->QueryReferences, -OBP_NAME_LOCKED);
|
|
}
|
|
|
|
POBJECT_DIRECTORY_ENTRY
|
|
ObpUnlinkDirectoryEntry (
|
|
IN POBJECT_DIRECTORY Directory,
|
|
IN ULONG HashIndex
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function removes the directory entry from a directory. Note that before calling this
|
|
function a lookup is necessary.
|
|
|
|
Arguments:
|
|
|
|
Directory - Supplies the directory
|
|
|
|
HashIndex - The hash value obtained from from the lookup context
|
|
|
|
Return Value:
|
|
|
|
Returns the directory entry removed from parent
|
|
|
|
--*/
|
|
|
|
{
|
|
POBJECT_DIRECTORY_ENTRY *HeadDirectoryEntry;
|
|
POBJECT_DIRECTORY_ENTRY DirectoryEntry;
|
|
|
|
//
|
|
// The lookup path places the object in the front of the list, so basically
|
|
// we find the object immediately
|
|
//
|
|
|
|
HeadDirectoryEntry = (POBJECT_DIRECTORY_ENTRY *)&Directory->HashBuckets[ HashIndex ];
|
|
|
|
DirectoryEntry = *HeadDirectoryEntry;
|
|
|
|
//
|
|
// Unlink the entry from the head of the bucket chain and free the
|
|
// memory for the entry.
|
|
//
|
|
|
|
*HeadDirectoryEntry = DirectoryEntry->ChainLink;
|
|
DirectoryEntry->ChainLink = NULL;
|
|
|
|
return DirectoryEntry;
|
|
}
|
|
|
|
|
|
VOID
|
|
ObpLinkDirectoryEntry (
|
|
IN POBJECT_DIRECTORY Directory,
|
|
IN ULONG HashIndex,
|
|
IN POBJECT_DIRECTORY_ENTRY NewDirectoryEntry
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
The function inserts a new directory entry into a directory object
|
|
|
|
Arguments:
|
|
|
|
Directory - Supplies the directory object being modified. This
|
|
function assumes that we earlier did a lookup on the name
|
|
that was successful or we just did an insertion
|
|
|
|
HashIndex - The hash value obtained from from the lookup context
|
|
|
|
NewDirectoryEntry - Supplies the directory entry to be inserted
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
|
|
{
|
|
POBJECT_DIRECTORY_ENTRY *HeadDirectoryEntry;
|
|
|
|
//
|
|
// Get the right lookup bucket based on the HashIndex
|
|
//
|
|
|
|
HeadDirectoryEntry = (POBJECT_DIRECTORY_ENTRY *)&Directory->HashBuckets[ HashIndex ];
|
|
|
|
//
|
|
// Link the new entry into the chain at the insertion point.
|
|
// This puts the new object right at the head of the current
|
|
// hash bucket chain
|
|
//
|
|
|
|
NewDirectoryEntry->ChainLink = *HeadDirectoryEntry;
|
|
*HeadDirectoryEntry = NewDirectoryEntry;
|
|
}
|
|
|
|
VOID
|
|
ObpReleaseLookupContextObject (
|
|
IN POBP_LOOKUP_CONTEXT LookupContext
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function releases the object references (added during the lookup)
|
|
but still keeps the directory locked.
|
|
|
|
N.B. The caller must retain at least one reference to the object and
|
|
to the name to make sure the deletion does not happen under the lock.
|
|
|
|
Arguments:
|
|
|
|
LookupContext - Supplies the context used in previous lookup
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
//
|
|
// Remove the references added to the name info and object
|
|
//
|
|
|
|
if (LookupContext->Object) {
|
|
POBJECT_HEADER_NAME_INFO NameInfo;
|
|
|
|
NameInfo = OBJECT_HEADER_TO_NAME_INFO(OBJECT_TO_OBJECT_HEADER(LookupContext->Object));
|
|
|
|
ObpDereferenceNameInfo( NameInfo );
|
|
ObDereferenceObject(LookupContext->Object);
|
|
LookupContext->Object = NULL;
|
|
}
|
|
}
|
|
|
|
NTSTATUS
|
|
ObSwapObjectNames (
|
|
IN HANDLE DirectoryHandle,
|
|
IN HANDLE Handle1,
|
|
IN HANDLE Handle2,
|
|
IN ULONG Flags
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
The function swaps the names (and permanent object attribute) for two objects inserted into
|
|
the same directory. Both objects must be named and have the same object type.
|
|
The function can fail if another one of these objects has the name locked (for a lookup for example)
|
|
|
|
Arguments:
|
|
|
|
DirectoryHandle - Supplies the parent directory for both objects
|
|
|
|
Handle1 - Supplies the handle to the first object
|
|
|
|
Handle2 - Supplies the handle to the second object
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS.
|
|
|
|
--*/
|
|
|
|
{
|
|
|
|
#define INVALID_HASH_INDEX 0xFFFFFFFF
|
|
|
|
KPROCESSOR_MODE PreviousMode;
|
|
PVOID Object1, Object2;
|
|
NTSTATUS Status;
|
|
POBJECT_HEADER_NAME_INFO NameInfo1, NameInfo2;
|
|
POBJECT_HEADER ObjectHeader1 = NULL, ObjectHeader2 = NULL;
|
|
POBJECT_DIRECTORY Directory;
|
|
OBP_LOOKUP_CONTEXT LookupContext;
|
|
POBJECT_HEADER_NAME_INFO ExclusiveNameInfo1 = NULL, ExclusiveNameInfo2 = NULL;
|
|
POBJECT_DIRECTORY_ENTRY DirectoryEntry1 = NULL, DirectoryEntry2 = NULL;
|
|
ULONG HashIndex1 = INVALID_HASH_INDEX, HashIndex2 = INVALID_HASH_INDEX;
|
|
UNICODE_STRING TmpStr;
|
|
OBJECT_HANDLE_INFORMATION HandleInformation;
|
|
|
|
PreviousMode = KeGetPreviousMode();
|
|
|
|
UNREFERENCED_PARAMETER(Flags);
|
|
|
|
Object1 = NULL;
|
|
Object2 = NULL;
|
|
NameInfo1 = NULL;
|
|
NameInfo2 = NULL;
|
|
|
|
ObpInitializeLookupContext(&LookupContext);
|
|
|
|
Status = ObReferenceObjectByHandle( DirectoryHandle,
|
|
DIRECTORY_CREATE_OBJECT | DIRECTORY_CREATE_SUBDIRECTORY,
|
|
ObpDirectoryObjectType,
|
|
PreviousMode,
|
|
(PVOID *)&Directory,
|
|
NULL );
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
|
|
goto exit;
|
|
}
|
|
|
|
Status = ObReferenceObjectByHandle( Handle1,
|
|
DELETE,
|
|
(POBJECT_TYPE)NULL,
|
|
PreviousMode,
|
|
&Object1,
|
|
&HandleInformation );
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
|
|
goto exit;
|
|
}
|
|
|
|
Status = ObReferenceObjectByHandle( Handle2,
|
|
DELETE,
|
|
(POBJECT_TYPE)NULL,
|
|
PreviousMode,
|
|
&Object2,
|
|
&HandleInformation );
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
|
|
goto exit;
|
|
}
|
|
|
|
if (Object1 == Object2) {
|
|
|
|
Status = STATUS_OBJECT_NAME_COLLISION;
|
|
goto exit;
|
|
}
|
|
|
|
ObjectHeader1 = OBJECT_TO_OBJECT_HEADER( Object1 );
|
|
NameInfo1 = ObpReferenceNameInfo( ObjectHeader1 );
|
|
|
|
ObjectHeader2 = OBJECT_TO_OBJECT_HEADER( Object2 );
|
|
NameInfo2 = ObpReferenceNameInfo( ObjectHeader2 );
|
|
|
|
if (ObjectHeader1->Type != ObjectHeader2->Type) {
|
|
|
|
Status = STATUS_OBJECT_TYPE_MISMATCH;
|
|
goto exit;
|
|
}
|
|
|
|
if ((NameInfo1 == NULL)
|
|
||
|
|
(NameInfo2 == NULL)) {
|
|
|
|
Status = STATUS_OBJECT_NAME_INVALID;
|
|
goto exit;
|
|
}
|
|
|
|
if ((Directory != NameInfo1->Directory)
|
|
||
|
|
(Directory != NameInfo2->Directory)) {
|
|
|
|
Status = STATUS_OBJECT_NAME_INVALID;
|
|
goto exit;
|
|
}
|
|
|
|
ObpLockLookupContext ( &LookupContext, Directory );
|
|
|
|
//
|
|
// Check that the object we is still in the directory
|
|
//
|
|
|
|
if (Object1 != ObpLookupDirectoryEntry( Directory,
|
|
&NameInfo1->Name,
|
|
0,
|
|
FALSE,
|
|
&LookupContext )) {
|
|
|
|
//
|
|
// The object is no longer in directory
|
|
//
|
|
|
|
Status = STATUS_OBJECT_NAME_NOT_FOUND;
|
|
goto exit;
|
|
}
|
|
|
|
HashIndex1 = LookupContext.HashIndex;
|
|
DirectoryEntry1 = ObpUnlinkDirectoryEntry(Directory, HashIndex1);
|
|
|
|
ObpReleaseLookupContextObject(&LookupContext);
|
|
|
|
if (Object2 != ObpLookupDirectoryEntry( Directory,
|
|
&NameInfo2->Name,
|
|
0,
|
|
FALSE,
|
|
&LookupContext )) {
|
|
|
|
//
|
|
// The object is no longer in directory
|
|
//
|
|
|
|
Status = STATUS_OBJECT_NAME_NOT_FOUND;
|
|
goto exit;
|
|
}
|
|
|
|
HashIndex2 = LookupContext.HashIndex;
|
|
DirectoryEntry2 = ObpUnlinkDirectoryEntry(Directory, HashIndex2);
|
|
|
|
ObpReleaseLookupContextObject(&LookupContext);
|
|
|
|
//
|
|
// Now try to lock exclusively both object names
|
|
//
|
|
|
|
ExclusiveNameInfo1 = ObpTryReferenceNameInfoExclusive(ObjectHeader1);
|
|
|
|
if (ExclusiveNameInfo1 == NULL) {
|
|
|
|
Status = STATUS_LOCK_NOT_GRANTED;
|
|
goto exit;
|
|
}
|
|
|
|
ExclusiveNameInfo2 = ObpTryReferenceNameInfoExclusive(ObjectHeader2);
|
|
|
|
if (ExclusiveNameInfo2 == NULL) {
|
|
|
|
Status = STATUS_LOCK_NOT_GRANTED;
|
|
goto exit;
|
|
}
|
|
|
|
//
|
|
// We have both names exclusively locked. we can swap now them
|
|
//
|
|
|
|
TmpStr = ExclusiveNameInfo1->Name;
|
|
ExclusiveNameInfo1->Name = ExclusiveNameInfo2->Name;
|
|
ExclusiveNameInfo2->Name = TmpStr;
|
|
|
|
//
|
|
// Now link back the objects, using the swaped hashes
|
|
//
|
|
|
|
ObpLinkDirectoryEntry(Directory, HashIndex2, DirectoryEntry1);
|
|
ObpLinkDirectoryEntry(Directory, HashIndex1, DirectoryEntry2);
|
|
|
|
DirectoryEntry1 = NULL;
|
|
DirectoryEntry2 = NULL;
|
|
|
|
exit:
|
|
|
|
if (DirectoryEntry1) {
|
|
|
|
ObpLinkDirectoryEntry(Directory, HashIndex1, DirectoryEntry1);
|
|
}
|
|
|
|
if (DirectoryEntry2) {
|
|
|
|
ObpLinkDirectoryEntry(Directory, HashIndex2, DirectoryEntry2);
|
|
}
|
|
|
|
if (ExclusiveNameInfo1) {
|
|
ObpReleaseExclusiveNameLock(ObjectHeader1);
|
|
}
|
|
|
|
if (ExclusiveNameInfo2) {
|
|
ObpReleaseExclusiveNameLock(ObjectHeader2);
|
|
}
|
|
|
|
ObpReleaseLookupContext( &LookupContext );
|
|
|
|
if ( NT_SUCCESS( Status )
|
|
&&
|
|
(ObjectHeader1 != NULL)
|
|
&&
|
|
(ObjectHeader2 != NULL)) {
|
|
|
|
//
|
|
// Lock now both objects and move swap the permanent object flag, if different
|
|
//
|
|
|
|
if ((ObjectHeader1->Flags ^ ObjectHeader2->Flags) & OB_FLAG_PERMANENT_OBJECT) {
|
|
|
|
//
|
|
// Both objects are required to have the same object type. We lock them all
|
|
// before swaping the flags
|
|
//
|
|
|
|
ObpLockAllObjects(ObjectHeader1->Type);
|
|
|
|
//
|
|
// Test again under the lock whether flags were changed
|
|
//
|
|
|
|
if ((ObjectHeader1->Flags ^ ObjectHeader2->Flags) & OB_FLAG_PERMANENT_OBJECT) {
|
|
|
|
//
|
|
// swap the flags
|
|
//
|
|
|
|
ObjectHeader1->Flags ^= OB_FLAG_PERMANENT_OBJECT;
|
|
ObjectHeader2->Flags ^= OB_FLAG_PERMANENT_OBJECT;
|
|
}
|
|
|
|
ObpUnlockAllObjects(ObjectHeader1->Type);
|
|
}
|
|
}
|
|
|
|
ObpDereferenceNameInfo(NameInfo1);
|
|
ObpDereferenceNameInfo(NameInfo2);
|
|
|
|
if (Object1) {
|
|
|
|
ObpDeleteNameCheck( Object1 ); // check whether the permanent flag went away meanwhile
|
|
ObDereferenceObject( Object1 );
|
|
}
|
|
|
|
if (Object2) {
|
|
|
|
ObpDeleteNameCheck( Object2 ); // check whether the permanent flag went away meanwhile
|
|
ObDereferenceObject( Object2 );
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|