Leaked source code of windows server 2003
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.
 
 
 
 
 
 

2611 lines
91 KiB

/*++
Copyright (c) 1989-1993 Microsoft Corporation
Module Name:
parse.c
Abstract:
This module contains the code to implement the device object parse routine.
Author:
Darryl E. Havens (darrylh) 15-May-1988
Environment:
Kernel mode
Revision History:
--*/
#include "iomgr.h"
//
// Define macro to round up the size of a name for buffer optimization.
//
#define RoundNameSize( Length ) ( \
(Length < 64 - 8) ? 64 - 8 : \
(Length < 128 - 8) ? 128 - 8 :\
(Length < 256 - 8) ? 256 - 8 : Length )
#define IO_MAX_REMOUNT_REPARSE_ATTEMPTS 32
NTSTATUS
IopGetNetworkOpenInformation(
IN PFILE_OBJECT FileObject,
IN POPEN_PACKET Op
);
#ifdef ALLOC_PRAGMA
#pragma alloc_text(PAGE, IopParseFile)
#pragma alloc_text(PAGE, IopParseDevice)
#pragma alloc_text(PAGE, IopQueryName)
#pragma alloc_text(PAGE, IopQueryNameInternal)
#pragma alloc_text(PAGE, IopCheckBackupRestorePrivilege)
#pragma alloc_text(PAGE, IopGetNetworkOpenInformation)
#endif
NTSTATUS
IopCheckDeviceAndDriver(
IN POPEN_PACKET op,
IN PDEVICE_OBJECT parseDeviceObject
)
{
NTSTATUS status;
KIRQL irql;
//
// Make sure that the device and its driver are really there and they are
// going to stay there. The object itself cannot go away just yet because
// the object management system has performed a reference which bumps the
// count of the number of reasons why the object must stick around.
// However, the driver could be attempting to unload itself, so perform
// this check. If the driver is being unloaded, then set the final status
// of the operation to "No such device" and return with a NULL file object
// pointer.
//
// Note that it is possible to "open" an exclusive device more than once
// provided that the caller is performing a relative open. This feature
// is how users "allocate" a device, and then use it to perform operations.
//
irql = KeAcquireQueuedSpinLock( LockQueueIoDatabaseLock );
if (parseDeviceObject->DeviceObjectExtension->ExtensionFlags &
(DOE_UNLOAD_PENDING | DOE_DELETE_PENDING | DOE_REMOVE_PENDING | DOE_REMOVE_PROCESSED | DOE_START_PENDING) ||
parseDeviceObject->Flags & DO_DEVICE_INITIALIZING) {
status = STATUS_NO_SUCH_DEVICE;
} else if (parseDeviceObject->Flags & DO_EXCLUSIVE &&
parseDeviceObject->ReferenceCount != 0 &&
op->RelatedFileObject == NULL &&
!(op->Options & IO_ATTACH_DEVICE)) {
status = STATUS_ACCESS_DENIED;
} else {
parseDeviceObject->ReferenceCount++;
status = STATUS_SUCCESS;
}
KeReleaseQueuedSpinLock( LockQueueIoDatabaseLock, irql );
return status;
}
PVPB
IopCheckVpbMounted(
IN POPEN_PACKET op,
IN PDEVICE_OBJECT parseDeviceObject,
IN OUT PUNICODE_STRING RemainingName,
OUT PNTSTATUS status
)
{
PVPB vpb;
PVPB mountVpb;
KIRQL irql;
BOOLEAN alertable;
//
// Loop here until the VPB_MOUNTED test can be passed while holding the
// VPB spinlock. After the mount succeeds, it is still necessary to acquire
// the spinlock to check that the VPB (which may be different from the one
// before the mount) is still mounted. If it is, then its reference count
// is incremented before releasing the spinlock.
//
irql = KeAcquireQueuedSpinLock( LockQueueIoVpbLock );
while (!(parseDeviceObject->Vpb->Flags & VPB_MOUNTED)) {
KeReleaseQueuedSpinLock( LockQueueIoVpbLock, irql );
alertable = (op->CreateOptions & FILE_SYNCHRONOUS_IO_ALERT) ? TRUE : FALSE;
//
// Try to mount the volume, allowing only RAW to perform the mount if
// this is a DASD open.
//
mountVpb = NULL;
*status = IopMountVolume( parseDeviceObject,
(BOOLEAN) (!RemainingName->Length && !op->RelatedFileObject),
FALSE,
alertable,
&mountVpb );
//
// If the mount operation was unsuccessful, adjust the reference
// count for the device and return now.
//
if (!NT_SUCCESS( *status ) || *status == STATUS_USER_APC || *status == STATUS_ALERTED) {
IopDecrementDeviceObjectRef( parseDeviceObject, FALSE, FALSE );
if (!NT_SUCCESS( *status )) {
return NULL;
} else {
*status = STATUS_WRONG_VOLUME;
return NULL;
}
} else {
//
// In this case IopMountVolume did the synchronization already.
//
if (mountVpb) {
return mountVpb;
}
}
irql = KeAcquireQueuedSpinLock( LockQueueIoVpbLock );
}
//
// Synchronize here with the file system to make sure that volumes do not
// go away while en route to the FS.
//
vpb = parseDeviceObject->Vpb;
//
// Check here that the VPB is not locked.
//
if (vpb->Flags & VPB_LOCKED) {
*status = STATUS_ACCESS_DENIED;
vpb = NULL;
} else {
vpb->ReferenceCount += 1;
}
KeReleaseQueuedSpinLock( LockQueueIoVpbLock, irql );
//
// This is because VPB is locked.
// Do the decrement outside the VPB lock because of possible deadlocks.
//
if (!vpb) {
IopDecrementDeviceObjectRef( parseDeviceObject, FALSE, FALSE );
}
return vpb;
}
VOID
IopDereferenceVpbAndFree(
IN PVPB Vpb
)
{
KIRQL irql;
PVPB vpb = (PVPB) NULL;
irql = KeAcquireQueuedSpinLock( LockQueueIoVpbLock );
Vpb->ReferenceCount--;
if ((Vpb->ReferenceCount == 0) &&
(Vpb->RealDevice->Vpb != Vpb) &&
!(Vpb->Flags & VPB_PERSISTENT)) {
vpb = Vpb;
}
KeReleaseQueuedSpinLock( LockQueueIoVpbLock, irql );
if (vpb) {
ExFreePool( vpb );
}
}
NTSTATUS
IopParseDevice(
IN PVOID ParseObject,
IN PVOID ObjectType,
IN 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
)
/*++
Routine Description:
This routine interfaces to the NT Object Manager. It is invoked when
the object system is given the name of an entity to create or open and the
name translates to a device object. This routine is specified as the parse
routine for all device objects.
In the normal case of an NtCreateFile, the user specifies either the name
of a device or of a file. In the former situation, this routine is invoked
with a pointer to the device and a null ("") string. For this case, the
routine simply allocates an IRP, fills it in, and passes it to the driver
for the device. The driver will then perform whatever rudimentary functions
are necessary and will return a status code indicating whether an error was
incurred. This status code is remembered in the Open Packet (OP).
In the latter situation, the name string to be opened/created is non-null.
That is, it contains the remainder of the pathname to the file that is to
be opened or created. For this case, the routine allocates an IRP, fills
it in, and passes it to the driver for the device. The driver may then
need to take further action or it may complete the request immediately. If
it needs to perform some work asynchronously, then it can queue the request
and return a status of STATUS_PENDING. This allows this routine and its
caller to return to the user so that he can continue. Otherwise, the open/
create is basically finished.
If the driver supports symbolic links, then it is also possible for the
driver to return a new name. This name will be returned to the Object
Manager as a new name to look up. The parsing will then begin again from
the start.
It is also the responsibility of this routine to create a file object for
the file, if the name specifies a file. The file object's address is
returned to the NtCreateFile service through the OP.
Arguments:
ParseObject - Pointer to the device object the name translated into.
ObjectType - Type of the object being opened.
AccessState - Running security access state information for operation.
AccessMode - Access mode of the original caller.
Attributes - Attributes to be applied to the object.
CompleteName - Complete name of the object.
RemainingName - Remaining name of the object.
Context - Pointer to an Open Packet (OP) from NtCreateFile service.
SecurityQos - Optional security quality of service indicator.
Object - The address of a variable to receive the created file object, if
any.
Return Value:
The function return value is one of the following:
a) Success - This indicates that the function succeeded and the object
parameter contains the address of the created file object.
b) Error - This indicates that the file was not found or created and
no file object was created.
c) Reparse - This indicates that the remaining name string has been
replaced by a new name that is to be parsed.
--*/
{
PIRP irp;
PIO_STACK_LOCATION irpSp;
POPEN_PACKET op;
PFILE_OBJECT fileObject;
NTSTATUS status;
IO_STATUS_BLOCK ioStatus;
IO_SECURITY_CONTEXT securityContext;
PDEVICE_OBJECT deviceObject;
PDEVICE_OBJECT parseDeviceObject;
BOOLEAN directDeviceOpen;
PVPB vpb;
ACCESS_MASK desiredAccess;
PDUMMY_FILE_OBJECT localFileObject;
LOGICAL realFileObjectRequired;
KPROCESSOR_MODE modeForPrivilegeCheck;
ULONG retryCount = 0;
BOOLEAN relativeVolumeOpen = FALSE; // True if opening a filesystem volume
PETHREAD CurrentThread;
ULONG returnedLength;
PAGED_CODE();
UNREFERENCED_PARAMETER (ObjectType);
CurrentThread = PsGetCurrentThread ();
reparse_loop:
//
// Assume failure by setting the returned object pointer to NULL.
//
*Object = (PVOID) NULL;
//
// Get the address of the Open Packet (OP).
//
op = Context;
//
// Ensure that this routine is actually being invoked because someone is
// attempting to open a device or a file through NtCreateFile. This code
// must be invoked from there (as opposed to some other random object
// create or open routine).
//
if (op == NULL ||
op->Type != IO_TYPE_OPEN_PACKET ||
op->Size != sizeof( OPEN_PACKET )) {
return STATUS_OBJECT_TYPE_MISMATCH;
}
//
// Obtain a pointer to the parse object as a device object, which is the
// actual type of the object anyway.
//
parseDeviceObject = (PDEVICE_OBJECT) ParseObject;
//
// If we passed through a mountpoint do an extra set of validation checks
// that we don't go to a remote device. We really have to let the object manager
// open the new path. If we fail it in the path that obtains the reparse point and
// validates the name (using IoIsValidNameGraftingBuffer) its not sufficient. This is because
// the path may be valid at that time and change before OB does the reparse.
//
if (op->TraversedMountPoint) {
ASSERT (op->Information == IO_REPARSE_TAG_MOUNT_POINT);
if ((parseDeviceObject->DeviceType != FILE_DEVICE_DISK) &&
(parseDeviceObject->DeviceType != FILE_DEVICE_CD_ROM) &&
(parseDeviceObject->DeviceType != FILE_DEVICE_VIRTUAL_DISK) &&
(parseDeviceObject->DeviceType != FILE_DEVICE_TAPE)) {
status = STATUS_IO_REPARSE_DATA_INVALID;
return op->FinalStatus = status;
}
}
//
// If this is a relative open, then get the device on which the file
// is really being opened from the related file object and use that for
// the remainder of this function and for all operations performed on
// the file object that is about to be created.
//
if (op->RelatedFileObject) {
parseDeviceObject = op->RelatedFileObject->DeviceObject;
}
//
// Make sure that the device and its driver are really there and they are
// going to stay there. The object itself cannot go away just yet because
// the object management system has performed a reference which bumps the
// count of the number of reasons why the object must stick around.
// However, the driver could be attempting to unload itself, so perform
// this check. If the driver is being unloaded, then set the final status
// of the operation to "No such device" and return with a NULL file object
// pointer.
//
// Note that it is possible to "open" an exclusive device more than once
// provided that the caller is performing a relative open. This feature
// is how users "allocate" a device, and then use it to perform operations.
//
status = IopCheckDeviceAndDriver( op, parseDeviceObject );
if (!NT_SUCCESS(status)) {
return op->FinalStatus = status;
}
//
// Since ObOpenObjectByName is called without being passed
// any object type information, we need to map the generic
// bits in the DesiredAccess mask here. We also need to save
// the object's generic mapping in the access state structure
// here, because this is the earliest opportunity we have
// to do so.
//
RtlMapGenericMask( &AccessState->RemainingDesiredAccess,
&IoFileObjectType->TypeInfo.GenericMapping );
RtlMapGenericMask( &AccessState->OriginalDesiredAccess,
&IoFileObjectType->TypeInfo.GenericMapping );
SeSetAccessStateGenericMapping( AccessState, &IoFileObjectType->TypeInfo.GenericMapping );
desiredAccess = AccessState->RemainingDesiredAccess;
//
// Compute the previous mode to be passed in to the privilege check
//
if (AccessMode != KernelMode || op->Options & IO_FORCE_ACCESS_CHECK) {
modeForPrivilegeCheck = UserMode;
} else {
modeForPrivilegeCheck = KernelMode;
}
IopCheckBackupRestorePrivilege( AccessState,
&op->CreateOptions,
modeForPrivilegeCheck,
op->Disposition
);
//
// If this is not the first time through here for this object, and the
// object itself is being opened, then the desired access must also
// include the previously granted access from the last pass. Likewise,
// if the privileges have been checked already, then this is another
// pass through for a file, so add in the previously granted access.
//
if ((op->Override && !RemainingName->Length) ||
AccessState->Flags & SE_BACKUP_PRIVILEGES_CHECKED) {
desiredAccess |= AccessState->PreviouslyGrantedAccess;
}
//
// If its a filesystem volume open and we are doing a relative open to it
// then do the access check. Note that relative opens can be nested and we propagate
// the fact that the relative open is for a volume using the FO_VOLUME_OPEN flag.
//
if (op->RelatedFileObject) {
if ((op->RelatedFileObject->Flags & FO_VOLUME_OPEN) && RemainingName->Length == 0) {
relativeVolumeOpen = TRUE;
}
}
//
// Now determine what type of security check should be made. This is
// based on whether the remaining name string is null. If it is null,
// then the device itself is being opened, so a full security check is
// performed. Otherwise, only a check to ensure that the caller can
// traverse the device object is made. Note that these checks are only
// made if the caller's mode is user, or if access checking is being
// forced. Note also that if an access check was already made on the
// device itself, and this code is being executed again because of a
// reparse, then the access check need not be made the second time
// around.
//
if ((AccessMode != KernelMode || op->Options & IO_FORCE_ACCESS_CHECK) &&
(!op->RelatedFileObject || relativeVolumeOpen) &&
!op->Override) {
BOOLEAN subjectContextLocked = FALSE;
BOOLEAN accessGranted;
ACCESS_MASK grantedAccess;
//
// The caller's mode is either user or access checking is being
// forced. Perform the appropriate access check on the device
// object.
//
if (!RemainingName->Length) {
UNICODE_STRING nameString;
PPRIVILEGE_SET privileges = NULL;
//
// The device itself is being opened. Make a full security check
// to ensure that the caller has the appropriate access.
//
KeEnterCriticalRegionThread( &CurrentThread->Tcb );
ExAcquireResourceSharedLite( &IopSecurityResource, TRUE );
SeLockSubjectContext( &AccessState->SubjectSecurityContext );
subjectContextLocked = TRUE;
accessGranted = SeAccessCheck( parseDeviceObject->SecurityDescriptor,
&AccessState->SubjectSecurityContext,
subjectContextLocked,
desiredAccess,
0,
&privileges,
&IoFileObjectType->TypeInfo.GenericMapping,
UserMode,
&grantedAccess,
&status );
if (privileges) {
(VOID) SeAppendPrivileges( AccessState,
privileges );
SeFreePrivileges( privileges );
}
if (accessGranted) {
AccessState->PreviouslyGrantedAccess |= grantedAccess;
AccessState->RemainingDesiredAccess &= ~( grantedAccess | MAXIMUM_ALLOWED );
op->Override = TRUE;
}
nameString.Length = 8;
nameString.MaximumLength = 8;
nameString.Buffer = L"File";
SeOpenObjectAuditAlarm( &nameString,
parseDeviceObject,
CompleteName,
parseDeviceObject->SecurityDescriptor,
AccessState,
FALSE,
accessGranted,
UserMode,
&AccessState->GenerateOnClose );
ExReleaseResourceLite( &IopSecurityResource );
KeLeaveCriticalRegionThread( &CurrentThread->Tcb );
} else {
//
// The device is not being opened, rather, a file on the device
// is being opened or created. Therefore, only perform a check
// here for traverse access to the device.
//
//
// First determine if we have to perform traverse checking at all.
// Traverse checking only needs to be done if the device being
// traversed is a disk, or if the caller does not already have
// traverse checking privilege. Note that the former case is so
// that an administrator can turn off access to the "system
// partition", or someone would be able to install a trojan horse
// into the system by simply replacing one of the files there with
// something of their own.
//
if (!(AccessState->Flags & TOKEN_HAS_TRAVERSE_PRIVILEGE) ||
parseDeviceObject->DeviceType == FILE_DEVICE_DISK ||
parseDeviceObject->DeviceType == FILE_DEVICE_CD_ROM ) {
KeEnterCriticalRegionThread( &CurrentThread->Tcb );
ExAcquireResourceSharedLite( &IopSecurityResource, TRUE );
accessGranted = SeFastTraverseCheck( parseDeviceObject->SecurityDescriptor,
AccessState,
FILE_TRAVERSE,
UserMode );
if (!accessGranted) {
PPRIVILEGE_SET privileges = NULL;
//
// The caller was not granted traverse access through the
// normal fast path lookup. Perform a full-blown access
// check to determine whether some other ACE allows traverse
// access.
//
SeLockSubjectContext( &AccessState->SubjectSecurityContext );
subjectContextLocked = TRUE;
accessGranted = SeAccessCheck( parseDeviceObject->SecurityDescriptor,
&AccessState->SubjectSecurityContext,
subjectContextLocked,
FILE_TRAVERSE,
0,
&privileges,
&IoFileObjectType->TypeInfo.GenericMapping,
UserMode,
&grantedAccess,
&status );
if (privileges) {
(VOID) SeAppendPrivileges( AccessState,
privileges );
SeFreePrivileges( privileges );
}
}
ExReleaseResourceLite( &IopSecurityResource );
KeLeaveCriticalRegionThread( &CurrentThread->Tcb );
} else {
accessGranted = TRUE;
}
}
//
// Unlock the subject's security context so that it can be changed,
// if it was locked.
//
if (subjectContextLocked) {
SeUnlockSubjectContext( &AccessState->SubjectSecurityContext );
}
//
// Finally, determine whether or not access was granted to the device.
// If not, clean everything up and get out now without even invoking
// the device driver.
//
if (!accessGranted) {
IopDecrementDeviceObjectRef( parseDeviceObject, FALSE, FALSE );
return STATUS_ACCESS_DENIED;
}
}
realFileObjectRequired = !(op->QueryOnly || op->DeleteOnly);
if (RemainingName->Length == 0 &&
op->RelatedFileObject == NULL &&
((desiredAccess & ~(SYNCHRONIZE |
FILE_READ_ATTRIBUTES |
READ_CONTROL |
ACCESS_SYSTEM_SECURITY |
WRITE_OWNER |
WRITE_DAC)) == 0) &&
realFileObjectRequired) {
//
// If the name of the object being opened is just the name of the
// device itself, and there is no related file object, and the caller
// is opening the device for only read attributes access, then this
// device will not be mounted. This allows applications to obtain
// attributes about the device without actually mounting it.
//
// Note that if this *is* a direct device open, then the normal path
// through the I/O system and drivers may never be used, even if
// the device appears to be mounted. This is because the user may
// remove the media from the drive (even though it is mounted), and
// now attempting to determine what type of drive it is will still
// fail, this time very hard, because a whole mount process is now
// required, thus defeating this feature.
//
directDeviceOpen = TRUE;
} else {
//
// Otherwise, this is a normal open of a file, directory, device, or
// volume.
//
directDeviceOpen = FALSE;
}
//
// There are now five different cases. These are as follows:
//
// 1) This is a relative open, in which case we want to send the
// request to then same device that opened the relative file object.
//
// 2) The VPB pointer in the device object is NULL. This means that
// this device does not support a file system. This includes
// devices such as terminals, etc.
//
// 3) The VPB pointer in the device object is not NULL and:
//
// a) The VPB is "blank". That is, the VPB has never been filled
// in, which means that the device has never been mounted.
//
// b) The VPB is non-blank, but the verify flag on the device is
// set, indicating that the door to the drive may have been
// opened and the media may therefore have been changed.
//
// c) The VPB is non-blank and the verify flag is not set.
//
// Both of the latter are not explicitly checked for, as #c is
// the normal case, and #b is the responsibility of the file
// system to check.
//
//
// If this is a file system that supports volumes, vpbRefCount will
// be filled in to point to the reference count in the Vpb. Error
// exits paths later on key off this value to see if they should
// decrement the ref count. Note that a direct device open does not
// make it to the file system, so no increment is needed, and no
// decrement will be performed in objsup.c IopDeleteFile().
//
vpb = NULL;
//
// If the related open was a direct device open then we should go through the full mount
// path for this open as this may not be a direct device open.
//
if (op->RelatedFileObject && (!(op->RelatedFileObject->Flags & FO_DIRECT_DEVICE_OPEN))) {
deviceObject = (PDEVICE_OBJECT)ParseObject;
if (op->RelatedFileObject->Vpb) {
vpb = op->RelatedFileObject->Vpb;
//
// Synchronize here with the file system to make sure that
// volumes don't go away while en route to the FS.
//
IopInterlockedIncrementUlong( LockQueueIoVpbLock,
(PLONG) &vpb->ReferenceCount);
}
} else {
deviceObject = parseDeviceObject;
if (parseDeviceObject->Vpb && !directDeviceOpen) {
vpb = IopCheckVpbMounted( op,
parseDeviceObject,
RemainingName,
&status );
//
// Device object reference is decremented in IopCheckVpbMounted.
//
if ( !vpb ) {
return status;
}
//
// Set the address of the device object associated with the VPB.
//
deviceObject = vpb->DeviceObject;
}
//
// If the top deviceobject hint is set use the hint if possible.
//
if (op->InternalFlags & IOP_CREATE_USE_TOP_DEVICE_OBJECT_HINT) {
//
// You cannot use the device object hint if you are trying to
// open the device directly or if you are dealing with a device
// that is not a file system. In these cases, return an error.
//
if (directDeviceOpen ||
(deviceObject->DeviceType != FILE_DEVICE_DISK_FILE_SYSTEM &&
deviceObject->DeviceType != FILE_DEVICE_CD_ROM_FILE_SYSTEM &&
deviceObject->DeviceType != FILE_DEVICE_TAPE_FILE_SYSTEM &&
deviceObject->DeviceType != FILE_DEVICE_NETWORK_FILE_SYSTEM &&
deviceObject->DeviceType != FILE_DEVICE_DFS_FILE_SYSTEM)) {
if (vpb) {
IopDereferenceVpbAndFree( vpb );
}
IopDecrementDeviceObjectRef( parseDeviceObject, FALSE, FALSE );
return STATUS_INVALID_PARAMETER;
}
if (IopVerifyDeviceObjectOnStack(deviceObject, op->TopDeviceObjectHint)) {
deviceObject = op->TopDeviceObjectHint;
} else {
if (vpb) {
IopDereferenceVpbAndFree(vpb);
}
IopDecrementDeviceObjectRef( parseDeviceObject, FALSE, FALSE );
if (op->TraversedMountPoint) {
op->TraversedMountPoint = FALSE;
return STATUS_MOUNT_POINT_NOT_RESOLVED;
} else {
return STATUS_INVALID_DEVICE_OBJECT_PARAMETER;
}
}
} else {
//
// Walk the attached device list.
//
if (deviceObject->AttachedDevice) {
deviceObject = IoGetAttachedDevice( deviceObject );
}
}
}
//
// If the TraversedMountPoint flag is still set, clear it now. We needed
// to keep it to return the correct status if IopVerifyDeviceObjectOnStack
// failed above.
//
if (op->TraversedMountPoint) {
op->TraversedMountPoint = FALSE;
}
//
// If the driver says that the IO manager should do the access checks, lets do it here.
// We do the check against the parse device object as that device object has a name
// and we can set an ACL against it.
// We only worry about related opens of devices as the other case is taken care of in the
// filesystem.
//
if ((deviceObject->Characteristics & FILE_DEVICE_SECURE_OPEN) &&
(AccessMode != KernelMode || op->Options & IO_FORCE_ACCESS_CHECK) &&
(op->RelatedFileObject || RemainingName->Length) && (!relativeVolumeOpen)) {
BOOLEAN subjectContextLocked = FALSE;
BOOLEAN accessGranted;
ACCESS_MASK grantedAccess;
UNICODE_STRING nameString;
PPRIVILEGE_SET privileges = NULL;
//
// If the device wants to ensure secure opens then lets check the two
// cases which were skipped earlier. These cases are if its a relative
// open or if there are trailing names.
//
KeEnterCriticalRegionThread( &CurrentThread->Tcb );
ExAcquireResourceSharedLite( &IopSecurityResource, TRUE );
SeLockSubjectContext( &AccessState->SubjectSecurityContext );
subjectContextLocked = TRUE;
accessGranted = SeAccessCheck( parseDeviceObject->SecurityDescriptor,
&AccessState->SubjectSecurityContext,
subjectContextLocked,
desiredAccess,
0,
&privileges,
&IoFileObjectType->TypeInfo.GenericMapping,
UserMode,
&grantedAccess,
&status );
if (privileges) {
(VOID) SeAppendPrivileges( AccessState,
privileges );
SeFreePrivileges( privileges );
}
if (accessGranted) {
AccessState->PreviouslyGrantedAccess |= grantedAccess;
AccessState->RemainingDesiredAccess &= ~( grantedAccess | MAXIMUM_ALLOWED );
}
nameString.Length = 8;
nameString.MaximumLength = 8;
nameString.Buffer = L"File";
SeOpenObjectAuditAlarm( &nameString,
deviceObject,
CompleteName,
parseDeviceObject->SecurityDescriptor,
AccessState,
FALSE,
accessGranted,
UserMode,
&AccessState->GenerateOnClose );
SeUnlockSubjectContext( &AccessState->SubjectSecurityContext );
ExReleaseResourceLite( &IopSecurityResource );
KeLeaveCriticalRegionThread( &CurrentThread->Tcb );
if (!accessGranted) {
IopDecrementDeviceObjectRef( parseDeviceObject, FALSE, FALSE );
if (vpb) {
IopDereferenceVpbAndFree(vpb);
}
return STATUS_ACCESS_DENIED;
}
}
//
// Allocate and fill in the I/O Request Packet (IRP) to use in interfacing
// to the driver. The allocation is done using an exception handler in
// case the caller does not have enough quota to allocate the packet.
//
irp = IopAllocateIrp( deviceObject->StackSize, FALSE );
if (!irp) {
//
// An IRP could not be allocated. Cleanup and return an appropriate
// error status code.
//
IopDecrementDeviceObjectRef( parseDeviceObject, FALSE, FALSE );
if (vpb) {
IopDereferenceVpbAndFree(vpb);
}
return STATUS_INSUFFICIENT_RESOURCES;
}
irp->Tail.Overlay.Thread = CurrentThread;
irp->RequestorMode = AccessMode;
irp->Flags = IRP_CREATE_OPERATION | IRP_SYNCHRONOUS_API | IRP_DEFER_IO_COMPLETION;
securityContext.SecurityQos = SecurityQos;
securityContext.AccessState = AccessState;
securityContext.DesiredAccess = desiredAccess;
securityContext.FullCreateOptions = op->CreateOptions;
//
// Get a pointer to the stack location for the first driver. This is where
// the original function codes and parameters are passed.
//
irpSp = IoGetNextIrpStackLocation( irp );
irpSp->Control = 0;
if (op->CreateFileType == CreateFileTypeNone) {
//
// This is a normal file open or create function.
//
irpSp->MajorFunction = IRP_MJ_CREATE;
irpSp->Parameters.Create.EaLength = op->EaLength;
irpSp->Flags = (UCHAR) op->Options;
if (!(Attributes & OBJ_CASE_INSENSITIVE)) {
irpSp->Flags |= SL_CASE_SENSITIVE;
}
} else if (op->CreateFileType == CreateFileTypeNamedPipe) {
//
// A named pipe is being created.
//
irpSp->MajorFunction = IRP_MJ_CREATE_NAMED_PIPE;
irpSp->Parameters.CreatePipe.Parameters = op->ExtraCreateParameters;
} else {
//
// A mailslot is being created.
//
irpSp->MajorFunction = IRP_MJ_CREATE_MAILSLOT;
irpSp->Parameters.CreateMailslot.Parameters = op->ExtraCreateParameters;
}
//
// Also fill in the NtCreateFile service's caller's parameters.
//
irp->Overlay.AllocationSize = op->AllocationSize;
irp->AssociatedIrp.SystemBuffer = op->EaBuffer;
irpSp->Parameters.Create.Options = (op->Disposition << 24) | (op->CreateOptions & 0x00ffffff);
irpSp->Parameters.Create.FileAttributes = op->FileAttributes;
irpSp->Parameters.Create.ShareAccess = op->ShareAccess;
irpSp->Parameters.Create.SecurityContext = &securityContext;
//
// Fill in local parameters so this routine can determine when the I/O is
// finished, and the normal I/O completion code will not get any errors.
//
irp->UserIosb = &ioStatus;
irp->MdlAddress = (PMDL) NULL;
irp->PendingReturned = FALSE;
irp->Cancel = FALSE;
irp->UserEvent = (PKEVENT) NULL;
irp->CancelRoutine = (PDRIVER_CANCEL) NULL;
irp->Tail.Overlay.AuxiliaryBuffer = (PVOID) NULL;
//
// Allocate and initialize the file object that will be used in dealing
// with the device for the remainder of this session with the user. How
// the file object is allocated is based on whether or not a real file
// object is actually required. It is not required for the query and
// delete only operations.
//
if (realFileObjectRequired) {
OBJECT_ATTRIBUTES objectAttributes;
ULONG fileObjectSize;
//
// A real, full-blown file object is actually required.
//
InitializeObjectAttributes( &objectAttributes,
(PUNICODE_STRING) NULL,
Attributes,
(HANDLE) NULL,
(PSECURITY_DESCRIPTOR) NULL
);
if (op->InternalFlags &
(IOP_CREATE_USE_TOP_DEVICE_OBJECT_HINT|IOP_CREATE_IGNORE_SHARE_ACCESS_CHECK)) {
fileObjectSize = sizeof(FILE_OBJECT) + sizeof(IOP_FILE_OBJECT_EXTENSION);
} else {
fileObjectSize = sizeof(FILE_OBJECT);
}
status = ObCreateObject( KernelMode,
IoFileObjectType,
&objectAttributes,
AccessMode,
(PVOID) NULL,
fileObjectSize,
0,
0,
(PVOID *) &fileObject );
if (!NT_SUCCESS( status )) {
IoFreeIrp( irp );
IopDecrementDeviceObjectRef( parseDeviceObject, FALSE, FALSE );
if (vpb) {
IopDereferenceVpbAndFree(vpb);
}
return op->FinalStatus = status;
}
IopPerfLogFileCreate(fileObject, CompleteName);
RtlZeroMemory( fileObject, sizeof( FILE_OBJECT ) );
if (op->CreateOptions & (FILE_SYNCHRONOUS_IO_ALERT | FILE_SYNCHRONOUS_IO_NONALERT)) {
fileObject->Flags = FO_SYNCHRONOUS_IO;
if (op->CreateOptions & FILE_SYNCHRONOUS_IO_ALERT) {
fileObject->Flags |= FO_ALERTABLE_IO;
}
}
if (op->InternalFlags &
(IOP_CREATE_USE_TOP_DEVICE_OBJECT_HINT|IOP_CREATE_IGNORE_SHARE_ACCESS_CHECK)) {
PIOP_FILE_OBJECT_EXTENSION fileObjectExtension;
fileObject->Flags |= FO_FILE_OBJECT_HAS_EXTENSION;
fileObjectExtension = (PIOP_FILE_OBJECT_EXTENSION)(fileObject + 1);
fileObjectExtension->FileObjectExtensionFlags = 0;
fileObjectExtension->TopDeviceObjectHint = NULL;
fileObjectExtension->FilterContext = NULL;
if (op->InternalFlags & IOP_CREATE_USE_TOP_DEVICE_OBJECT_HINT) {
fileObjectExtension->TopDeviceObjectHint = deviceObject;
}
if (op->InternalFlags & IOP_CREATE_IGNORE_SHARE_ACCESS_CHECK) {
fileObjectExtension->FileObjectExtensionFlags |=FO_EXTENSION_IGNORE_SHARE_ACCESS_CHECK;
}
}
//
// Now fill in the file object as best is possible at this point and set
// a pointer to it in the IRP so everyone else can find it.
//
if (fileObject->Flags & FO_SYNCHRONOUS_IO) {
KeInitializeEvent( &fileObject->Lock, SynchronizationEvent, FALSE );
fileObject->Waiters = 0;
fileObject->CurrentByteOffset.QuadPart = 0;
}
if (op->CreateOptions & FILE_NO_INTERMEDIATE_BUFFERING) {
fileObject->Flags |= FO_NO_INTERMEDIATE_BUFFERING;
}
if (op->CreateOptions & FILE_WRITE_THROUGH) {
fileObject->Flags |= FO_WRITE_THROUGH;
}
if (op->CreateOptions & FILE_SEQUENTIAL_ONLY) {
fileObject->Flags |= FO_SEQUENTIAL_ONLY;
}
if (op->CreateOptions & FILE_RANDOM_ACCESS) {
fileObject->Flags |= FO_RANDOM_ACCESS;
}
} else {
//
// This is either a quick delete or query operation. For these cases,
// it is possible to optimize the Object Manager out of the picture by
// simply putting together something that "looks" like a file object,
// and then operating on it.
//
localFileObject = op->LocalFileObject;
RtlZeroMemory( localFileObject, sizeof( DUMMY_FILE_OBJECT ) );
fileObject = (PFILE_OBJECT) &localFileObject->ObjectHeader.Body;
localFileObject->ObjectHeader.Type = IoFileObjectType;
localFileObject->ObjectHeader.PointerCount = 1;
}
if (directDeviceOpen) {
fileObject->Flags |= FO_DIRECT_DEVICE_OPEN;
}
if (!(Attributes & OBJ_CASE_INSENSITIVE)) {
fileObject->Flags |= FO_OPENED_CASE_SENSITIVE;
}
fileObject->Type = IO_TYPE_FILE;
fileObject->Size = sizeof( FILE_OBJECT );
fileObject->RelatedFileObject = op->RelatedFileObject;
fileObject->DeviceObject = parseDeviceObject;
irp->Tail.Overlay.OriginalFileObject = fileObject;
irpSp->FileObject = fileObject;
//
// Allocate a file name string buffer which is large enough to contain
// the entire remaining name string and initialize the maximum length.
//
if (RemainingName->Length) {
fileObject->FileName.MaximumLength = RoundNameSize( RemainingName->Length );
fileObject->FileName.Buffer = ExAllocatePoolWithTag( PagedPool,
fileObject->FileName.MaximumLength,
'mNoI' );
if (!fileObject->FileName.Buffer) {
IoFreeIrp( irp );
IopDecrementDeviceObjectRef( parseDeviceObject, FALSE, FALSE );
if (vpb) {
IopDereferenceVpbAndFree(vpb);
}
fileObject->DeviceObject = (PDEVICE_OBJECT) NULL;
if (realFileObjectRequired) {
ObDereferenceObject( fileObject );
}
return STATUS_INSUFFICIENT_RESOURCES;
}
}
//
// Now copy the name string into the file object from the remaining name
// that is being reparsed. If the driver decides to reparse, then it must
// replace this name.
//
RtlCopyUnicodeString( &fileObject->FileName, RemainingName );
//
// Before invoking the driver's open routine, check to see whether or not
// this is a fast network attributes query and, if so, and the driver
// implements the function, attempt to call it here.
//
if (op->QueryOnly) {
PFAST_IO_DISPATCH fastIoDispatch = deviceObject->DriverObject->FastIoDispatch;
BOOLEAN result;
if (fastIoDispatch &&
fastIoDispatch->SizeOfFastIoDispatch > FIELD_OFFSET( FAST_IO_DISPATCH, FastIoQueryOpen ) &&
fastIoDispatch->FastIoQueryOpen) {
IoSetNextIrpStackLocation( irp );
irpSp->DeviceObject = deviceObject;
result = (fastIoDispatch->FastIoQueryOpen)( irp,
op->NetworkInformation,
deviceObject );
if (result) {
op->FinalStatus = irp->IoStatus.Status;
op->Information = irp->IoStatus.Information;
//
// The operation worked, so simply dereference and free the
// resources acquired up to this point.
//
if ((op->FinalStatus == STATUS_REPARSE) &&
irp->Tail.Overlay.AuxiliaryBuffer) {
ASSERT( op->Information > IO_REPARSE_TAG_RESERVED_ONE );
ExFreePool( irp->Tail.Overlay.AuxiliaryBuffer );
irp->Tail.Overlay.AuxiliaryBuffer = NULL;
op->RelatedFileObject = (PFILE_OBJECT) NULL;
}
if (fileObject->FileName.Length) {
ExFreePool( fileObject->FileName.Buffer );
}
IopDecrementDeviceObjectRef( parseDeviceObject, FALSE, FALSE );
if (vpb) {
IopDereferenceVpbAndFree(vpb);
}
#if DBG
irp->CurrentLocation = irp->StackCount + 2;
#endif // DBG
IoFreeIrp( irp );
//
// Finally, indicate that the parse routine was actually
// invoked and that the information returned herein can be
// used.
//
op->ParseCheck = OPEN_PACKET_PATTERN;
status = STATUS_SUCCESS;
if (!op->FullAttributes) {
try {
op->BasicInformation->FileAttributes = op->NetworkInformation->FileAttributes;
} except(EXCEPTION_EXECUTE_HANDLER) {
status = GetExceptionCode();
}
}
return status;
} else {
//
// The fast I/O operation did not work, so take the longer
// route.
//
irp->Tail.Overlay.CurrentStackLocation++;
irp->CurrentLocation++;
}
}
}
//
// Finally, initialize the file object's event to the Not Signaled state
// and remember that a file object was created.
//
KeInitializeEvent( &fileObject->Event, NotificationEvent, FALSE );
op->FileObject = fileObject;
//
// Insert the packet at the head of the IRP list for the thread.
//
IopQueueThreadIrp( irp );
//
// Now invoke the driver itself to open the file.
//
status = IoCallDriver( deviceObject, irp );
//
// One of four things may have happened when the driver was invoked:
//
// 1. The I/O operation is pending (Status == STATUS_PENDING). This can
// occur on devices which need to perform some sort of device
// manipulation (such as opening a file for a file system).
//
// 2. The driver returned an error (Status < 0). This occurs when either
// a supplied parameter was in error, or the device or file system
// incurred or discovered an error.
//
// 3. The operation ended in a reparse (Status == STATUS_REPARSE). This
// occurs when a file system opens the file, only to discover that it
// represents a symbolic link.
//
// 4. The operation is complete and was successful (Status ==
// STATUS_SUCCESS). Note that for this case the only action is to
// return a pointer to the file object.
//
if (status == STATUS_PENDING) {
(VOID) KeWaitForSingleObject( &fileObject->Event,
Executive,
KernelMode,
FALSE,
(PLARGE_INTEGER) NULL );
status = ioStatus.Status;
} else {
//
// The I/O operation was completed without returning a status of
// pending. This means that at this point, the IRP has not been
// fully completed. Complete it now.
//
KIRQL irql;
ASSERT( !irp->PendingReturned );
ASSERT( !irp->MdlAddress );
//
// In the case of name junctions do the transmogrify work.
//
if (irp->IoStatus.Status == STATUS_REPARSE &&
irp->IoStatus.Information == IO_REPARSE_TAG_MOUNT_POINT ) {
PREPARSE_DATA_BUFFER reparseBuffer = NULL;
ASSERT ( irp->Tail.Overlay.AuxiliaryBuffer != NULL );
reparseBuffer = (PREPARSE_DATA_BUFFER) irp->Tail.Overlay.AuxiliaryBuffer;
ASSERT( reparseBuffer->ReparseTag == IO_REPARSE_TAG_MOUNT_POINT );
ASSERT( reparseBuffer->ReparseDataLength < MAXIMUM_REPARSE_DATA_BUFFER_SIZE );
ASSERT( reparseBuffer->Reserved < MAXIMUM_REPARSE_DATA_BUFFER_SIZE );
IopDoNameTransmogrify( irp,
fileObject,
reparseBuffer );
}
//
// Now finish up the request.
//
KeRaiseIrql( APC_LEVEL, &irql );
//
// Note that normally the system would simply call IopCompleteRequest
// here to complete the packet. However, because this is a create
// operation, several assumptions can be made that make it much faster
// to perform the couple of operations that completing the request
// would perform. These include: copying the I/O status block,
// dequeueing the IRP and freeing it, and setting the file object's
// event to the signalled state. The latter is done here by hand,
// since it is known that it is not possible for any thread to be
// waiting on the event.
//
ioStatus = irp->IoStatus;
status = ioStatus.Status;
fileObject->Event.Header.SignalState = 1;
IopDequeueThreadIrp( irp );
//
// The SystemBuffer is in some cases used by the driver, and
// needs to be freed if present.
//
if ((irp->Flags & IRP_BUFFERED_IO) && (irp->Flags & IRP_DEALLOCATE_BUFFER)) {
ExFreePool(irp->AssociatedIrp.SystemBuffer);
}
IoFreeIrp( irp );
KeLowerIrql( irql );
}
//
// Copy the information field of the I/O status block back to the
// original caller in case it is required.
//
op->Information = ioStatus.Information;
if (!NT_SUCCESS( status )) {
int openCancelled;
//
// The operation ended in an error. Kill the file object, dereference
// the device object, and return a null pointer.
//
if (fileObject->FileName.Length) {
ExFreePool( fileObject->FileName.Buffer );
fileObject->FileName.Length = 0;
}
fileObject->DeviceObject = (PDEVICE_OBJECT) NULL;
openCancelled = (fileObject->Flags & FO_FILE_OPEN_CANCELLED);
if (realFileObjectRequired) {
ObDereferenceObject( fileObject );
}
op->FileObject = (PFILE_OBJECT) NULL;
IopDecrementDeviceObjectRef( parseDeviceObject, FALSE, FALSE );
if ((!openCancelled) && (vpb )) {
IopDereferenceVpbAndFree(vpb);
}
return op->FinalStatus = status;
} else if (status == STATUS_REPARSE) {
//
// The operation resulted in a reparse. This means that the file
// name in the file object is the new name to be looked up. Replace
// the complete name string with the new name and return STATUS_REPARSE
// so the object manager knows to start over again. Note, however,
// that the file name buffer in the file object itself is kept intact
// so that it can be reused when coming back here again.
//
// A reparse status may also have been returned from the file system if
// the volume that was in a drive needed to have been verified, but
// the verification failed, and a new volume was mounted. In this
// case, everything starts over again using the new volume.
//
ASSERT( IO_REPARSE == IO_REPARSE_TAG_RESERVED_ZERO );
if ((ioStatus.Information == IO_REPARSE) ||
(ioStatus.Information == IO_REPARSE_TAG_MOUNT_POINT)) {
//
// If the complete name buffer isn't large enough, reallocate it.
//
if (CompleteName->MaximumLength < fileObject->FileName.Length) {
PVOID buffer;
buffer = ExAllocatePoolWithTag( PagedPool,
fileObject->FileName.Length,
'cFoI' );
if (!buffer) {
return op->FinalStatus = STATUS_INSUFFICIENT_RESOURCES;
} else {
if (CompleteName->Buffer) {
ExFreePool( CompleteName->Buffer );
}
CompleteName->Buffer = buffer;
CompleteName->MaximumLength = fileObject->FileName.Length;
}
}
RtlCopyUnicodeString( CompleteName, &fileObject->FileName );
//
// For NTFS directory junction points we NULL the RelatedFileObject.
// If the prior call was a relative open, the subsequent one will
// not be.
//
if (ioStatus.Information == IO_REPARSE_TAG_MOUNT_POINT) {
op->RelatedFileObject = (PFILE_OBJECT) NULL;
}
}
//
// Kill the file object, dereference the device object, and return a
// null pointer.
//
if (fileObject->FileName.Length) {
ExFreePool( fileObject->FileName.Buffer );
fileObject->FileName.Length = 0;
}
fileObject->DeviceObject = (PDEVICE_OBJECT) NULL;
if (realFileObjectRequired) {
ObDereferenceObject( fileObject );
}
op->FileObject = (PFILE_OBJECT) NULL;
IopDecrementDeviceObjectRef( parseDeviceObject, FALSE, FALSE );
if (vpb) {
IopDereferenceVpbAndFree(vpb);
}
ASSERT( IO_REMOUNT == IO_REPARSE_TAG_RESERVED_ONE );
if (ioStatus.Information == IO_REPARSE_TAG_RESERVED_ONE) {
//
// If we are reparsing to verify a volume, restart the reparse
// by attempting to parse the device once again. Note that it
// would be best to simply recurse, but it's not possible since
// there is a limited amount of stack available to kernel mode
// and a limit needs to be enforced for the number of times that
// verify reparse can occur.
//
if (++retryCount > IO_MAX_REMOUNT_REPARSE_ATTEMPTS) {
return STATUS_UNSUCCESSFUL;
}
goto reparse_loop;
} else {
//
// Really reparsing a symbolic link, so go back to the object
// manager so it can begin the parse from the top.
//
op->RelatedFileObject = (PFILE_OBJECT) NULL;
//
// Note that the mountpoint should be set only for the correct
// tag. IO_REMOUNT is sent by FAT,CDFS and UDFS to remount a volume.
// IO_REPARSE is set by the network filesystems to just use a different path.
//
if (ioStatus.Information == IO_REPARSE_TAG_MOUNT_POINT) {
op->TraversedMountPoint = TRUE;
}
//
// No driver should return this status for a volume open.
// If they do then we would skip the security check as override is true.
// To catch that case we bugcheck here.
//
if (op->Override) {
KeBugCheckEx(DRIVER_RETURNED_STATUS_REPARSE_FOR_VOLUME_OPEN,
(ULONG_PTR)parseDeviceObject,
(ULONG_PTR)deviceObject,
(ULONG_PTR)CompleteName,
(ULONG_PTR)ioStatus.Information
);
}
return STATUS_REPARSE;
}
} else {
//
// The operation was successful. The first thing to do is to see if
// the device that processed the open also opened the file. If
// not, we need to adjust the vpb reference counts. Then, if this is
// not a query or a delete, but rather a normal open/create, return
// the address of the FileObject to the caller and set the
// information returned in the original requestor's I/O status block.
// Also set the value of the parse check field in the open packet to
// a value which will let the caller know that this routine was
// successful in creating the file object. Finally, return the status
// of the operation to the caller.
//
PDEVICE_OBJECT deviceObjectThatOpenedFile;
deviceObjectThatOpenedFile = IoGetRelatedDeviceObject(fileObject);
if (deviceObject != deviceObjectThatOpenedFile) {
PVPB newVpb;
//
// The device that opened the related file is not the one
// that opened this file. So, readjust the vpb reference
// counts.
//
newVpb = fileObject->Vpb;
//
// If the new VPB is not the same as the original VPB
// then reference the new one before freeing the old one.
// If a filter was just added to the fileobject stack the VPB will not
// change. So check for a difference in VPB as well.
//
if (newVpb != vpb) {
if (newVpb) {
IopInterlockedIncrementUlong( LockQueueIoVpbLock,
(PLONG) &newVpb->ReferenceCount);
}
if (vpb) {
IopDereferenceVpbAndFree(vpb);
}
}
}
if (realFileObjectRequired) {
*Object = fileObject;
op->ParseCheck = OPEN_PACKET_PATTERN;
//
// Add a reference so the file object cannot go away before
// the create routine gets chance to flag the object for handle
// create.
//
ObReferenceObject( fileObject );
//
// If the filename length is zero and its not a relative open or
// its a relative open to a volume open then set the volume open flag.
// Also set it only for filesystem device object volume.
//
if ((!fileObject->RelatedFileObject || fileObject->RelatedFileObject->Flags & FO_VOLUME_OPEN) &&
(!fileObject->FileName.Length)) {
switch (deviceObjectThatOpenedFile->DeviceType) {
case FILE_DEVICE_DISK_FILE_SYSTEM:
case FILE_DEVICE_CD_ROM_FILE_SYSTEM:
case FILE_DEVICE_TAPE_FILE_SYSTEM:
case FILE_DEVICE_FILE_SYSTEM:
fileObject->Flags |= FO_VOLUME_OPEN;
break;
default:
break;
}
}
return op->FinalStatus = ioStatus.Status;
} else {
//
// This is either a quick query or delete operation. Determine
// which it is and quickly perform the operation.
//
if (op->QueryOnly) {
PFAST_IO_DISPATCH fastIoDispatch;
BOOLEAN queryResult = FALSE;
fastIoDispatch = deviceObjectThatOpenedFile->DriverObject->FastIoDispatch;
if (!op->FullAttributes) {
PFILE_BASIC_INFORMATION basicInfo = NULL;
//
// This is a simple FAT file attribute query. Attempt to
// obtain the basic information about the file.
//
try {
if (fastIoDispatch && fastIoDispatch->FastIoQueryBasicInfo) {
queryResult = fastIoDispatch->FastIoQueryBasicInfo(
fileObject,
TRUE,
op->BasicInformation,
&ioStatus,
deviceObjectThatOpenedFile
);
}
if (!queryResult) {
basicInfo = ExAllocatePool( NonPagedPool,
sizeof( FILE_BASIC_INFORMATION ) );
if (basicInfo) {
status = IoQueryFileInformation(
fileObject,
FileBasicInformation,
sizeof( FILE_BASIC_INFORMATION ),
basicInfo,
&returnedLength
);
if (NT_SUCCESS( status )) {
RtlCopyMemory( op->BasicInformation,
basicInfo,
returnedLength );
}
ExFreePool( basicInfo );
} else {
status = STATUS_INSUFFICIENT_RESOURCES;
}
} else {
status = ioStatus.Status;
}
} except(EXCEPTION_EXECUTE_HANDLER) {
if (basicInfo) {
ExFreePool( basicInfo );
}
status = GetExceptionCode();
}
} else {
//
// This is a full attribute query. Attempt to obtain the
// full network attributes for the file. This includes
// both the basic and standard information about the
// file. Try the fast path first, if it exists.
//
if (fastIoDispatch &&
fastIoDispatch->SizeOfFastIoDispatch > FIELD_OFFSET( FAST_IO_DISPATCH, FastIoQueryNetworkOpenInfo ) &&
fastIoDispatch->FastIoQueryNetworkOpenInfo) {
queryResult = fastIoDispatch->FastIoQueryNetworkOpenInfo(
fileObject,
TRUE,
op->NetworkInformation,
&ioStatus,
deviceObjectThatOpenedFile
);
}
if (!queryResult) {
//
// Either the fast dispatch routine did not exist, or
// it simply wasn't callable at this time. Attempt to
// obtain all of the information at once via an IRP-
// based call.
//
status = IoQueryFileInformation(
fileObject,
FileNetworkOpenInformation,
sizeof( FILE_NETWORK_OPEN_INFORMATION ),
op->NetworkInformation,
&returnedLength
);
if (!NT_SUCCESS( status )) {
if (status == STATUS_INVALID_PARAMETER ||
status == STATUS_NOT_IMPLEMENTED) {
status = IopGetNetworkOpenInformation(fileObject, op);
}
}
}
}
} else {
//
// There is nothing to do for a quick delete since the caller
// set the FILE_DELETE_ON_CLOSE CreateOption so it is already
// set in the file system.
//
NOTHING;
}
op->ParseCheck = OPEN_PACKET_PATTERN;
if (realFileObjectRequired) {
ObDereferenceObject( fileObject );
} else {
IopDeleteFile( fileObject );
}
op->FileObject = (PFILE_OBJECT) NULL;
op->FinalStatus = status;
return status;
}
}
}
NTSTATUS
IopGetNetworkOpenInformation(
IN PFILE_OBJECT FileObject,
IN POPEN_PACKET Op
)
/*++
Routine Description:
This routines gets the network information in two steps.
Its called out as a separate routine from IopParseDevice to save stack for
common create paths.
Arguments:
FileObject - Pointer to the fileobject for the opened file.
Op - Pointer to the open packet.
Return Value:
NTSTATUS
--*/
{
#define COPY_ATTRIBUTES( n, b, s ) { \
(n)->CreationTime.QuadPart = (b)->CreationTime.QuadPart; \
(n)->LastAccessTime.QuadPart = (b)->LastAccessTime.QuadPart; \
(n)->LastWriteTime.QuadPart = (b)->LastWriteTime.QuadPart; \
(n)->ChangeTime.QuadPart = (b)->ChangeTime.QuadPart; \
(n)->AllocationSize.QuadPart = (s)->AllocationSize.QuadPart; \
(n)->EndOfFile.QuadPart = (s)->EndOfFile.QuadPart; \
(n)->FileAttributes = (b)->FileAttributes; }
FILE_BASIC_INFORMATION basicInfo;
FILE_STANDARD_INFORMATION stdInfo;
ULONG returnedLength;
NTSTATUS status;
PAGED_CODE();
//
// The IRP-based call did not work either, so
// simply try to obtain the information by
// doing IRP-based queries for the basic and
// standard information and piecing together
// the results into the caller's buffer. Note
// that it might be possible to perform fast
// I/O operations to get the data, but it
// might also fail because of the above. So
// simply query the information the long way.
//
status = IoQueryFileInformation(
FileObject,
FileBasicInformation,
sizeof( FILE_BASIC_INFORMATION ),
&basicInfo,
&returnedLength
);
if (NT_SUCCESS( status )) {
status = IoQueryFileInformation(
FileObject,
FileStandardInformation,
sizeof( FILE_STANDARD_INFORMATION ),
&stdInfo,
&returnedLength
);
if (NT_SUCCESS( status )) {
COPY_ATTRIBUTES( Op->NetworkInformation,
&basicInfo,
&stdInfo );
}
}
return status;
}
NTSTATUS
IopParseFile(
IN PVOID ParseObject,
IN PVOID ObjectType,
IN 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
)
/*++
Routine Description:
This routine interfaces to the NT Object Manager. It is invoked when
the object system is given the name of an entity to create or open and is
also given a handle to a directory file object that the operation is to be
performed relative to. This routine is specified as the parse routine for
all file objects.
This routine simply invokes the parse routine for the appropriate device
that is associated with the file object. It is the responsibility of that
routine to perform the operation.
Arguments:
ParseObject - Pointer to the file object that the name is to be opened or
created relative to.
ObjectType - Type of the object being opened.
AccessState - Running security access state information for operation.
AccessMode - Access mode of the original caller.
Attributes - Attributes to be applied to the object.
CompleteName - Complete name of the object.
RemainingName - Remaining name of the object.
Context - Pointer to an Open Packet (OP) from NtCreateFile service.
SecurityQos - Supplies a pointer to the captured QOS information
if available.
Object - The address of a variable to receive the created file object, if
any.
Return Value:
The function return value is one of the following:
a) Success - This indicates that the function succeeded and the object
parameter contains the address of the created file object.
b) Error - This indicates that the file was not found or created and
no file object was created.
c) Reparse - This indicates that the remaining name string has been
replaced by a new name that is to be parsed.
--*/
{
PDEVICE_OBJECT deviceObject;
POPEN_PACKET op;
PAGED_CODE();
//
// Get the address of the Open Packet (OP).
//
op = (POPEN_PACKET) Context;
//
// Ensure that this routine is actually being invoked because someone is
// attempting to open a device or a file through NtCreateFile. This code
// must be invoked from there (as opposed to some other random object
// create or open routine).
//
if (op == NULL ||
op->Type != IO_TYPE_OPEN_PACKET ||
op->Size != sizeof( OPEN_PACKET )) {
return STATUS_OBJECT_TYPE_MISMATCH;
}
//
// Get a pointer to the device object for this file.
//
deviceObject = IoGetRelatedDeviceObject( (PFILE_OBJECT) ParseObject );
//
// Pass the related file object to the device object parse routine.
//
op->RelatedFileObject = (PFILE_OBJECT) ParseObject;
//
// Open or create the specified file.
//
return IopParseDevice( deviceObject,
ObjectType,
AccessState,
AccessMode,
Attributes,
CompleteName,
RemainingName,
Context,
SecurityQos,
Object );
}
NTSTATUS
IopQueryNameInternal(
IN PVOID Object,
IN BOOLEAN HasObjectName,
IN BOOLEAN UseDosDeviceName,
OUT POBJECT_NAME_INFORMATION ObjectNameInfo,
IN ULONG Length,
OUT PULONG ReturnLength,
IN KPROCESSOR_MODE Mode
)
/*++
Routine Description:
This function implements the query name procedure for the Object Manager
for querying the names of file objects.
Arguments:
Object - Pointer to the file object whose name is to be retrieved.
HasObjectName - Indicates whether or not the object has a name.
UseDosDeviceName - Indicates whether to translate the device object part
of the fileobject into the dosdevice name space or the
regular \device namespace via ob
ObjectNameInfo - Buffer in which to return the name.
Length - Specifies the length of the output buffer, in bytes.
ReturnLength - Specifies the number of bytes actually returned in the
output buffer or the number of bytes needed if Length is smaller
than needed.
Return Value:
The function return value is the final status of the query operation.
--*/
{
NTSTATUS status;
ULONG lengthNeeded;
PFILE_OBJECT fileObject;
PUCHAR buffer;
PWSTR p;
POBJECT_NAME_INFORMATION deviceNameInfo;
PFILE_NAME_INFORMATION fileNameInfo;
ULONG length;
BOOLEAN deviceNameOverflow;
BOOLEAN dosLookupSuccess = 0;
UNREFERENCED_PARAMETER( HasObjectName );
PAGED_CODE();
ASSERT( FIELD_OFFSET( FILE_NAME_INFORMATION, FileName ) < sizeof( OBJECT_NAME_INFORMATION ) );
//
// Ensure that the size of the output buffer is at least the minimum
// size required to include the basic object name information structure.
//
if (Length < sizeof( OBJECT_NAME_INFORMATION )) {
*ReturnLength = sizeof(OBJECT_NAME_INFORMATION);
return STATUS_INFO_LENGTH_MISMATCH;
}
//
// Begin by allocating a buffer in which to build the name of the file.
//
buffer = ExAllocatePoolWithTag( PagedPool, Length, ' oI' );
if (!buffer) {
return STATUS_INSUFFICIENT_RESOURCES;
}
//
// Query the name of the device on which the file is open.
//
fileObject = (PFILE_OBJECT) Object;
deviceNameInfo = (POBJECT_NAME_INFORMATION) buffer;
if (UseDosDeviceName) {
if (fileObject->DeviceObject->DeviceType == FILE_DEVICE_NETWORK_FILE_SYSTEM) {
lengthNeeded = sizeof(OBJECT_NAME_INFORMATION) + 2*sizeof(WCHAR); // For the extra '\' and '\0'
if (lengthNeeded > Length) {
status = STATUS_BUFFER_OVERFLOW;
} else {
status = STATUS_SUCCESS;
}
deviceNameInfo->Name.Length = sizeof(WCHAR);
deviceNameInfo->Name.MaximumLength = sizeof(WCHAR);
p = (PWSTR) (deviceNameInfo + 1);
*p = '\\'; // Start with a '\' as RDR does not return the extra
deviceNameInfo->Name.Buffer = p;
} else {
status = IoVolumeDeviceToDosName( fileObject->DeviceObject, &deviceNameInfo->Name );
lengthNeeded = sizeof(OBJECT_NAME_INFORMATION) + deviceNameInfo->Name.Length + sizeof(WCHAR);
}
//
// If querying the dos name fails try to atleast get the real device name
//
if (!NT_SUCCESS(status)) {
status = ObQueryNameString( (PVOID) fileObject->DeviceObject,
deviceNameInfo,
Length,
&lengthNeeded );
} else {
dosLookupSuccess++;
}
} else {
status = ObQueryNameString( (PVOID) fileObject->DeviceObject,
deviceNameInfo,
Length,
&lengthNeeded );
}
if (!NT_SUCCESS( status )) {
if (status != STATUS_INFO_LENGTH_MISMATCH) {
return status;
}
}
//
// Ensure that there is enough room in the output buffer to return the
// name and copy it.
//
p = (PWSTR) (ObjectNameInfo + 1);
//
// If we got a DOS name, note the name isn't contiguous to the device name info,
// and that we should free it (the Rtl call did not know we allocated this big
// buffer, and made a new one).
//
try {
if (UseDosDeviceName && dosLookupSuccess) {
ULONG BaseCopyLength;
ULONG NameCopyLength;
//
// We will never take an exception in this path. That's why we don't have to free
// the device name buffer in the exception handler.
//
ASSERT(Mode == KernelMode);
//
// Figure out how much of each part we can copy.
//
BaseCopyLength = sizeof(UNICODE_STRING);
if ( Length < lengthNeeded ) {
if ( Length < sizeof(UNICODE_STRING)) {
BaseCopyLength = Length;
NameCopyLength = 0;
} else {
NameCopyLength = Length - BaseCopyLength;
}
} else {
NameCopyLength = deviceNameInfo->Name.Length;
}
//
// Copy in two parts - the base chunk of the UNICODE_STRING and then
// as much of the name as will fit.
//
RtlCopyMemory( ObjectNameInfo,
deviceNameInfo,
BaseCopyLength );
RtlCopyMemory( p,
deviceNameInfo->Name.Buffer,
NameCopyLength );
if (fileObject->DeviceObject->DeviceType != FILE_DEVICE_NETWORK_FILE_SYSTEM) {
ExFreePool( deviceNameInfo->Name.Buffer );
}
} else {
RtlCopyMemory( ObjectNameInfo,
deviceNameInfo,
lengthNeeded > Length ? Length : lengthNeeded );
}
ObjectNameInfo->Name.Buffer = p;
p = (PWSTR) ((PCHAR) p + deviceNameInfo->Name.Length);
//
// If the buffer is already full, note and continue to pick up the filename length.
// We want to return the required length for the entire result.
//
deviceNameOverflow = FALSE;
if (lengthNeeded > Length) {
*ReturnLength = lengthNeeded;
deviceNameOverflow = TRUE;
}
//
// Reset the state for the buffer to obtain the filename portion of the
// name and calculate the remaining length of the caller's buffer. Note
// that in the following calculations, there are two assumptions and
// and dependencies:
//
// 1) The above query of the device name's returned length needed
// include a NULL character which will be included at the end
// of the entire name. This is included in the calculations
// although it does not appear to be included.
//
// 2) The sizeof the object name information buffer is assumed
// (and guaranteed because it can never change) to be larger
// than the filename offset in a file name information buffer.
// Therefore it is known that the new length of the "buffer"
// variable can be set to the remaining length plus at least 4.
//
fileNameInfo = (PFILE_NAME_INFORMATION) buffer;
if (deviceNameOverflow) {
length = Length;
} else {
length = Length - lengthNeeded;
length += FIELD_OFFSET( FILE_NAME_INFORMATION, FileName );
}
if (((Mode == UserMode) && (!UseDosDeviceName)) ||
!(fileObject->Flags & FO_SYNCHRONOUS_IO)) {
//
// Query the name of the file based using an intermediary buffer.
//
status = IopQueryXxxInformation( fileObject,
FileNameInformation,
length,
Mode,
(PVOID) fileNameInfo,
&lengthNeeded,
TRUE );
} else {
//
// This is a kernel mode request for a file that was opened for
// synchronous I/O. A special function that does not obtain the
// file object lock is required, otherwise the request may deadlock
// since the lock is probably already owned.
//
status = IopGetFileInformation( fileObject,
length,
FileNameInformation,
fileNameInfo,
&lengthNeeded );
}
//
// If an error occurred attempting to obtain the filename return now. Note
// that buffer overflow is a warning, not an error.
//
if (NT_ERROR( status )) {
if (status == STATUS_INVALID_PARAMETER ||
status == STATUS_INVALID_DEVICE_REQUEST ||
status == STATUS_NOT_IMPLEMENTED ||
status == STATUS_INVALID_INFO_CLASS) {
lengthNeeded = FIELD_OFFSET( FILE_NAME_INFORMATION, FileName );
fileNameInfo->FileNameLength = 0;
fileNameInfo->FileName[0] = OBJ_NAME_PATH_SEPARATOR;
status = STATUS_SUCCESS;
} else {
leave;
}
}
//
// Compute the correct length
// Note that ReturnLength already contains a space for NULL added by the previous ObQueryNameString.
//
if (deviceNameOverflow) {
*ReturnLength += fileNameInfo->FileNameLength;
status = STATUS_BUFFER_OVERFLOW;
leave;
}
//
// Set the remaining length of the caller's buffer as well as the total
// length needed to contain the entire name of the file.
//
length = lengthNeeded - FIELD_OFFSET( FILE_NAME_INFORMATION, FileName );
lengthNeeded = (ULONG)((PUCHAR) p - (PUCHAR) ObjectNameInfo) + fileNameInfo->FileNameLength;
//
// Attempt to copy the name of the file into the output buffer. Note
// that if the file name does not begin w/a '\', then it is not volume
// relative, so the name of the file cannot be expressed as the
// concatenation of the name of the device and the file. Therefore an
// error is returned.
//
// The only example of this situation known at this time is when one
// opens a directory by file ID, and then opens a file relative to that
// directory. When attempting to query the path, if the caller did not
// have traverse access to open the directory, then the only name that
// can be returned is the path name to the file from the directory, but
// the volume-relative name cannot be returned. Therefore, the file
// system returns only the name of the directory and the path to the
// file, but this is not volume-relative so the only recourse is to
// return an error.
//
// Note that if the caller were to call NtQueryInformationFile and
// request FileNameInformation, then the name above named will be
// successfully returned from the file system.
//
if (fileNameInfo->FileName[0] != OBJ_NAME_PATH_SEPARATOR) {
status = STATUS_OBJECT_PATH_INVALID;
leave;
}
RtlCopyMemory( p,
fileNameInfo->FileName,
length );
p = (PWSTR) ((PCH) p + length);
*p = '\0';
lengthNeeded += sizeof( WCHAR );
*ReturnLength = lengthNeeded;
length = (ULONG)((PUCHAR) p - (PUCHAR) ObjectNameInfo);
ObjectNameInfo->Name.Length = (USHORT) (length - sizeof( *ObjectNameInfo ));
ObjectNameInfo->Name.MaximumLength = (USHORT) ((length - sizeof( *ObjectNameInfo )) + sizeof( WCHAR ));
}
finally {
//
// Finally, free the temporary buffer.
//
ExFreePool( buffer );
}
return status;
}
NTSTATUS
IopQueryName(
IN PVOID Object,
IN BOOLEAN HasObjectName,
OUT POBJECT_NAME_INFORMATION ObjectNameInfo,
IN ULONG Length,
OUT PULONG ReturnLength,
IN KPROCESSOR_MODE Mode
)
/*++
Routine Description:
This function implements the query name procedure for the Object Manager
for querying the names of file objects.
Arguments:
Object - Pointer to the file object whose name is to be retrieved.
HasObjectName - Indicates whether or not the object has a name.
ObjectNameInfo - Buffer in which to return the name.
Length - Specifies the length of the output buffer, in bytes.
ReturnLength - Specifies the number of bytes actually returned in the
output buffer.
Mode = Processor mode of the caller
Return Value:
The function return value is the final status of the query operation.
--*/
{
UNREFERENCED_PARAMETER (Mode);
return IopQueryNameInternal( Object,
HasObjectName,
FALSE,
ObjectNameInfo,
Length,
ReturnLength,
Mode );
}
VOID
IopCheckBackupRestorePrivilege(
IN PACCESS_STATE AccessState,
IN OUT PULONG CreateOptions,
IN KPROCESSOR_MODE PreviousMode,
IN ULONG Disposition
)
/*++
Routine Description:
This funcion will determine if the caller is asking for any accesses
that may be satisfied by Backup or Restore privileges, and if so,
perform the privilge checks. If the privilege checks succeed, then
the appropriate bits will be moved out of the RemainingDesiredAccess
field in the AccessState structure and placed into the PreviouslyGrantedAccess
field.
Note that access is not denied if the caller does not have either or
both of the privileges, since he may be granted the desired access
via the security descriptor on the object.
This routine will also set a flag in the AccessState structure so that
it will not perform these privilege checks again in case we come through
this way again due to a reparse.
Arguments:
AccessState - The AccessState containing the current state of this access
attempt.
CreateOptions - The CreateOptions field from the OPEN_PACKET structure for
this open attempt.
PreviousMode - The processor mode to be used in checking parameters.
Disposition - The create disposition for this request.
Return Value:
None.
--*/
{
ACCESS_MASK desiredAccess;
ACCESS_MASK readAccess;
ACCESS_MASK writeAccess;
PRIVILEGE_SET requiredPrivileges;
BOOLEAN accessGranted;
BOOLEAN keepBackupIntent = FALSE;
BOOLEAN ForceRestoreCheck = FALSE;
PAGED_CODE();
//
// Check to determine whether or not this check has already been made.
// If so, simply return back to the caller.
//
if (AccessState->Flags & SE_BACKUP_PRIVILEGES_CHECKED) {
return;
}
if (*CreateOptions & FILE_OPEN_FOR_BACKUP_INTENT) {
AccessState->Flags |= SE_BACKUP_PRIVILEGES_CHECKED;
readAccess = READ_CONTROL | ACCESS_SYSTEM_SECURITY | FILE_GENERIC_READ | FILE_TRAVERSE;
writeAccess = WRITE_DAC | WRITE_OWNER | ACCESS_SYSTEM_SECURITY | FILE_GENERIC_WRITE | FILE_ADD_FILE | FILE_ADD_SUBDIRECTORY | DELETE;
desiredAccess = AccessState->RemainingDesiredAccess;
//
// If the caller has requested MAXIMUM_ALLOWED, then make it appear as
// if the request was for everything permitted by Backup and Restore,
// and then grant everything that can actually be granted.
//
if (desiredAccess & MAXIMUM_ALLOWED) {
desiredAccess |= ( readAccess | writeAccess );
}
//
// If the disposition says that we're opening the file, check for both backup
// and restore privilege, depending on what's in the desired access.
//
// If the disposition says that we're creating or trying to overwrite the file,
// then all we need to do is to check for restore privilege, and if it's there,
// grant every possible access.
//
if ((Disposition == FILE_OPEN ) || (Disposition == FILE_OPEN_IF) || (Disposition == FILE_OVERWRITE_IF)) {
//
// If the request was for any of the bits in the read access mask, then
// assume that this is a backup operation, and check for the Backup
// privielege. If the caller has it, then grant the intersection of
// the desired access and read access masks.
//
if (readAccess & desiredAccess) {
requiredPrivileges.PrivilegeCount = 1;
requiredPrivileges.Control = PRIVILEGE_SET_ALL_NECESSARY;
requiredPrivileges.Privilege[0].Luid = SeBackupPrivilege;
requiredPrivileges.Privilege[0].Attributes = 0;
accessGranted = SePrivilegeCheck( &requiredPrivileges,
&AccessState->SubjectSecurityContext,
PreviousMode );
if (accessGranted) {
//
// The caller has Backup privilege, so grant the appropriate
// accesses.
//
keepBackupIntent = TRUE;
(VOID) SeAppendPrivileges( AccessState, &requiredPrivileges );
AccessState->PreviouslyGrantedAccess |= ( desiredAccess & readAccess );
AccessState->RemainingDesiredAccess &= ~readAccess;
desiredAccess &= ~readAccess;
AccessState->Flags |= TOKEN_HAS_BACKUP_PRIVILEGE;
}
}
} else {
ForceRestoreCheck = TRUE;
}
//
// If the request was for any of the bits in the write access mask, then
// assume that this is a restore operation, so check for the Restore
// privilege. If the caller has it, then grant the intersection of
// the desired access and write access masks.
//
if ((writeAccess & desiredAccess) || ForceRestoreCheck) {
requiredPrivileges.PrivilegeCount = 1;
requiredPrivileges.Control = PRIVILEGE_SET_ALL_NECESSARY;
requiredPrivileges.Privilege[0].Luid = SeRestorePrivilege;
requiredPrivileges.Privilege[0].Attributes = 0;
accessGranted = SePrivilegeCheck( &requiredPrivileges,
&AccessState->SubjectSecurityContext,
PreviousMode );
if (accessGranted) {
//
// The caller has Restore privilege, so grant the appropriate
// accesses.
//
keepBackupIntent = TRUE;
(VOID) SeAppendPrivileges( AccessState, &requiredPrivileges );
AccessState->PreviouslyGrantedAccess |= (desiredAccess & writeAccess);
AccessState->RemainingDesiredAccess &= ~writeAccess;
AccessState->Flags |= TOKEN_HAS_RESTORE_PRIVILEGE;
}
}
//
// If either of the access types was granted because the caller had
// backup or restore privilege, then the backup intent flag is kept.
// Otherwise, it is cleared so that it is not passed onto the driver
// so that it is not incorrectly propogated anywhere else, since this
// caller does not actually have the privilege enabled.
//
if (!keepBackupIntent) {
*CreateOptions &= ~FILE_OPEN_FOR_BACKUP_INTENT;
}
}
}