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.
 
 
 
 
 
 

4388 lines
130 KiB

/*++
Copyright (c) 1989-1999 Microsoft Corporation
Module Name:
fspyLib.c
Abstract:
This contains library support routines for FileSpy. These routines
do the main work for logging the I/O operations --- creating the log
records, recording the relevant information, attach/detach from
devices, etc.
As of the Windows XP SP1 IFS Kit version of this sample and later, this
sample can be built for each build environment released with the IFS Kit
with no additional modifications. To provide this capability, additional
compile-time logic was added -- see the '#if WINVER' locations. Comments
tagged with the 'VERSION NOTE' header have also been added as appropriate to
describe how the logic must change between versions.
If this sample is built in the Windows XP environment or later, it will run
on Windows 2000 or later. This is done by dynamically loading the routines
that are only available on Windows XP or later and making run-time decisions
to determine what code to execute. Comments tagged with 'MULTIVERISON NOTE'
mark the locations where such logic has been added.
Environment:
Kernel mode
// @@BEGIN_DDKSPLIT
Author:
George Jenkins (georgeje) 6-Jan-1999
Neal Christiansen (nealch)
Molly Brown (mollybro)
Revision History:
Neal Christiansen (nealch) 06-Jul-2001
Modified to use Stream Contexts to track names
Ravisankar Pudipeddi (ravisp) 07-May-2002
Make it work on IA64
Molly Brown (mollybro) 21-May-2002
Modify sample to make it support running on Windows 2000 or later if
built in the latest build environment and allow it to be built in W2K
and later build environments.
// @@END_DDKSPLIT
--*/
#include <stdio.h>
#include <ntifs.h>
#include "filespy.h"
#include "fspyKern.h"
#ifdef ALLOC_PRAGMA
#pragma alloc_text(INIT, SpyReadDriverParameters)
#pragma alloc_text(PAGE, SpyAttachDeviceToDeviceStack)
#pragma alloc_text(PAGE, SpyQueryFileSystemForFileName)
#pragma alloc_text(PAGE, SpyQueryInformationFile)
#pragma alloc_text(PAGE, SpyIsAttachedToDeviceByUserDeviceName)
#pragma alloc_text(PAGE, SpyIsAttachedToDevice)
#pragma alloc_text(PAGE, SpyIsAttachedToDeviceW2K)
#pragma alloc_text(PAGE, SpyAttachToMountedDevice)
#pragma alloc_text(PAGE, SpyCleanupMountedDevice)
#pragma alloc_text(PAGE, SpyAttachToDeviceOnDemand)
#pragma alloc_text(PAGE, SpyAttachToDeviceOnDemandW2K)
#pragma alloc_text(PAGE, SpyStartLoggingDevice)
#pragma alloc_text(PAGE, SpyStopLoggingDevice)
#pragma alloc_text(PAGE, SpyAttachToFileSystemDevice)
#pragma alloc_text(PAGE, SpyDetachFromFileSystemDevice)
#pragma alloc_text(PAGE, SpyGetAttachList)
#pragma alloc_text(PAGE, SpyGetObjectName)
#if WINVER >= 0x0501
#pragma alloc_text(INIT, SpyLoadDynamicFunctions)
#pragma alloc_text(INIT, SpyGetCurrentVersion)
#pragma alloc_text(PAGE, SpyIsAttachedToDeviceWXPAndLater)
#pragma alloc_text(PAGE, SpyAttachToDeviceOnDemandWXPAndLater)
#pragma alloc_text(PAGE, SpyEnumerateFileSystemVolumes)
#pragma alloc_text(PAGE, SpyGetBaseDeviceObjectName)
#endif
#endif
//////////////////////////////////////////////////////////////////////////
// //
// Library support routines //
// //
//////////////////////////////////////////////////////////////////////////
VOID
SpyReadDriverParameters (
IN PUNICODE_STRING RegistryPath
)
/*++
Routine Description:
This routine tries to read the FileSpy-specific parameters from
the registry. These values will be found in the registry location
indicated by the RegistryPath passed in.
Arguments:
RegistryPath - the path key which contains the values that are
the FileSpy parameters
Return Value:
None.
--*/
{
OBJECT_ATTRIBUTES attributes;
HANDLE driverRegKey;
NTSTATUS status;
ULONG bufferSize, resultLength;
PVOID buffer = NULL;
UNICODE_STRING valueName;
PKEY_VALUE_PARTIAL_INFORMATION pValuePartialInfo;
PAGED_CODE();
//
// All the global values are already set to default values. Any
// values we read from the registry will override these defaults.
//
//
// Do the initial setup to start reading from the registry.
//
InitializeObjectAttributes( &attributes,
RegistryPath,
OBJ_CASE_INSENSITIVE,
NULL,
NULL);
status = ZwOpenKey( &driverRegKey,
KEY_READ,
&attributes);
if (!NT_SUCCESS(status)) {
driverRegKey = NULL;
goto SpyReadDriverParameters_Exit;
}
bufferSize = sizeof( KEY_VALUE_PARTIAL_INFORMATION ) + sizeof( ULONG );
buffer = ExAllocatePoolWithTag( NonPagedPool, bufferSize, FILESPY_POOL_TAG );
if (NULL == buffer) {
goto SpyReadDriverParameters_Exit;
}
//
// Read the gMaxRecordsToAllocate from the registry
//
RtlInitUnicodeString(&valueName, MAX_RECORDS_TO_ALLOCATE);
status = ZwQueryValueKey( driverRegKey,
&valueName,
KeyValuePartialInformation,
buffer,
bufferSize,
&resultLength);
if (NT_SUCCESS(status)) {
pValuePartialInfo = (PKEY_VALUE_PARTIAL_INFORMATION) buffer;
ASSERT(pValuePartialInfo->Type == REG_DWORD);
gMaxRecordsToAllocate = *((PLONG)&pValuePartialInfo->Data);
}
//
// Read the gMaxNamesToAllocate from the registry
//
RtlInitUnicodeString(&valueName, MAX_NAMES_TO_ALLOCATE);
status = ZwQueryValueKey( driverRegKey,
&valueName,
KeyValuePartialInformation,
buffer,
bufferSize,
&resultLength);
if (NT_SUCCESS(status)) {
pValuePartialInfo = (PKEY_VALUE_PARTIAL_INFORMATION) buffer;
ASSERT(pValuePartialInfo->Type == REG_DWORD);
gMaxNamesToAllocate = *((PLONG)&pValuePartialInfo->Data);
}
//
// Read the initial debug setting from the registry
//
RtlInitUnicodeString(&valueName, DEBUG_LEVEL);
status = ZwQueryValueKey( driverRegKey,
&valueName,
KeyValuePartialInformation,
buffer,
bufferSize,
&resultLength );
if (NT_SUCCESS( status )) {
pValuePartialInfo = (PKEY_VALUE_PARTIAL_INFORMATION) buffer;
ASSERT( pValuePartialInfo->Type == REG_DWORD );
gFileSpyDebugLevel |= *((PULONG)&pValuePartialInfo->Data);
}
//
// Read the attachment mode setting from the registry
//
RtlInitUnicodeString(&valueName, ATTACH_MODE);
status = ZwQueryValueKey( driverRegKey,
&valueName,
KeyValuePartialInformation,
buffer,
bufferSize,
&resultLength );
if (NT_SUCCESS( status )) {
pValuePartialInfo = (PKEY_VALUE_PARTIAL_INFORMATION) buffer;
ASSERT( pValuePartialInfo->Type == REG_DWORD );
gFileSpyAttachMode = *((PULONG)&pValuePartialInfo->Data);
}
goto SpyReadDriverParameters_Exit;
SpyReadDriverParameters_Exit:
if (NULL != buffer) {
ExFreePoolWithTag( buffer, FILESPY_POOL_TAG );
}
if (NULL != driverRegKey) {
ZwClose(driverRegKey);
}
return;
}
#if WINVER >= 0x0501
VOID
SpyLoadDynamicFunctions (
)
/*++
Routine Description:
This routine tries to load the function pointers for the routines that
are not supported on all versions of the OS. These function pointers are
then stored in the global structure gSpyDynamicFunctions.
This support allows for one driver to be built that will run on all
versions of the OS Windows 2000 and greater. Note that on Windows 2000,
the functionality may be limited.
Arguments:
None.
Return Value:
None.
--*/
{
UNICODE_STRING functionName;
RtlZeroMemory( &gSpyDynamicFunctions, sizeof( gSpyDynamicFunctions ) );
//
// For each routine that we would want to use, lookup its address in the
// kernel or hal. If it is not present, that field in our global
// gSpyDynamicFunctions structure will be set to NULL.
//
RtlInitUnicodeString( &functionName, L"FsRtlRegisterFileSystemFilterCallbacks" );
gSpyDynamicFunctions.RegisterFileSystemFilterCallbacks = MmGetSystemRoutineAddress( &functionName );
RtlInitUnicodeString( &functionName, L"IoAttachDeviceToDeviceStackSafe" );
gSpyDynamicFunctions.AttachDeviceToDeviceStackSafe = MmGetSystemRoutineAddress( &functionName );
RtlInitUnicodeString( &functionName, L"IoEnumerateDeviceObjectList" );
gSpyDynamicFunctions.EnumerateDeviceObjectList = MmGetSystemRoutineAddress( &functionName );
RtlInitUnicodeString( &functionName, L"IoGetLowerDeviceObject" );
gSpyDynamicFunctions.GetLowerDeviceObject = MmGetSystemRoutineAddress( &functionName );
RtlInitUnicodeString( &functionName, L"IoGetDeviceAttachmentBaseRef" );
gSpyDynamicFunctions.GetDeviceAttachmentBaseRef = MmGetSystemRoutineAddress( &functionName );
RtlInitUnicodeString( &functionName, L"IoGetDiskDeviceObject" );
gSpyDynamicFunctions.GetDiskDeviceObject = MmGetSystemRoutineAddress( &functionName );
RtlInitUnicodeString( &functionName, L"IoGetAttachedDeviceReference" );
gSpyDynamicFunctions.GetAttachedDeviceReference = MmGetSystemRoutineAddress( &functionName );
RtlInitUnicodeString( &functionName, L"RtlGetVersion" );
gSpyDynamicFunctions.GetVersion = MmGetSystemRoutineAddress( &functionName );
}
VOID
SpyGetCurrentVersion (
)
/*++
Routine Description:
This routine reads the current OS version using the correct routine based
on what routine is available.
Arguments:
None.
Return Value:
None.
--*/
{
if (NULL != gSpyDynamicFunctions.GetVersion) {
RTL_OSVERSIONINFOW versionInfo;
NTSTATUS status;
//
// VERSION NOTE: RtlGetVersion does a bit more than we need, but
// we are using it if it is available to show how to use it. It
// is available on Windows XP and later. RtlGetVersion and
// RtlVerifyVersionInfo (both documented in the IFS Kit docs) allow
// you to make correct choices when you need to change logic based
// on the current OS executing your code.
//
versionInfo.dwOSVersionInfoSize = sizeof( RTL_OSVERSIONINFOW );
status = (gSpyDynamicFunctions.GetVersion)( &versionInfo );
ASSERT( NT_SUCCESS( status ) );
gSpyOsMajorVersion = versionInfo.dwMajorVersion;
gSpyOsMinorVersion = versionInfo.dwMinorVersion;
} else {
PsGetVersion( &gSpyOsMajorVersion,
&gSpyOsMinorVersion,
NULL,
NULL );
}
}
#endif
////////////////////////////////////////////////////////////////////////
// //
// Memory allocation routines //
// //
////////////////////////////////////////////////////////////////////////
PVOID
SpyAllocateBuffer (
IN OUT PLONG Counter,
IN LONG MaxCounterValue,
OUT PULONG RecordType
)
/*++
Routine Description:
Allocates a new buffer from the gFreeBufferList if there is enough memory
to do so and Counter does not exceed MaxCounterValue. The RecordType
is set to one of the record type constants based on the allocation state.
Arguments:
Counter - (optional) the counter variable to test and increment if
we can allocate
MaxCounterValue - (ignored if Counter not given) the value which
Counter should not exceed
RecordType - (optional) set to one of the following:
RECORD_TYPE_NORMAL allocation succeeded
RECORD_TYPE_OUT_OF_MEMORY allocation failed because the system was
out of memory
RECORD_TYPE_EXCEED_MEMORY_ALLOWANCE allocation failed because the
counter exceeded its maximum value.
Return Value:
Pointer to the buffer allocate, or NULL if allocation failed (either
because system is out of memory or we have exceeded the MaxCounterValue).
--*/
{
PVOID newBuffer;
ULONG newRecordType = RECORD_TYPE_NORMAL;
#ifdef MEMORY_DBG
//
// When we are debugging the memory usage to make sure that we
// don't leak memory, we want to allocate the memory from pool
// so that we can use the Driver Verifier to help debug any
// memory problems.
//
newBuffer = ExAllocatePoolWithTag( NonPagedPool,
RECORD_SIZE,
FILESPY_LOGRECORD_TAG );
#else
//
// When we are not debugging the memory usage, we use a look-aside
// list for better performance.
//
newBuffer = ExAllocateFromNPagedLookasideList( &gFreeBufferList );
#endif
if (newBuffer) {
if (Counter) {
if (*Counter < MaxCounterValue) {
InterlockedIncrement(Counter);
} else {
//
// We've exceed our driver's memory limit so note that
// and give back the record
//
SetFlag( newRecordType,
(RECORD_TYPE_STATIC | RECORD_TYPE_EXCEED_MEMORY_ALLOWANCE) );
#ifdef MEMORY_DBG
ExFreePoolWithTag( newBuffer, FILESPY_POOL_TAG );
#else
ExFreeToNPagedLookasideList( &gFreeBufferList, newBuffer );
#endif
newBuffer = NULL;
}
}
} else {
SetFlag( newRecordType,
(RECORD_TYPE_STATIC | RECORD_TYPE_OUT_OF_MEMORY) );
}
if (RecordType) {
*RecordType = newRecordType;
}
return newBuffer;
}
VOID
SpyFreeBuffer (
IN PVOID Buffer,
IN PLONG Counter
)
/*++
Routine Description:
Returns a Buffer to the gFreeBufferList.
Arguments:
Buffer - the buffer to return to the gFreeBufferList
Return Value:
None.
--*/
{
#ifdef MEMORY_DBG
ExFreePoolWithTag( Buffer, FILESPY_POOL_TAG );
#else
ExFreeToNPagedLookasideList( &gFreeBufferList, Buffer );
#endif
//
// Update the count
//
if (Counter) {
InterlockedDecrement(Counter);
}
}
////////////////////////////////////////////////////////////////////////
// //
// Logging routines //
// //
////////////////////////////////////////////////////////////////////////
PRECORD_LIST
SpyNewRecord (
IN ULONG AssignedSequenceNumber
)
/*++
Routine Description:
Allocates a new RECORD_LIST structure if there is enough memory to do so. A
sequence number is updated for each request for a new record.
Arguments:
AssignedSequenceNumber - 0 if you want this function to generate the
next sequence number; if not 0, the new record is assigned the
given sequence number.
Return Value:
Pointer to the RECORD_LIST allocated, or NULL if no memory is available.
--*/
{
PRECORD_LIST newRecord = NULL;
ULONG currentSequenceNumber;
KIRQL irql;
ULONG initialRecordType;
newRecord = (PRECORD_LIST) SpyAllocateBuffer( &gRecordsAllocated,
gMaxRecordsToAllocate,
&initialRecordType);
KeAcquireSpinLock(&gLogSequenceLock, &irql);
//
// Assign a new sequence number if 0 was passed in, otherwise use the
// number passed in
//
if (AssignedSequenceNumber == 0) {
gLogSequenceNumber++;
currentSequenceNumber = gLogSequenceNumber;
} else {
currentSequenceNumber = AssignedSequenceNumber;
}
if ((newRecord == NULL) &&
!InterlockedCompareExchange( &gStaticBufferInUse, TRUE, FALSE)) {
//
// Toggle on our gStaticBufferInUse flag and use the static out of memory
// buffer to record this log entry. This special log record is used
// to notify the user application that we are out of memory. Log
// request will be dropped until we can get more memory.
//
newRecord = (PRECORD_LIST)gOutOfMemoryBuffer;
newRecord->LogRecord.RecordType = initialRecordType;
newRecord->LogRecord.Length = SIZE_OF_LOG_RECORD;
newRecord->LogRecord.SequenceNumber = currentSequenceNumber;
} else if (newRecord) {
//
// We were able to allocate a new record so initialize it
// appropriately.
//
newRecord->LogRecord.RecordType = initialRecordType;
newRecord->LogRecord.Length = SIZE_OF_LOG_RECORD;
newRecord->LogRecord.SequenceNumber = currentSequenceNumber;
}
KeReleaseSpinLock(&gLogSequenceLock, irql);
//
// Init record specific fields.
//
if (newRecord != NULL) {
newRecord->NewContext = NULL;
newRecord->WaitEvent = NULL;
newRecord->Flags = 0;
}
return( newRecord );
}
VOID
SpyFreeRecord (
IN PRECORD_LIST Record
)
/*++
Routine Description:
Frees a RECORD_LIST, which returns the memory to the gFreeBufferList look-aside
list and updates the gRecordsAllocated count.
Arguments:
Record - the record to free
Return Value:
None.
--*/
{
//
// If there is a context record defined, release it now
//
#if USE_STREAM_CONTEXTS
if (NULL != Record->NewContext) {
SpyReleaseContext( Record->NewContext );
}
#endif
if (FlagOn( Record->LogRecord.RecordType, RECORD_TYPE_STATIC )) {
//
// This is our static record, so reset our gStaticBufferInUse
// flag.
//
InterlockedExchange( &gStaticBufferInUse, FALSE );
} else {
//
// This isn't our static memory buffer, so free the dynamically
// allocated memory.
//
SpyFreeBuffer( Record, &gRecordsAllocated );
}
}
PRECORD_LIST
SpyLogFastIoStart (
IN FASTIO_TYPE FastIoType,
IN PDEVICE_OBJECT DeviceObject,
IN PFILE_OBJECT FileObject OPTIONAL,
IN PLARGE_INTEGER FileOffset OPTIONAL,
IN ULONG Length OPTIONAL,
IN BOOLEAN Wait OPTIONAL
)
/*++
Routine Description:
Creates the log record if possible and records the necessary Fast I/O
information at the beginning of the fast I/O operation in RecordList
according to LoggingFlags.
The optional arguments are not recorded for all Fast I/O types. If
the argument is not needed for a given Fast I/O type, the parameter
was ignored.
Arguments:
FastIoType - The type of fast I/O we are logging (REQUIRED)
DeviceObject - The device object for our filter. (REQUIRED)
FileObject - Pointer to the file object this operation is on (OPTIONAL)
FileOffset - Pointer to the file offset for this operation (OPTIONAL)
Length - Length of the data for this operation (OPTIONAL)
Wait - Whether or not this operation can wait for a result (OPTIONAL)
Return Value:
The RECORD_LIST structure created with the appropriate information
filled in. If a RECORD_LIST structure couldn't be allocated, NULL
is returned.
--*/
{
PRECORD_LIST pRecordList;
PRECORD_FASTIO pRecordFastIo;
PFILESPY_DEVICE_EXTENSION devExt;
//
// Try to get a new record
//
pRecordList = SpyNewRecord(0);
//
// If we didn't get a RECORD_LIST, exit and return NULL
//
if (pRecordList == NULL) {
return NULL;
}
//
// We got a RECORD_LIST, so now fill in the appropriate information
//
pRecordFastIo = &pRecordList->LogRecord.Record.RecordFastIo;
//
// Perform the necessary book keeping for the RECORD_LIST
//
SetFlag( pRecordList->LogRecord.RecordType, RECORD_TYPE_FASTIO );
//
// Set the RECORD_FASTIO fields that are set for all Fast I/O types
//
pRecordFastIo->Type = FastIoType;
KeQuerySystemTime(&pRecordFastIo->StartTime);
//
// Get process and thread information
//
pRecordFastIo->ProcessId = (ULONG_PTR) PsGetCurrentProcessId();
pRecordFastIo->ThreadId = (ULONG_PTR) PsGetCurrentThreadId();
//
// Record the information that is appropriate based on the
// Fast I/O type
//
pRecordFastIo->FileObject = (FILE_ID)FileObject;
pRecordFastIo->DeviceObject = (DEVICE_ID)DeviceObject;
pRecordFastIo->FileOffset.QuadPart = ((FileOffset != NULL) ? FileOffset->QuadPart : 0);
pRecordFastIo->Length = Length;
pRecordFastIo->Wait = Wait;
devExt = DeviceObject->DeviceExtension;
if (FastIoType == CHECK_IF_POSSIBLE) {
//
// On NTFS, locks are sometimes held but top-level irp is not set,
// therefore it is not safe to query the base file system for the
// file name at this time. If we've got it in the cache, we'll
// use it. Otherwise, we will not return a name.
//
SpySetName(pRecordList, DeviceObject, FileObject, NLFL_ONLY_CHECK_CACHE, NULL);
} else {
SpySetName(pRecordList, DeviceObject, FileObject, 0, NULL);
}
return pRecordList;
}
VOID
SpyLogFastIoComplete (
IN PIO_STATUS_BLOCK ReturnStatus,
IN PRECORD_LIST RecordList
)
/*++
Routine Description:
Records the necessary Fast I/O information in RecordList according to
LoggingFlags.
The optional arguments are not recorded for all Fast I/O types. If
the argument is not needed for a given Fast I/O type, the parameter
was ignored.
Arguments:
ReturnStatus - The return value of the operation (OPTIONAL)
RecordList - The PRECORD_LIST in which the Fast I/O information is stored.
Return Value:
None.
--*/
{
PRECORD_FASTIO pRecordFastIo;
ASSERT(RecordList);
pRecordFastIo = &RecordList->LogRecord.Record.RecordFastIo;
//
// Set the RECORD_FASTIO fields that are set for all Fast I/O types
//
KeQuerySystemTime(&pRecordFastIo->CompletionTime);
if (ReturnStatus != NULL) {
pRecordFastIo->ReturnStatus = ReturnStatus->Status;
} else {
pRecordFastIo->ReturnStatus = 0;
}
SpyLog(RecordList);
}
#if WINVER >= 0x0501 /* See comment in DriverEntry */
VOID
SpyLogPreFsFilterOperation (
IN PFS_FILTER_CALLBACK_DATA Data,
OUT PRECORD_LIST RecordList
)
{
NAME_LOOKUP_FLAGS lookupFlags = 0;
PRECORD_FS_FILTER_OPERATION pRecordFsFilterOp;
pRecordFsFilterOp = &RecordList->LogRecord.Record.RecordFsFilterOp;
//
// Record the information we use for an originating Irp. We first
// need to initialize some of the RECORD_LIST and RECORD_IRP fields.
// Then get the interesting information from the Irp.
//
SetFlag( RecordList->LogRecord.RecordType, RECORD_TYPE_FS_FILTER_OP );
pRecordFsFilterOp->FsFilterOperation = Data->Operation;
pRecordFsFilterOp->FileObject = (FILE_ID) Data->FileObject;
pRecordFsFilterOp->DeviceObject = (FILE_ID) Data->DeviceObject;
pRecordFsFilterOp->ProcessId = (FILE_ID)PsGetCurrentProcessId();
pRecordFsFilterOp->ThreadId = (FILE_ID)PsGetCurrentThreadId();
KeQuerySystemTime(&pRecordFsFilterOp->OriginatingTime);
//
// Do not query for the name on any of the release operations
// because a file system resource is currently being held and
// we may deadlock.
//
switch (Data->Operation) {
case FS_FILTER_RELEASE_FOR_CC_FLUSH:
case FS_FILTER_RELEASE_FOR_SECTION_SYNCHRONIZATION:
case FS_FILTER_RELEASE_FOR_MOD_WRITE:
SPY_LOG_PRINT( SPYDEBUG_TRACE_DETAILED_CONTEXT_OPS,
("FileSpy!SpyLogPreFsFilterOp: RelOper\n") );
SetFlag( lookupFlags, NLFL_ONLY_CHECK_CACHE );
break;
}
//
// Only set the volumeName if the next device is a file system
// since we only want to prepend the volumeName if we are on
// top of a local file system.
//
SpySetName( RecordList, Data->DeviceObject, Data->FileObject, lookupFlags, NULL);
}
VOID
SpyLogPostFsFilterOperation (
IN NTSTATUS OperationStatus,
OUT PRECORD_LIST RecordList
)
{
PRECORD_FS_FILTER_OPERATION pRecordFsFilterOp;
pRecordFsFilterOp = &RecordList->LogRecord.Record.RecordFsFilterOp;
//
// Record the information we see in the post operation.
//
pRecordFsFilterOp->ReturnStatus = OperationStatus;
KeQuerySystemTime(&pRecordFsFilterOp->CompletionTime);
}
#endif
NTSTATUS
SpyAttachDeviceToDeviceStack (
IN PDEVICE_OBJECT SourceDevice,
IN PDEVICE_OBJECT TargetDevice,
IN OUT PDEVICE_OBJECT *AttachedToDeviceObject
)
/*++
Routine Description:
This routine attaches the SourceDevice to the TargetDevice's stack and
returns the device object SourceDevice was directly attached to in
AttachedToDeviceObject. Note that the SourceDevice does not necessarily
get attached directly to TargetDevice. The SourceDevice will get attached
to the top of the stack of which TargetDevice is a member.
VERSION NOTE:
In Windows XP, a new API was introduced to close a rare timing window that
can cause IOs to start being sent to a device before its
AttachedToDeviceObject is set in its device extension. This is possible
if a filter is attaching to a device stack while the system is actively
processing IOs. The new API closes this timing window by setting the
device extension field that holds the AttachedToDeviceObject while holding
the IO Manager's lock that protects the device stack.
A sufficient work around for earlier versions of the OS is to set the
AttachedToDeviceObject to the device object that the SourceDevice is most
likely to attach to. While it is possible that another filter will attach
in between the SourceDevice and TargetDevice, this will prevent the
system from bug checking if the SourceDevice receives IOs before the
AttachedToDeviceObject is correctly set.
For a driver built in the Windows 2000 build environment, we will always
use the work-around code to attach. For a driver that is built in the
Windows XP or later build environments (therefore you are building a
multiversion driver), we will determine which method of attachment to use
based on which APIs are available.
Arguments:
SourceDevice - The device object to be attached to the stack.
TargetDevice - The device that we currently think is the top of the stack
to which SourceDevice should be attached.
AttachedToDeviceObject - This is set to the device object to which
SourceDevice is attached if the attach is successful.
Return Value:
Return STATUS_SUCCESS if the device is successfully attached. If
TargetDevice represents a stack to which devices can no longer be attached,
STATUS_NO_SUCH_DEVICE is returned.
--*/
{
PAGED_CODE();
#if WINVER >= 0x0501
if (IS_WINDOWSXP_OR_LATER()) {
ASSERT( NULL != gSpyDynamicFunctions.AttachDeviceToDeviceStackSafe );
return (gSpyDynamicFunctions.AttachDeviceToDeviceStackSafe)( SourceDevice,
TargetDevice,
AttachedToDeviceObject );
} else {
#endif
*AttachedToDeviceObject = TargetDevice;
*AttachedToDeviceObject = IoAttachDeviceToDeviceStack( SourceDevice,
TargetDevice );
if (*AttachedToDeviceObject == NULL) {
return STATUS_NO_SUCH_DEVICE;
}
return STATUS_SUCCESS;
#if WINVER >= 0x0501
}
#endif
}
NTSTATUS
SpyLog (
IN PRECORD_LIST NewRecord
)
/*++
Routine Description:
This routine appends the completed log record to the gOutputBufferList.
Arguments:
NewRecord - The record to append to the gOutputBufferList
Return Value:
The function returns STATUS_SUCCESS.
--*/
{
KIRQL controlDeviceIrql;
KIRQL outputBufferIrql;
KeAcquireSpinLock( &gControlDeviceStateLock, &controlDeviceIrql );
if (gControlDeviceState == OPENED) {
//
// The device is still open so add this record onto the list
//
KeAcquireSpinLock(&gOutputBufferLock, &outputBufferIrql);
InsertTailList(&gOutputBufferList, &NewRecord->List);
KeReleaseSpinLock(&gOutputBufferLock, outputBufferIrql);
} else {
//
// We can no longer log this record, so free the record
//
SpyFreeRecord( NewRecord );
}
KeReleaseSpinLock( &gControlDeviceStateLock, controlDeviceIrql );
return STATUS_SUCCESS;
}
////////////////////////////////////////////////////////////////////////
// //
// FileName cache routines //
// //
////////////////////////////////////////////////////////////////////////
BOOLEAN
SpyGetFullPathName (
IN PFILE_OBJECT FileObject,
IN OUT PUNICODE_STRING FileName,
IN PFILESPY_DEVICE_EXTENSION devExt,
IN NAME_LOOKUP_FLAGS LookupFlags
)
/*++
Routine Description:
This routine retrieves the full pathname of the FileObject. Note that
the buffers containing pathname components may be stored in paged pool,
therefore if we are at DISPATCH_LEVEL we cannot look up the name.
The file is looked up one of the following ways based on the LookupFlags:
1. FlagOn( FileObject->Flags, FO_VOLUME_OPEN ) or (FileObject->FileName.Length == 0).
This is a volume open, so just use DeviceName from the devExt
for the FileName, if it exists.
2. NAMELOOKUPFL_IN_CREATE and NAMELOOKUPFL_OPEN_BY_ID are set.
This is an open by file id, so format the file id into the FileName
string if there is enough room.
3. NAMELOOKUPFL_IN_CREATE set and FileObject->RelatedFileObject != NULL.
This is a relative open, therefore the fullpath file name must
be built up from the name of the FileObject->RelatedFileObject
and FileObject->FileName.
4. NAMELOOKUPFL_IN_CREATE and FileObject->RelatedFileObject == NULL.
This is an absolute open, therefore the fullpath file name is
found in FileObject->FileName.
5. No LookupFlags set.
This is a lookup sometime after CREATE. FileObject->FileName is
no longer guaranteed to be valid, so use ObQueryNameString
to get the fullpath name of the FileObject.
Arguments:
FileObject - Pointer to the FileObject to the get name of.
FileName - Unicode string that will be filled in with the filename, It
is assumed that the caller allocates and frees the memory used by
the string. The buffer and MaximumLength for this string should be
set. If there is room in the buffer, the string will be NULL
terminated.
devExt - Contains the device name and next device object
which are needed to build the full path name.
LookupFlags - The flags to say whether to get the name from the file
object or to get the file id.
Return Value:
Returns TRUE if the returned name should be saved in the cache,
returns FALSE if the returned name should NOT be saved in the cache.
In all cases some sort of valid name is always returned.
--*/
{
NTSTATUS status;
ULONG i;
BOOLEAN retValue = TRUE;
UCHAR buffer[sizeof(FILE_NAME_INFORMATION) + MAX_NAME_SPACE];
//
// Copy over the name the user gave for this device. These names
// should be meaningful to the user. Note that we do not do this for
// NETWORK file system because internally they already show the
// connection name. If this is a direct device open of the network
// file system device, we will copy over the device name to be
// returned to the user.
//
if (FILE_DEVICE_NETWORK_FILE_SYSTEM != devExt->ThisDeviceObject->DeviceType) {
RtlCopyUnicodeString( FileName, &devExt->UserNames );
} else if (FlagOn( FileObject->Flags, FO_DIRECT_DEVICE_OPEN )) {
ASSERT( devExt->ThisDeviceObject->DeviceType == FILE_DEVICE_NETWORK_FILE_SYSTEM );
RtlCopyUnicodeString( FileName, &devExt->DeviceName );
//
// We are now done since there will be no more to the name in this
// case, so return TRUE.
//
return TRUE;
}
//
// See if we can request the name
//
if (FlagOn( LookupFlags, NLFL_ONLY_CHECK_CACHE )) {
RtlAppendUnicodeToString( FileName, L"[-=Not In Cache=-]" );
return FALSE;
}
//
// Can not get the name at DPC level
//
if (KeGetCurrentIrql() > APC_LEVEL) {
RtlAppendUnicodeToString( FileName, L"[-=At DPC Level=-]" );
return FALSE;
}
//
// If there is a ToplevelIrp then this is a nested operation and
// there might be other locks held. Can not get name without the
// potential of deadlocking.
//
if (IoGetTopLevelIrp() != NULL) {
RtlAppendUnicodeToString( FileName, L"[-=Nested Operation=-]" );
return FALSE;
}
//
// CASE 1: This FileObject refers to a Volume open. Either the
// flag is set or no filename is specified.
//
if (FlagOn( FileObject->Flags, FO_VOLUME_OPEN ) ||
(FlagOn( LookupFlags, NLFL_IN_CREATE ) &&
(FileObject->FileName.Length == 0) &&
(FileObject->RelatedFileObject == NULL))) {
//
// We've already copied the VolumeName so just return.
//
}
//
// CASE 2: We are opening the file by ID.
//
else if (FlagOn( LookupFlags, NLFL_IN_CREATE ) &&
FlagOn( LookupFlags, NLFL_OPEN_BY_ID )) {
# define OBJECT_ID_KEY_LENGTH 16
UNICODE_STRING fileIdName;
RtlInitEmptyUnicodeString( &fileIdName,
(PWSTR)buffer,
sizeof(buffer) );
if (FileObject->FileName.Length == sizeof(LONGLONG)) {
//
// Opening by FILE ID, generate a name
//
swprintf( fileIdName.Buffer,
L"<%016I64x>",
*((PLONGLONG)FileObject->FileName.Buffer) );
} else if ((FileObject->FileName.Length == OBJECT_ID_KEY_LENGTH) ||
(FileObject->FileName.Length == OBJECT_ID_KEY_LENGTH +
sizeof(WCHAR)))
{
PUCHAR idBuffer;
//
// Opening by Object ID, generate a name
//
idBuffer = (PUCHAR)&FileObject->FileName.Buffer[0];
if (FileObject->FileName.Length != OBJECT_ID_KEY_LENGTH) {
//
// Skip win32 backslash at start of buffer
//
idBuffer = (PUCHAR)&FileObject->FileName.Buffer[1];
}
swprintf( fileIdName.Buffer,
L"<%08x-%04hx-%04hx-%04hx-%04hx%08x>",
*(PULONG)&idBuffer[0],
*(PUSHORT)&idBuffer[0+4],
*(PUSHORT)&idBuffer[0+4+2],
*(PUSHORT)&idBuffer[0+4+2+2],
*(PUSHORT)&idBuffer[0+4+2+2+2],
*(PULONG)&idBuffer[0+4+2+2+2+2]);
} else {
//
// Unknown ID format
//
swprintf( fileIdName.Buffer,
L"[-=Unknown ID (Len=%u)=-]",
FileObject->FileName.Length);
}
fileIdName.Length = wcslen( fileIdName.Buffer ) * sizeof( WCHAR );
//
// Append the fileIdName to FileName.
//
RtlAppendUnicodeStringToString( FileName, &fileIdName );
//
// Don't cache the ID name
//
retValue = FALSE;
}
//
// CASE 3: We are opening a file that has a RelatedFileObject.
//
else if (FlagOn( LookupFlags, NLFL_IN_CREATE ) &&
(NULL != FileObject->RelatedFileObject)) {
//
// Must be a relative open. Use ObQueryNameString to get
// the name of the related FileObject. Then we will append this
// fileObject's name.
//
// Note:
// The name in FileObject and FileObject->RelatedFileObject are accessible. Names further up
// the related file object chain (ie FileObject->RelatedFileObject->RelatedFileObject)
// may not be accessible. This is the reason we use ObQueryNameString
// to get the name for the RelatedFileObject.
//
PFILE_NAME_INFORMATION relativeNameInfo = (PFILE_NAME_INFORMATION)buffer;
ULONG returnLength;
status = SpyQueryFileSystemForFileName( FileObject->RelatedFileObject,
devExt->AttachedToDeviceObject,
sizeof( buffer ),
relativeNameInfo,
&returnLength );
if (NT_SUCCESS( status ) &&
((FileName->Length + relativeNameInfo->FileNameLength + FileObject->FileName.Length + sizeof( L'\\' ))
<= FileName->MaximumLength)) {
//
// We were able to get the relative fileobject's name and we have
// enough room in the FileName buffer, so build up the file name
// in the following format:
// [volumeName]\[relativeFileObjectName]\[FileObjectName]
// The VolumeName is already in FileName if we've got one.
//
RtlCopyMemory( &FileName->Buffer[FileName->Length/sizeof(WCHAR)],
relativeNameInfo->FileName,
relativeNameInfo->FileNameLength );
FileName->Length += (USHORT)relativeNameInfo->FileNameLength;
} else if ((FileName->Length + FileObject->FileName.Length + sizeof(L"...\\")) <=
FileName->MaximumLength ) {
//
// Either the query for the relative fileObject name was unsuccessful,
// or we don't have enough room for the relativeFileObject name, but we
// do have enough room for "...\[fileObjectName]" in FileName.
//
status = RtlAppendUnicodeToString( FileName, L"...\\" );
ASSERT( status == STATUS_SUCCESS );
}
//
// If there is not a slash and the end of the related file object
// string and there is not a slash at the front of the file object
// string, then add one.
//
if (((FileName->Length < sizeof(WCHAR) ||
(FileName->Buffer[(FileName->Length/sizeof(WCHAR))-1] != L'\\'))) &&
((FileObject->FileName.Length < sizeof(WCHAR)) ||
(FileObject->FileName.Buffer[0] != L'\\')))
{
RtlAppendUnicodeToString( FileName, L"\\" );
}
//
// At this time, copy over the FileObject->FileName to the FileName
// unicode string.
//
RtlAppendUnicodeStringToString( FileName, &FileObject->FileName );
}
//
// CASE 4: We have a open on a file with an absolute path.
//
else if (FlagOn( LookupFlags, NLFL_IN_CREATE ) &&
(FileObject->RelatedFileObject == NULL) ) {
//
// We have an absolute path, so try to copy that into FileName.
//
RtlAppendUnicodeStringToString( FileName, &FileObject->FileName );
}
//
// CASE 5: We are retrieving the file name sometime after the
// CREATE operation.
//
else if (!FlagOn( LookupFlags, NLFL_IN_CREATE )) {
PFILE_NAME_INFORMATION nameInfo = (PFILE_NAME_INFORMATION)buffer;
ULONG returnLength;
status = SpyQueryFileSystemForFileName( FileObject,
devExt->AttachedToDeviceObject,
sizeof( buffer ),
nameInfo,
&returnLength );
if (NT_SUCCESS( status )) {
if ((FileName->Length + nameInfo->FileNameLength) <= FileName->MaximumLength) {
//
// We've got enough room for the file name, so copy it into
// FileName.
//
RtlCopyMemory( &FileName->Buffer[FileName->Length/sizeof(WCHAR)],
nameInfo->FileName,
nameInfo->FileNameLength );
FileName->Length += (USHORT)nameInfo->FileNameLength;
} else {
//
// We don't have enough room for the file name, so copy our
// EXCEED_NAME_BUFFER error message.
//
RtlAppendUnicodeToString( FileName,
L"[-=Name To Large=-]" );
}
} else {
//
// Got an error trying to get the file name from the base file system,
// so put that error message into FileName.
//
swprintf((PWCHAR)buffer,L"[-=Error 0x%x Getting Name=-]",status );
RtlAppendUnicodeToString( FileName, (PWCHAR)buffer );
//
// Don't cache an error-generated name
//
retValue = FALSE;
}
}
//
// When we get here we have a valid name.
// Sometimes when we query a name it has a trailing slash, other times
// it doesn't. To make sure the contexts are correct we are going to
// remove a trailing slash if there is not a ":" just before it.
//
if ((FileName->Length >= (2*sizeof(WCHAR))) &&
(FileName->Buffer[(FileName->Length/sizeof(WCHAR))-1] == L'\\') &&
(FileName->Buffer[(FileName->Length/sizeof(WCHAR))-2] != L':'))
{
FileName->Length -= sizeof(WCHAR);
}
//
// See if we are actually opening the target directory. If so then
// remove the trailing name and slash. Note that we won't remove
// the initial slash (just after the colon).
//
if (FlagOn( LookupFlags, NLFL_OPEN_TARGET_DIR ) &&
(FileName->Length > 0))
{
i = (FileName->Length / sizeof(WCHAR)) - 1;
//
// See if the path ends in a backslash, if so skip over it
// (since the file system did).
//
if ((i > 0) &&
(FileName->Buffer[i] == L'\\') &&
(FileName->Buffer[i-1] != L':')) {
i--;
}
//
// Scan backwards over the last component
//
for ( ;
i > 0;
i-- )
{
if (FileName->Buffer[i] == L'\\') {
if ((i > 0) && (FileName->Buffer[i-1] == L':')) {
i++;
}
FileName->Length = (USHORT)(i * sizeof(WCHAR));
break;
}
}
}
return retValue;
}
NTSTATUS
SpyQueryCompletion (
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp,
IN PKEVENT SynchronizingEvent
)
/*++
Routine Description:
This routine does the cleanup necessary once the query request completed
by the file system.
Arguments:
DeviceObject - This will be NULL since we originated this
Irp.
Irp - The io request structure containing the information
about the current state of our file name query.
SynchronizingEvent - The event to signal to notify the
originator of this request that the operation is
complete.
Return Value:
Returns STATUS_MORE_PROCESSING_REQUIRED so that IO Manager
will not try to free the Irp again.
--*/
{
UNREFERENCED_PARAMETER( DeviceObject );
//
// Make sure that the Irp status is copied over to the user's
// IO_STATUS_BLOCK so that the originator of this irp will know
// the final status of this operation.
//
ASSERT( NULL != Irp->UserIosb );
*Irp->UserIosb = Irp->IoStatus;
//
// Signal SynchronizingEvent so that the originator of this
// Irp know that the operation is completed.
//
KeSetEvent( SynchronizingEvent, IO_NO_INCREMENT, FALSE );
//
// We are now done, so clean up the irp that we allocated.
//
IoFreeIrp( Irp );
//
// If we return STATUS_SUCCESS here, the IO Manager will
// perform the cleanup work that it thinks needs to be done
// for this IO operation. This cleanup work includes:
// * Copying data from the system buffer to the user's buffer
// if this was a buffered IO operation.
// * Freeing any MDLs that are in the Irp.
// * Copying the Irp->IoStatus to Irp->UserIosb so that the
// originator of this irp can see the final status of the
// operation.
// * If this was an asynchronous request or this was a
// synchronous request that got pending somewhere along the
// way, the IO Manager will signal the Irp->UserEvent, if one
// exists, otherwise it will signal the FileObject->Event.
// (This can have REALLY bad implications if the irp originator
// did not an Irp->UserEvent and the irp originator is not
// waiting on the FileObject->Event. It would not be that
// farfetched to believe that someone else in the system is
// waiting on FileObject->Event and who knows who will be
// awoken as a result of the IO Manager signaling this event.
//
// Since some of these operations require the originating thread's
// context (e.g., the IO Manager need the UserBuffer address to
// be valid when copy is done), the IO Manager queues this work
// to an APC on the Irp's originating thread.
//
// Since FileSpy allocated and initialized this irp, we know
// what cleanup work needs to be done. We can do this cleanup
// work more efficiently than the IO Manager since we are handling
// a very specific case. Therefore, it is better for us to
// perform the cleanup work here then free the irp than passing
// control back to the IO Manager to do this work.
//
// By returning STATUS_MORE_PROCESS_REQUIRED, we tell the IO Manager
// to stop processing this irp until it is told to restart processing
// with a call to IoCompleteRequest. Since the IO Manager has
// already performed all the work we want it to do on this
// irp, we do the cleanup work, return STATUS_MORE_PROCESSING_REQUIRED,
// and ask the IO Manager to resume processing by calling
// IoCompleteRequest.
//
return STATUS_MORE_PROCESSING_REQUIRED;
}
NTSTATUS
SpyQueryFileSystemForFileName (
IN PFILE_OBJECT FileObject,
IN PDEVICE_OBJECT NextDeviceObject,
IN ULONG FileNameInfoLength,
OUT PFILE_NAME_INFORMATION FileNameInfo,
OUT PULONG ReturnedLength
)
/*++
Routine Description:
This routine rolls an irp to query the name of the
FileObject parameter from the base file system.
Note: ObQueryNameString CANNOT be used here because it
would cause recursive lookup of the file name for FileObject.
Arguments:
FileObject - the file object for which we want the name.
NextDeviceObject - the device object for the next driver in the
stack. This is where we want to start our request
for the name of FileObject.
FileNameInfoLength - the length in bytes of FileNameInfo
parameter.
FileNameInfo - the buffer that will be receive the name
information. This must be memory that safe to write
to from kernel space.
ReturnedLength - the number of bytes written to FileNameInfo.
Return Value:
Returns the status of the operation.
--*/
{
PIRP irp;
PIO_STACK_LOCATION irpSp;
KEVENT event;
IO_STATUS_BLOCK ioStatus;
NTSTATUS status;
PAGED_CODE();
irp = IoAllocateIrp( NextDeviceObject->StackSize, FALSE );
if (irp == NULL) {
return STATUS_INSUFFICIENT_RESOURCES;
}
//
// Set our current thread as the thread for this
// irp so that the IO Manager always knows which
// thread to return to if it needs to get back into
// the context of the thread that originated this
// irp.
//
irp->Tail.Overlay.Thread = PsGetCurrentThread();
//
// Set that this irp originated from the kernel so that
// the IO Manager knows that the buffers do not
// need to be probed.
//
irp->RequestorMode = KernelMode;
//
// Initialize the UserIosb and UserEvent in the
//
ioStatus.Status = STATUS_SUCCESS;
ioStatus.Information = 0;
irp->UserIosb = &ioStatus;
irp->UserEvent = NULL; //already zeroed
//
// Set the IRP_SYNCHRONOUS_API to denote that this
// is a synchronous IO request.
//
irp->Flags = IRP_SYNCHRONOUS_API;
irpSp = IoGetNextIrpStackLocation( irp );
irpSp->MajorFunction = IRP_MJ_QUERY_INFORMATION;
irpSp->FileObject = FileObject;
//
// Setup the parameters for IRP_MJ_QUERY_INFORMATION.
// The buffer we want to be filled in should be placed in
// the system buffer.
//
irp->AssociatedIrp.SystemBuffer = FileNameInfo;
irpSp->Parameters.QueryFile.Length = FileNameInfoLength;
irpSp->Parameters.QueryFile.FileInformationClass = FileNameInformation;
//
// Set up the completion routine so that we know when our
// request for the file name is completed. At that time,
// we can free the irp.
//
KeInitializeEvent( &event, NotificationEvent, FALSE );
IoSetCompletionRoutine( irp,
SpyQueryCompletion,
&event,
TRUE,
TRUE,
TRUE );
status = IoCallDriver( NextDeviceObject, irp );
SPY_LOG_PRINT( SPYDEBUG_TRACE_NAME_REQUESTS,
("FileSpy!SpyQueryFileSystemForFileName: Issued name request -- IoCallDriver status: 0x%08x\n",
status) );
if (STATUS_PENDING == status) {
(VOID) KeWaitForSingleObject( &event,
Executive,
KernelMode,
FALSE,
NULL );
}
ASSERT(KeReadStateEvent(&event) || !NT_SUCCESS(ioStatus.Status));
SPY_LOG_PRINT( SPYDEBUG_TRACE_NAME_REQUESTS,
("FileSpy!SpyQueryFileSystemForFileName: Finished waiting for name request to complete...\n") );
*ReturnedLength = (ULONG) ioStatus.Information;
return ioStatus.Status;
}
NTSTATUS
SpyQueryInformationFile (
IN PDEVICE_OBJECT NextDeviceObject,
IN PFILE_OBJECT FileObject,
OUT PVOID FileInformation,
IN ULONG Length,
IN FILE_INFORMATION_CLASS FileInformationClass,
OUT PULONG LengthReturned OPTIONAL
)
/*++
Routine Description:
This routine returns the requested information about a specified file.
The information returned is determined by the FileInformationClass that
is specified, and it is placed into the caller's FileInformation buffer.
Arguments:
NextDeviceObject - Supplies the device object where this IO should start
in the device stack.
FileObject - Supplies the file object about which the requested
information should be returned.
FileInformation - Supplies a buffer to receive the requested information
returned about the file. This must be a buffer allocated from kernel
space.
Length - Supplies the length, in bytes, of the FileInformation buffer.
FileInformationClass - Specifies the type of information which should be
returned about the file.
LengthReturned - the number of bytes returned if the operation was
successful.
Return Value:
The status returned is the final completion status of the operation.
--*/
{
PIRP irp = NULL;
PIO_STACK_LOCATION irpSp = NULL;
IO_STATUS_BLOCK ioStatusBlock;
KEVENT event;
NTSTATUS status;
PAGED_CODE();
//
// In DBG builds, make sure that we have valid parameters before we do
// any work here.
//
ASSERT( NULL != NextDeviceObject );
ASSERT( NULL != FileObject );
ASSERT( NULL != FileInformation );
//
// The parameters look ok, so setup the Irp.
//
KeInitializeEvent( &event, NotificationEvent, FALSE );
ioStatusBlock.Status = STATUS_SUCCESS;
ioStatusBlock.Information = 0;
irp = IoAllocateIrp( NextDeviceObject->StackSize, FALSE );
if (irp == NULL) {
return STATUS_INSUFFICIENT_RESOURCES;
}
//
// Set our current thread as the thread for this
// irp so that the IO Manager always knows which
// thread to return to if it needs to get back into
// the context of the thread that originated this
// irp.
//
irp->Tail.Overlay.Thread = PsGetCurrentThread();
//
// Set that this irp originated from the kernel so that
// the IO Manager knows that the buffers do not
// need to be probed.
//
irp->RequestorMode = KernelMode;
//
// Initialize the UserIosb and UserEvent in the
irp->UserIosb = &ioStatusBlock;
irp->UserEvent = NULL;
//
// Set the IRP_SYNCHRONOUS_API to denote that this
// is a synchronous IO request.
//
irp->Flags = IRP_SYNCHRONOUS_API;
irpSp = IoGetNextIrpStackLocation( irp );
irpSp->MajorFunction = IRP_MJ_QUERY_INFORMATION;
irpSp->FileObject = FileObject;
//
// Setup the parameters for IRP_MJ_QUERY_INFORMATION. These
// were supplied by the caller of this routine.
// The buffer we want to be filled in should be placed in
// the system buffer.
//
irp->AssociatedIrp.SystemBuffer = FileInformation;
irpSp->Parameters.QueryFile.Length = Length;
irpSp->Parameters.QueryFile.FileInformationClass = FileInformationClass;
//
// Set up the completion routine so that we know when our
// request for the file name is completed. At that time,
// we can free the irp.
//
IoSetCompletionRoutine( irp,
SpyQueryCompletion,
&event,
TRUE,
TRUE,
TRUE );
status = IoCallDriver( NextDeviceObject, irp );
if (STATUS_PENDING == status) {
KeWaitForSingleObject( &event,
Executive,
KernelMode,
FALSE,
NULL );
}
//
// Verify the completion has actually been run
//
ASSERT(KeReadStateEvent(&event) || !NT_SUCCESS(ioStatusBlock.Status));
if (ARGUMENT_PRESENT(LengthReturned)) {
*LengthReturned = (ULONG) ioStatusBlock.Information;
}
return ioStatusBlock.Status;
}
////////////////////////////////////////////////////////////////////////
// //
// Common attachment and detachment routines //
// //
////////////////////////////////////////////////////////////////////////
//
// VERSION NOTE:
//
// To be able to safely find out if our filter is attached to a device given
// its name on Windows 2000 and later, we need to use the approach in
// SpyIsAttachedToDeviceByUserDeviceName. This method uses APIs that are
// available on Windows 2000 and later. On Windows XP or later, you could
// change this routine to separate the translation from DeviceName to device
// object from the search to see if our filter's device is attached to the
// device stack. In Windows XP and later, the logic to translate the
// DeviceName to the device object is the same, but you can use the logic
// in SpyIsAttachedToDeviceWXPAndLater to find your filter's device object
// in the device stack safely.
//
NTSTATUS
SpyIsAttachedToDeviceByUserDeviceName (
IN PUNICODE_STRING DeviceName,
IN OUT PBOOLEAN IsAttached,
IN OUT PDEVICE_OBJECT *StackDeviceObject,
IN OUT PDEVICE_OBJECT *OurAttachedDeviceObject
)
/*++
Routine Description:
This routine maps a user's device name to a file system device stack, if
one exists. Then this routine walks the device stack to find a device
object belonging to our driver.
The APIs used here to walk the device stack are all safe to use while you
are guaranteed that the device stack will not go away. We enforce this
guarantee
Arguments:
DeviceName - The name provided by the user to identify this device.
IsAttached - This is set to TRUE if our filter is attached to this device
stack, otherwise this is set to FALSE.
StackDeviceObject - Set to a device object in the stack identified by the
DeviceName. If this is non-NULL, the caller is responsible for removing
the reference put on this object before it was returned.
AttachedDeviceObject - Set to the deviceObject which FileSpy has previously
attached to the device stack identify by DeviceName. If this is
non-NULL, the caller is responsible for removing the reference put on
this object before it was returned.
Return Value:
Returns STATUS_SUCCESS if we were able to successfully translate the
DeviceName into a device stack and return the StackDeviceObject. If an
error occurs during the translation of the DeviceName into a device stack,
the appropriate error code is returned.
--*/
{
WCHAR nameBuf[DEVICE_NAMES_SZ];
UNICODE_STRING volumeNameUnicodeString;
NTSTATUS status;
OBJECT_ATTRIBUTES objectAttributes;
IO_STATUS_BLOCK openStatus;
PFILE_OBJECT volumeFileObject;
HANDLE fileHandle;
PDEVICE_OBJECT baseFsDeviceObject;
PAGED_CODE();
//
// Initialize return state
//
ASSERT( NULL != StackDeviceObject );
ASSERT( NULL != OurAttachedDeviceObject );
ASSERT( NULL != IsAttached );
*StackDeviceObject = NULL;
*OurAttachedDeviceObject = NULL;
*IsAttached = FALSE;
//
// Setup the name to open
//
RtlInitEmptyUnicodeString( &volumeNameUnicodeString, nameBuf, sizeof( nameBuf ) );
RtlAppendUnicodeToString( &volumeNameUnicodeString, L"\\DosDevices\\" );
RtlAppendUnicodeStringToString( &volumeNameUnicodeString, DeviceName );
InitializeObjectAttributes( &objectAttributes,
&volumeNameUnicodeString,
OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
NULL,
NULL);
//
// open the file object for the given device
//
status = ZwCreateFile( &fileHandle,
SYNCHRONIZE|FILE_READ_DATA,
&objectAttributes,
&openStatus,
NULL,
0,
FILE_SHARE_READ|FILE_SHARE_WRITE,
FILE_OPEN,
FILE_SYNCHRONOUS_IO_NONALERT,
NULL,
0);
if (STATUS_OBJECT_PATH_NOT_FOUND == status ||
STATUS_OBJECT_NAME_INVALID == status) {
//
// Maybe this name didn't need the "\DosDevices\" prepended to the
// name. Try the open again using just the DeviceName passed in.
//
InitializeObjectAttributes( &objectAttributes,
DeviceName,
OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
NULL,
NULL);
//
// open the file object for the given device
//
status = ZwCreateFile( &fileHandle,
SYNCHRONIZE|FILE_READ_DATA,
&objectAttributes,
&openStatus,
NULL,
0,
FILE_SHARE_READ|FILE_SHARE_WRITE,
FILE_OPEN,
FILE_SYNCHRONOUS_IO_NONALERT,
NULL,
0);
if (!NT_SUCCESS( status )) {
return status;
}
//
// We were able to open the device using the name passed in, so
// now we will fall through and do the rest of this work.
//
} else if (!NT_SUCCESS( status )) {
return status;
}
//
// get a pointer to the volumes file object
//
status = ObReferenceObjectByHandle( fileHandle,
FILE_READ_DATA,
*IoFileObjectType,
KernelMode,
&volumeFileObject,
NULL);
if(!NT_SUCCESS( status )) {
ZwClose( fileHandle );
return status;
}
//
// Get the device object we want to attach to (parent device object in chain)
//
baseFsDeviceObject = IoGetBaseFileSystemDeviceObject( volumeFileObject );
if (baseFsDeviceObject == NULL) {
ObDereferenceObject( volumeFileObject );
ZwClose( fileHandle );
return STATUS_INVALID_DEVICE_STATE;
}
//
// Now see if we are attached to this device stack. Note that we need to
// keep this file object open while we do this search to ensure that the
// stack won't get torn down while SpyIsAttachedToDevice does its work.
//
*IsAttached = SpyIsAttachedToDevice( baseFsDeviceObject,
OurAttachedDeviceObject );
//
// Return the base file system's device object to represent this device
// stack even if we didn't find our device object in the stack.
//
ObReferenceObject( baseFsDeviceObject );
*StackDeviceObject = baseFsDeviceObject;
//
// Close our handle
//
ObDereferenceObject( volumeFileObject );
ZwClose( fileHandle );
return STATUS_SUCCESS;
}
//
// VERSION NOTE:
//
// In Windows 2000, the APIs to safely walk an arbitrary file system device
// stack were not supported. If we can guarantee that a device stack won't
// be torn down during the walking of the device stack, we can walk from
// the base file system's device object up to the top of the device stack
// to see if we are attached. We know the device stack will not go away if
// we are in the process of processing a mount request OR we have a file object
// open on this device.
//
// In Windows XP and later, the IO Manager provides APIs that will allow us to
// walk through the chain safely using reference counts to protect the device
// object from going away while we are inspecting it. This can be done at any
// time.
//
// MULTIVERSION NOTE:
//
// If built for Windows XP or later, this driver is built to run on
// multiple versions. When this is the case, we will test for the presence of
// the new IO Manager routines that allow for a filter to safely walk the file
// system device stack and use those APIs if they are present to determine if
// we have already attached to this volume. If these new IO Manager routines
// are not present, we will assume that we are at the bottom of the file
// system stack and walk up the stack looking for our device object.
//
BOOLEAN
SpyIsAttachedToDevice (
IN PDEVICE_OBJECT DeviceObject,
IN OUT PDEVICE_OBJECT *AttachedDeviceObject OPTIONAL
)
{
PAGED_CODE();
#if WINVER >= 0x0501
if (IS_WINDOWSXP_OR_LATER()) {
ASSERT( NULL != gSpyDynamicFunctions.GetLowerDeviceObject &&
NULL != gSpyDynamicFunctions.GetDeviceAttachmentBaseRef );
return SpyIsAttachedToDeviceWXPAndLater( DeviceObject, AttachedDeviceObject );
} else {
#endif
return SpyIsAttachedToDeviceW2K( DeviceObject, AttachedDeviceObject );
#if WINVER >= 0x0501
}
#endif
}
BOOLEAN
SpyIsAttachedToDeviceW2K (
PDEVICE_OBJECT DeviceObject,
PDEVICE_OBJECT *AttachedDeviceObject OPTIONAL
)
/*++
Routine Description:
VERSION: Windows 2000
This routine walks up the device stack from the DeviceObject passed in
looking for a device object that belongs to our filter.
Note: For this routine to operate safely, the caller must ensure two
things:
* the DeviceObject is the base file system's device object and therefore
is at the bottom of the file system stack
* this device stack won't be going away while we walk up this stack. If
we currently have a file object open for this device stack or we are
in the process of mounting this device, this guarantee is satisfied.
Arguments:
DeviceObject - The device chain we want to look through
AttachedDeviceObject - Set to the deviceObject which FileSpy
has previously attached to DeviceObject. If this is non-NULL,
the caller must clear the reference put on this device object.
Return Value:
TRUE if we are attached, FALSE if not
--*/
{
PDEVICE_OBJECT currentDeviceObject;
PAGED_CODE();
for (currentDeviceObject = DeviceObject;
currentDeviceObject != NULL;
currentDeviceObject = currentDeviceObject->AttachedDevice) {
if (IS_FILESPY_DEVICE_OBJECT( currentDeviceObject )) {
//
// We are attached. If requested, return the found device object.
//
if (ARGUMENT_PRESENT( AttachedDeviceObject )) {
ObReferenceObject( currentDeviceObject );
*AttachedDeviceObject = currentDeviceObject;
}
return TRUE;
}
}
//
// We did not find ourselves on the attachment chain. Return a NULL
// device object pointer (if requested) and return we did not find
// ourselves.
//
if (ARGUMENT_PRESENT(AttachedDeviceObject)) {
*AttachedDeviceObject = NULL;
}
return FALSE;
}
#if WINVER >= 0x0501
BOOLEAN
SpyIsAttachedToDeviceWXPAndLater (
PDEVICE_OBJECT DeviceObject,
PDEVICE_OBJECT *AttachedDeviceObject OPTIONAL
)
/*++
Routine Description:
VERSION: Windows XP and later
This walks down the attachment chain looking for a device object that
belongs to this driver. If one is found, the attached device object
is returned in AttachedDeviceObject.
Arguments:
DeviceObject - The device chain we want to look through
AttachedDeviceObject - Set to the deviceObject which FileSpy
has previously attached to DeviceObject.
Return Value:
TRUE if we are attached, FALSE if not
--*/
{
PDEVICE_OBJECT currentDevObj;
PDEVICE_OBJECT nextDevObj;
PAGED_CODE();
//
// Get the device object at the TOP of the attachment chain
//
ASSERT( NULL != gSpyDynamicFunctions.GetAttachedDeviceReference );
currentDevObj = (gSpyDynamicFunctions.GetAttachedDeviceReference)( DeviceObject );
//
// Scan down the list to find our device object.
//
do {
if (IS_FILESPY_DEVICE_OBJECT( currentDevObj )) {
//
// We have found that we are already attached. If we are
// returning the device object, leave it referenced else remove
// the reference.
//
if (NULL != AttachedDeviceObject) {
*AttachedDeviceObject = currentDevObj;
} else {
ObDereferenceObject( currentDevObj );
}
return TRUE;
}
//
// Get the next attached object. This puts a reference on
// the device object.
//
ASSERT( NULL != gSpyDynamicFunctions.GetLowerDeviceObject );
nextDevObj = (gSpyDynamicFunctions.GetLowerDeviceObject)( currentDevObj );
//
// Dereference our current device object, before
// moving to the next one.
//
ObDereferenceObject( currentDevObj );
currentDevObj = nextDevObj;
} while (NULL != currentDevObj);
//
// Mark no device returned
//
if (ARGUMENT_PRESENT(AttachedDeviceObject)) {
*AttachedDeviceObject = NULL;
}
return FALSE;
}
#endif //WINVER >= 0x0501
NTSTATUS
SpyAttachToMountedDevice (
IN PDEVICE_OBJECT DeviceObject,
IN PDEVICE_OBJECT FilespyDeviceObject
)
/*++
Routine Description:
This routine will attach the FileSpyDeviceObject to the filter stack
that DeviceObject is in.
NOTE: If there is an error in attaching, the caller is responsible
for deleting the FilespyDeviceObject.
Arguments:
DeviceObject - The device object in the stack to which we want to attach.
FilespyDeviceObject - The filespy device object that is to be attached to
"DeviceObject".
Return Value:
Returns STATUS_SUCCESS if the filespy deviceObject could be attached,
otherwise an appropriate error code is returned.
--*/
{
PFILESPY_DEVICE_EXTENSION devExt = FilespyDeviceObject->DeviceExtension;
NTSTATUS status = STATUS_SUCCESS;
ULONG i;
PAGED_CODE();
ASSERT( IS_FILESPY_DEVICE_OBJECT( FilespyDeviceObject ) );
#if WINVER >= 0x0501
ASSERT( !SpyIsAttachedToDevice( DeviceObject, NULL ) );
#endif
//
// Insert pointer from extension back to owning device object
//
devExt->ThisDeviceObject = FilespyDeviceObject;
//
// Propagate flags from Device Object we are trying to attach to.
// Note that we do this before the actual attachment to make sure
// the flags are properly set once we are attached (since an IRP
// can come in immediately after attachment but before the flags would
// be set).
//
if (FlagOn( DeviceObject->Flags, DO_BUFFERED_IO )) {
SetFlag( FilespyDeviceObject->Flags, DO_BUFFERED_IO );
}
if (FlagOn( DeviceObject->Flags, DO_DIRECT_IO )) {
SetFlag( FilespyDeviceObject->Flags, DO_DIRECT_IO );
}
//
// It is possible for this attachment request to fail because this device
// object has not finished initializing. This can occur if this filter
// loaded just as this volume was being mounted.
//
for (i=0; i < 8; i++) {
LARGE_INTEGER interval;
//
// Attach our device object to the given device object
// The only reason this can fail is if someone is trying to dismount
// this volume while we are attaching to it.
//
status = SpyAttachDeviceToDeviceStack( FilespyDeviceObject,
DeviceObject,
&devExt->AttachedToDeviceObject );
if (NT_SUCCESS(status) ) {
//
// Do all common initializing of the device extension
//
SetFlag(devExt->Flags,IsVolumeDeviceObject);
RtlInitEmptyUnicodeString( &devExt->UserNames,
devExt->UserNamesBuffer,
sizeof(devExt->UserNamesBuffer) );
SpyInitDeviceNamingEnvironment( FilespyDeviceObject );
SPY_LOG_PRINT( SPYDEBUG_DISPLAY_ATTACHMENT_NAMES,
("FileSpy!SpyAttachToMountedDevice: Attaching to volume %p \"%wZ\"\n",
devExt->AttachedToDeviceObject,
&devExt->DeviceName) );
//
// Add this device to our attachment list
//
ExAcquireFastMutex( &gSpyDeviceExtensionListLock );
InsertTailList( &gSpyDeviceExtensionList, &devExt->NextFileSpyDeviceLink );
ExReleaseFastMutex( &gSpyDeviceExtensionListLock );
SetFlag(devExt->Flags,ExtensionIsLinked);
return STATUS_SUCCESS;
}
//
// Delay, giving the device object a chance to finish its
// initialization so we can try again
//
interval.QuadPart = (500 * DELAY_ONE_MILLISECOND); //delay 1/2 second
KeDelayExecutionThread( KernelMode, FALSE, &interval );
}
return status;
}
VOID
SpyCleanupMountedDevice (
IN PDEVICE_OBJECT DeviceObject
)
/*++
Routine Description:
This cleans up any allocated memory in the device extension.
Arguments:
DeviceObject - The device we are cleaning up
Return Value:
--*/
{
PFILESPY_DEVICE_EXTENSION devExt = DeviceObject->DeviceExtension;
PAGED_CODE();
ASSERT(IS_FILESPY_DEVICE_OBJECT( DeviceObject ));
SpyCleanupDeviceNamingEnvironment( DeviceObject );
//
// Unlink from global list
//
if (FlagOn(devExt->Flags,ExtensionIsLinked)) {
ExAcquireFastMutex( &gSpyDeviceExtensionListLock );
RemoveEntryList( &devExt->NextFileSpyDeviceLink );
ExReleaseFastMutex( &gSpyDeviceExtensionListLock );
ClearFlag(devExt->Flags,ExtensionIsLinked);
}
}
////////////////////////////////////////////////////////////////////////
// //
// Start/stop logging routines //
// //
////////////////////////////////////////////////////////////////////////
//
// VERSION NOTE:
//
// On Windows 2000, we will try to attach a new FileSpy device object to the
// device stack represented by the DeviceObject parameter. We cannot get the
// real disk device at this time, so this field will be set to NULL in the
// device extension. We also cannot get the device name as it is named
// in the storage stack for this volume (e.g., \Device\HarddiskVolume1), so we
// will just use the user's name for the device for our device name. On
// Windows 2000, this information is only available as the device mounts.
//
// On Windows XP and later, we will try to attach a new FileSpy device object
// to the device stack represented by the DeviceObject parameter. We are able
// to get the disk device object for this stack, so that will be appropriately
// set in the device extension. We will also be able to get the device name
// as it is named by the storage stack.
//
// MULTIVERSION NOTE:
//
// In SpyAttachToDeviceOnDemand, you see the code to determine which method of
// determining if we are already attached based on the dynamically loaded
// functions present. If this driver is build for Windows 2000 specifically,
// this logic will not be used.
//
NTSTATUS
SpyAttachToDeviceOnDemand (
IN PDEVICE_OBJECT DeviceObject,
IN PUNICODE_STRING UserDeviceName,
IN OUT PDEVICE_OBJECT *FileSpyDeviceObject
)
/*++
Routine Description:
This routine does what is necessary to attach to a device sometime after
the device has been mounted.
Arguments:
DeviceObject - The device object that represents the file system stack
for the volume named by UserDeviceName.
UserDeviceName - Name of device for which logging should be started
FileSpyDeviceObject - Set to the new filespy device object that was
attached if we could successfully attach.
Return Value:
STATUS_SUCCESS if we were able to attach, or an appropriate error code
otherwise.
--*/
{
PAGED_CODE();
//
// If this device is a DFS device, we do not want to attach to it, so
// do this quick check here and return an error if this is the case.
//
// DFS will just redirect the operation to the appropriate redirector. If
// you are interested in monitoring these IOs, you should attach to the
// redirectors. You cannot attach to these on demand by naming the DFS
// device, therefore we fail these requests.
//
if (DeviceObject->DeviceType == FILE_DEVICE_DFS) {
return STATUS_NOT_SUPPORTED;
}
#if WINVER >= 0x0501
if (IS_WINDOWSXP_OR_LATER()) {
ASSERT( NULL != gSpyDynamicFunctions.GetDeviceAttachmentBaseRef &&
NULL != gSpyDynamicFunctions.GetDiskDeviceObject );
return SpyAttachToDeviceOnDemandWXPAndLater( DeviceObject,
UserDeviceName,
FileSpyDeviceObject );
} else {
#endif
return SpyAttachToDeviceOnDemandW2K( DeviceObject,
UserDeviceName,
FileSpyDeviceObject );
#if WINVER >= 0x0501
}
#endif
}
NTSTATUS
SpyAttachToDeviceOnDemandW2K (
IN PDEVICE_OBJECT DeviceObject,
IN PUNICODE_STRING UserDeviceName,
IN OUT PDEVICE_OBJECT *FileSpyDeviceObject
)
/*++
Routine Description:
VERSION: Windows 2000
This routine does what is necessary to attach to a device sometime after
the device has been mounted.
Note that on Windows 2000, we cannot get the disk device object, therefore
we will just use the User's device name as our name here.
Arguments:
DeviceObject - The device object that represents the file system stack
for the volume named by UserDeviceName.
UserDeviceName - Name of device for which logging should be started
FileSpyDeviceObject - Set to the new filespy device object that was
attached if we could successfully attach.
Return Value:
STATUS_SUCCESS if we were able to attach, or an appropriate error code
otherwise.
--*/
{
NTSTATUS status;
PFILESPY_DEVICE_EXTENSION devExt;
PAGED_CODE();
ASSERT( FileSpyDeviceObject != NULL );
//
// Create a new device object so we can attach it in the filter stack
//
status = IoCreateDevice( gFileSpyDriverObject,
sizeof( FILESPY_DEVICE_EXTENSION ),
NULL,
DeviceObject->DeviceType,
0,
FALSE,
FileSpyDeviceObject );
if (!NT_SUCCESS( status )) {
return status;
}
//
// Set disk device object
//
devExt = (*FileSpyDeviceObject)->DeviceExtension;
devExt->Flags = 0;
//
// We cannot get the disk device object when we attach on demand in W2K.
//
devExt->DiskDeviceObject = NULL;
//
// Set Device Name, we will just use the user's device name on W2K.
//
RtlInitEmptyUnicodeString( &devExt->DeviceName,
devExt->DeviceNameBuffer,
sizeof(devExt->DeviceNameBuffer) );
RtlAppendUnicodeStringToString( &devExt->DeviceName,
UserDeviceName );
//
// Call the routine to attach to a mounted device.
//
status = SpyAttachToMountedDevice( DeviceObject,
*FileSpyDeviceObject );
if (!NT_SUCCESS( status )) {
SPY_LOG_PRINT( SPYDEBUG_ERROR,
("FileSpy!SpyStartLoggingDevice: Could not attach to \"%wZ\"; logging not started.\n",
UserDeviceName) );
SpyCleanupMountedDevice( *FileSpyDeviceObject );
IoDeleteDevice( *FileSpyDeviceObject );
*FileSpyDeviceObject = NULL;
}
return status;
}
#if WINVER >= 0x0501
NTSTATUS
SpyAttachToDeviceOnDemandWXPAndLater (
IN PDEVICE_OBJECT DeviceObject,
IN PUNICODE_STRING UserDeviceName,
IN OUT PDEVICE_OBJECT *FileSpyDeviceObject
)
/*++
Routine Description:
This routine does what is necessary to attach to a device sometime after
the device has been mounted.
Arguments:
DeviceObject - The device object that represents the file system stack
for the volume named by UserDeviceName.
UserDeviceName - Name of device for which logging should be started
FileSpyDeviceObject - Set to the new filespy device object that was
attached if we could successfully attach.
Return Value:
STATUS_SUCCESS if we were able to attach, or an appropriate error code
otherwise.
--*/
{
NTSTATUS status;
PFILESPY_DEVICE_EXTENSION devExt;
PDEVICE_OBJECT baseFileSystemDeviceObject = NULL;
PDEVICE_OBJECT diskDeviceObject = NULL;
PAGED_CODE();
UNREFERENCED_PARAMETER( UserDeviceName );
ASSERT( FileSpyDeviceObject != NULL );
//
// If this is a network file system, there will not be a disk device
// associated with this device, so there is no need to make this request
// of the IO Manager. We will get the name of the network file system
// later from the baseFileSystemDeviceObject vs. the diskDeviceObject
// which is used to retrieve the device name for local volumes.
//
baseFileSystemDeviceObject = (gSpyDynamicFunctions.GetDeviceAttachmentBaseRef)( DeviceObject );
if (FILE_DEVICE_NETWORK_FILE_SYSTEM != baseFileSystemDeviceObject->DeviceType) {
//
// If this is not a network file system, query the IO Manager to get
// the diskDeviceObject. We will only attach if this device has a
// disk device object.
//
// It may not have a disk device object for the following reasons:
// - It is a control device object for a driver
// - There is no media in the device.
//
status = (gSpyDynamicFunctions.GetDiskDeviceObject)( baseFileSystemDeviceObject,
&diskDeviceObject );
if (!NT_SUCCESS( status )) {
SPY_LOG_PRINT( SPYDEBUG_ERROR,
("FileSpy!SpyStartLoggingDevice: No disk device object exists for \"%wZ\"; cannot log this volume.\n",
UserDeviceName) );
goto SpyAttachToDeviceOnDemand_Exit;
}
}
//
// Create a new device object so we can attach it in the filter stack
//
status = IoCreateDevice( gFileSpyDriverObject,
sizeof( FILESPY_DEVICE_EXTENSION ),
NULL,
DeviceObject->DeviceType,
0,
FALSE,
FileSpyDeviceObject );
if (!NT_SUCCESS( status )) {
goto SpyAttachToDeviceOnDemand_Exit;
}
//
// Set disk device object
//
devExt = (*FileSpyDeviceObject)->DeviceExtension;
devExt->Flags = 0;
devExt->DiskDeviceObject = diskDeviceObject;
//
// Set Device Name
//
RtlInitEmptyUnicodeString( &devExt->DeviceName,
devExt->DeviceNameBuffer,
sizeof(devExt->DeviceNameBuffer) );
if (NULL != diskDeviceObject) {
SpyGetObjectName( diskDeviceObject,
&devExt->DeviceName );
} else {
ASSERT( NULL != baseFileSystemDeviceObject &&
FILE_DEVICE_NETWORK_FILE_SYSTEM == baseFileSystemDeviceObject->DeviceType);
SpyGetObjectName( baseFileSystemDeviceObject,
&devExt->DeviceName );
}
//
// Call the routine to attach to a mounted device.
//
status = SpyAttachToMountedDevice( DeviceObject,
*FileSpyDeviceObject );
if (!NT_SUCCESS( status )) {
SPY_LOG_PRINT( SPYDEBUG_ERROR,
("FileSpy!SpyStartLoggingDevice: Could not attach to \"%wZ\"; logging not started.\n",
UserDeviceName) );
SpyCleanupMountedDevice( *FileSpyDeviceObject );
IoDeleteDevice( *FileSpyDeviceObject );
*FileSpyDeviceObject = NULL;
goto SpyAttachToDeviceOnDemand_Exit;
}
SpyAttachToDeviceOnDemand_Exit:
if (NULL != baseFileSystemDeviceObject) {
ObDereferenceObject( baseFileSystemDeviceObject );
}
if (NULL != diskDeviceObject) {
ObDereferenceObject( diskDeviceObject );
}
return status;
}
#endif
NTSTATUS
SpyStartLoggingDevice (
IN PWSTR UserDeviceName
)
/*++
Routine Description:
This routine ensures that we are attached to the specified device
then turns on logging for that device.
Note: Since all network drives through LAN Manager are represented by _
one_ device object, we want to only attach to this device stack once
and use only one device extension to represent all these drives.
Since FileSpy does not do anything to filter I/O on the LAN Manager's
device object to only log the I/O to the requested drive, the user
will see all I/O to a network drive it he/she is attached to a
network drive.
Arguments:
UserDeviceName - Name of device for which logging should be started
Return Value:
STATUS_SUCCESS if the logging has been successfully started, or
an appropriate error code if the logging could not be started.
--*/
{
UNICODE_STRING userDeviceName;
NTSTATUS status;
PFILESPY_DEVICE_EXTENSION devExt;
BOOLEAN isAttached = FALSE;
PDEVICE_OBJECT stackDeviceObject;
PDEVICE_OBJECT filespyDeviceObject;
PAGED_CODE();
//
// Check to see if we have previously attached to this device by
// opening this device name then looking through its list of attached
// devices.
//
RtlInitUnicodeString( &userDeviceName, UserDeviceName );
status = SpyIsAttachedToDeviceByUserDeviceName( &userDeviceName,
&isAttached,
&stackDeviceObject,
&filespyDeviceObject );
if (!NT_SUCCESS( status )) {
//
// There was an error, so return the error code.
//
return status;
}
if (isAttached) {
//
// We are already attached, so just make sure that logging is turned on
// for this device.
//
ASSERT( NULL != filespyDeviceObject );
devExt = filespyDeviceObject->DeviceExtension;
SetFlag(devExt->Flags,LogThisDevice);
SpyStoreUserName( devExt, &userDeviceName );
//
// Clear the reference that was returned from SpyIsAttachedToDevice.
//
ObDereferenceObject( filespyDeviceObject );
} else {
status = SpyAttachToDeviceOnDemand( stackDeviceObject,
&userDeviceName,
&filespyDeviceObject );
if (!NT_SUCCESS( status )) {
ObDereferenceObject( stackDeviceObject );
return status;
}
ASSERT( filespyDeviceObject != NULL );
devExt = filespyDeviceObject->DeviceExtension;
//
// We successfully attached so finish our device extension
// initialization. Along this code path, we want to turn on
// logging and store our device name.
//
SetFlag(devExt->Flags,LogThisDevice);
//
// We want to store the name that was used by the user-mode
// application to name this device.
//
SpyStoreUserName( devExt, &userDeviceName );
//
//
// Finished all initialization of the new device object, so clear the
// initializing flag now. This allows other filters to now attach
// to our device object.
//
//
ClearFlag( filespyDeviceObject->Flags, DO_DEVICE_INITIALIZING );
}
ObDereferenceObject( stackDeviceObject );
return STATUS_SUCCESS;
}
NTSTATUS
SpyStopLoggingDevice (
IN PWSTR DeviceName
)
/*++
Routine Description:
This routine stop logging the specified device. Since you can not
physically detach from devices, this routine simply sets a flag saying
to not log the device anymore.
Note: Since all network drives are represented by _one_ device object,
and, therefore, one device extension, if the user detaches from one
network drive, it has the affect of detaching from _all_ network
devices.
Arguments:
DeviceName - The name of the device to stop logging.
Return Value:
NT Status code
--*/
{
WCHAR nameBuf[DEVICE_NAMES_SZ];
UNICODE_STRING volumeNameUnicodeString;
PDEVICE_OBJECT deviceObject;
PDEVICE_OBJECT filespyDeviceObject;
BOOLEAN isAttached = FALSE;
PFILESPY_DEVICE_EXTENSION devExt;
NTSTATUS status;
PAGED_CODE();
RtlInitEmptyUnicodeString( &volumeNameUnicodeString, nameBuf, sizeof( nameBuf ) );
RtlAppendUnicodeToString( &volumeNameUnicodeString, DeviceName );
status = SpyIsAttachedToDeviceByUserDeviceName( &volumeNameUnicodeString,
&isAttached,
&deviceObject,
&filespyDeviceObject );
if (!NT_SUCCESS( status )) {
//
// We could not get the deviceObject from this DeviceName, so
// return the error code.
//
return status;
}
//
// Find Filespy's device object from the device stack to which
// deviceObject is attached.
//
if (isAttached) {
//
// FileSpy is attached and FileSpy's deviceObject was returned.
//
ASSERT( NULL != filespyDeviceObject );
devExt = filespyDeviceObject->DeviceExtension;
//
// Stop logging
//
ClearFlag(devExt->Flags,LogThisDevice);
status = STATUS_SUCCESS;
ObDereferenceObject( filespyDeviceObject );
} else {
status = STATUS_INVALID_PARAMETER;
}
ObDereferenceObject( deviceObject );
return status;
}
////////////////////////////////////////////////////////////////////////
// //
// Attaching/detaching to all volumes in system routines //
// //
////////////////////////////////////////////////////////////////////////
NTSTATUS
SpyAttachToFileSystemDevice (
IN PDEVICE_OBJECT DeviceObject,
IN PUNICODE_STRING DeviceName
)
/*++
Routine Description:
This will attach to the given file system device object. We attach to
these devices so we will know when new devices are mounted.
Arguments:
DeviceObject - The device to attach to
DeviceName - Contains the name of this device.
Return Value:
Status of the operation
--*/
{
PDEVICE_OBJECT filespyDeviceObject;
PFILESPY_DEVICE_EXTENSION devExt;
UNICODE_STRING fsrecName;
NTSTATUS status;
UNICODE_STRING tempName;
WCHAR tempNameBuffer[DEVICE_NAMES_SZ];
PAGED_CODE();
//
// See if this is a file system we care about. If not, return.
//
if (!IS_SUPPORTED_DEVICE_TYPE(DeviceObject->DeviceType)) {
return STATUS_SUCCESS;
}
//
// See if this is Microsoft's file system recognizer device (see if the name of the
// driver is the FS_REC driver). If so skip it. We don't need to
// attach to file system recognizer devices since we can just wait for the
// real file system driver to load. Therefore, if we can identify them, we won't
// attach to them.
//
RtlInitUnicodeString( &fsrecName, L"\\FileSystem\\Fs_Rec" );
RtlInitEmptyUnicodeString( &tempName,
tempNameBuffer,
sizeof(tempNameBuffer) );
SpyGetObjectName( DeviceObject->DriverObject, &tempName );
if (RtlCompareUnicodeString( &tempName, &fsrecName, TRUE ) == 0) {
return STATUS_SUCCESS;
}
//
// Create a new device object we can attach with
//
status = IoCreateDevice( gFileSpyDriverObject,
sizeof( FILESPY_DEVICE_EXTENSION ),
(PUNICODE_STRING) NULL,
DeviceObject->DeviceType,
0,
FALSE,
&filespyDeviceObject );
if (!NT_SUCCESS( status )) {
SPY_LOG_PRINT( SPYDEBUG_ERROR,
("FileSpy!SpyAttachToFileSystemDevice: Error creating volume device object for \"%wZ\", status=%08x\n",
DeviceName,
status) );
return status;
}
//
// Load extension, set device object associated with extension
//
devExt = filespyDeviceObject->DeviceExtension;
devExt->Flags = 0;
devExt->ThisDeviceObject = filespyDeviceObject;
//
// Propagate flags from Device Object we are trying to attach to.
// Note that we do this before the actual attachment to make sure
// the flags are properly set once we are attached (since an IRP
// can come in immediately after attachment but before the flags would
// be set).
//
if ( FlagOn( DeviceObject->Flags, DO_BUFFERED_IO )) {
SetFlag( filespyDeviceObject->Flags, DO_BUFFERED_IO );
}
if ( FlagOn( DeviceObject->Flags, DO_DIRECT_IO )) {
SetFlag( filespyDeviceObject->Flags, DO_DIRECT_IO );
}
if ( FlagOn( DeviceObject->Characteristics, FILE_DEVICE_SECURE_OPEN ) ) {
SetFlag( filespyDeviceObject->Characteristics, FILE_DEVICE_SECURE_OPEN );
}
//
// Do the attachment
//
status = SpyAttachDeviceToDeviceStack( filespyDeviceObject,
DeviceObject,
&devExt->AttachedToDeviceObject );
if (!NT_SUCCESS( status )) {
SPY_LOG_PRINT( SPYDEBUG_ERROR,
("FileSpy!SpyAttachToFileSystemDevice: Could not attach FileSpy to the filesystem control device object \"%wZ\".\n",
DeviceName) );
goto ErrorCleanupDevice;
}
//
// Since this is an attachment to a file system control device object
// we are not going to log anything, but properly initialize our
// extension.
//
RtlInitEmptyUnicodeString( &devExt->DeviceName,
devExt->DeviceNameBuffer,
sizeof(devExt->DeviceNameBuffer) );
RtlCopyUnicodeString( &devExt->DeviceName, DeviceName ); //Save Name
RtlInitEmptyUnicodeString( &devExt->UserNames,
devExt->UserNamesBuffer,
sizeof(devExt->UserNamesBuffer) );
SpyInitDeviceNamingEnvironment( filespyDeviceObject );
//
// The NETWORK device objects function as both CDOs (control device object)
// and VDOs (volume device object) so insert the NETWORK CDO devices into
// the list of attached device so we will properly enumerate it.
//
if (FILE_DEVICE_NETWORK_FILE_SYSTEM == DeviceObject->DeviceType) {
ExAcquireFastMutex( &gSpyDeviceExtensionListLock );
InsertTailList( &gSpyDeviceExtensionList, &devExt->NextFileSpyDeviceLink );
ExReleaseFastMutex( &gSpyDeviceExtensionListLock );
SetFlag(devExt->Flags,ExtensionIsLinked);
}
//
// Flag we are no longer initializing this device object
//
ClearFlag( filespyDeviceObject->Flags, DO_DEVICE_INITIALIZING );
//
// Display who we have attached to
//
SPY_LOG_PRINT( SPYDEBUG_DISPLAY_ATTACHMENT_NAMES,
("FileSpy!SpyAttachToFileSystemDevice: Attaching to file system %p \"%wZ\" (%s)\n",
DeviceObject,
&devExt->DeviceName,
GET_DEVICE_TYPE_NAME(filespyDeviceObject->DeviceType)) );
//
// VERSION NOTE:
//
// In Windows XP, the IO Manager provided APIs to safely enumerate all the
// device objects for a given driver. This allows filters to attach to
// all mounted volumes for a given file system at some time after the
// volume has been mounted. There is no support for this functionality
// in Windows 2000.
//
// MULTIVERSION NOTE:
//
// If built for Windows XP or later, this driver is built to run on
// multiple versions. When this is the case, we will test
// for the presence of the new IO Manager routines that allow for volume
// enumeration. If they are not present, we will not enumerate the volumes
// when we attach to a new file system.
//
#if WINVER >= 0x0501
if (IS_WINDOWSXP_OR_LATER()) {
ASSERT( NULL != gSpyDynamicFunctions.EnumerateDeviceObjectList &&
NULL != gSpyDynamicFunctions.GetDiskDeviceObject &&
NULL != gSpyDynamicFunctions.GetDeviceAttachmentBaseRef &&
NULL != gSpyDynamicFunctions.GetLowerDeviceObject );
//
// Enumerate all the mounted devices that currently
// exist for this file system and attach to them.
//
status = SpyEnumerateFileSystemVolumes( DeviceObject, &tempName );
if (!NT_SUCCESS( status )) {
SPY_LOG_PRINT( SPYDEBUG_ERROR,
("FileSpy!SpyAttachToFileSystemDevice: Error attaching to existing volumes for \"%wZ\", status=%08x\n",
DeviceName,
status) );
IoDetachDevice( devExt->AttachedToDeviceObject );
goto ErrorCleanupDevice;
}
}
#endif
return STATUS_SUCCESS;
/////////////////////////////////////////////////////////////////////
// Cleanup error handling
/////////////////////////////////////////////////////////////////////
ErrorCleanupDevice:
SpyCleanupMountedDevice( filespyDeviceObject );
IoDeleteDevice( filespyDeviceObject );
return status;
}
VOID
SpyDetachFromFileSystemDevice (
IN PDEVICE_OBJECT DeviceObject
)
/*++
Routine Description:
Given a base file system device object, this will scan up the attachment
chain looking for our attached device object. If found it will detach
us from the chain.
Arguments:
DeviceObject - The file system device to detach from.
Return Value:
--*/
{
PDEVICE_OBJECT ourAttachedDevice;
PFILESPY_DEVICE_EXTENSION devExt;
PAGED_CODE();
//
// We have to iterate through the device objects in the filter stack
// attached to DeviceObject. If we are attached to this filesystem device
// object, We should be at the top of the stack, but there is no guarantee.
// If we are in the stack and not at the top, we can safely call IoDetachDevice
// at this time because the IO Manager will only really detach our device
// object from the stack at a safe time.
//
//
// Skip the base file system device object (since it can't be us)
//
ourAttachedDevice = DeviceObject->AttachedDevice;
while (NULL != ourAttachedDevice) {
if (IS_FILESPY_DEVICE_OBJECT( ourAttachedDevice )) {
devExt = ourAttachedDevice->DeviceExtension;
//
// Display who we detached from
//
SPY_LOG_PRINT( SPYDEBUG_DISPLAY_ATTACHMENT_NAMES,
("FileSpy!SpyDetachFromFileSystem: Detaching from file system %p \"%wZ\" (%s)\n",
devExt->AttachedToDeviceObject,
&devExt->DeviceName,
GET_DEVICE_TYPE_NAME(ourAttachedDevice->DeviceType)) );
//
// Unlink from global list
//
if (FlagOn(devExt->Flags,ExtensionIsLinked)) {
ExAcquireFastMutex( &gSpyDeviceExtensionListLock );
RemoveEntryList( &devExt->NextFileSpyDeviceLink );
ExReleaseFastMutex( &gSpyDeviceExtensionListLock );
ClearFlag(devExt->Flags,ExtensionIsLinked);
}
//
// Detach us from the object just below us
// Cleanup and delete the object
//
SpyCleanupMountedDevice( ourAttachedDevice );
IoDetachDevice( DeviceObject );
IoDeleteDevice( ourAttachedDevice );
return;
}
//
// Look at the next device up in the attachment chain
//
DeviceObject = ourAttachedDevice;
ourAttachedDevice = ourAttachedDevice->AttachedDevice;
}
}
#if WINVER >= 0x0501
NTSTATUS
SpyEnumerateFileSystemVolumes (
IN PDEVICE_OBJECT FSDeviceObject,
IN PUNICODE_STRING Name
)
/*++
Routine Description:
Enumerate all the mounted devices that currently exist for the given file
system and attach to them. We do this because this filter could be loaded
at any time and there might already be mounted volumes for this file system.
Arguments:
FSDeviceObject - The device object for the file system we want to enumerate
Name - An already initialized unicode string used to retrieve names
Return Value:
The status of the operation
--*/
{
PDEVICE_OBJECT newDeviceObject;
PFILESPY_DEVICE_EXTENSION newDevExt;
PDEVICE_OBJECT *devList;
PDEVICE_OBJECT diskDeviceObject;
NTSTATUS status;
ULONG numDevices;
ULONG i;
PAGED_CODE();
//
// Find out how big of an array we need to allocate for the
// mounted device list.
//
ASSERT( NULL != gSpyDynamicFunctions.EnumerateDeviceObjectList );
status = (gSpyDynamicFunctions.EnumerateDeviceObjectList)( FSDeviceObject->DriverObject,
NULL,
0,
&numDevices);
//
// We only need to get this list of there are devices. If we
// don't get an error there are no devices so go on.
//
if (!NT_SUCCESS( status )) {
ASSERT(STATUS_BUFFER_TOO_SMALL == status);
//
// Allocate memory for the list of known devices
//
numDevices += 8; //grab a few extra slots
devList = ExAllocatePoolWithTag( NonPagedPool,
(numDevices * sizeof(PDEVICE_OBJECT)),
FILESPY_POOL_TAG );
if (NULL == devList) {
return STATUS_INSUFFICIENT_RESOURCES;
}
//
// Now get the list of devices. If we get an error again
// something is wrong, so just fail.
//
status = (gSpyDynamicFunctions.EnumerateDeviceObjectList)(
FSDeviceObject->DriverObject,
devList,
(numDevices * sizeof(PDEVICE_OBJECT)),
&numDevices);
if (!NT_SUCCESS( status )) {
ExFreePoolWithTag( devList, FILESPY_POOL_TAG );
return status;
}
//
// Walk the given list of devices and attach to them if we should.
//
for (i=0; i < numDevices; i++) {
//
// Do not attach if:
// - This is the control device object (the one passed in)
// - The device type does not match
// - We are already attached to it
//
if ((devList[i] != FSDeviceObject) &&
(devList[i]->DeviceType == FSDeviceObject->DeviceType) &&
!SpyIsAttachedToDevice( devList[i], NULL )) {
//
// See if this device has a name. If so, then it must
// be a control device so don't attach to it. This handles
// drivers with more then one control device.
//
SpyGetBaseDeviceObjectName( devList[i], Name );
if (Name->Length <= 0) {
//
// Get the disk device object associated with this
// file system device object. Only try to attach if we
// have a disk device object.
//
ASSERT( NULL != gSpyDynamicFunctions.GetDiskDeviceObject );
status = (gSpyDynamicFunctions.GetDiskDeviceObject)( devList[i], &diskDeviceObject );
if (NT_SUCCESS( status )) {
//
// Allocate a new device object to attach with
//
status = IoCreateDevice( gFileSpyDriverObject,
sizeof( FILESPY_DEVICE_EXTENSION ),
(PUNICODE_STRING) NULL,
devList[i]->DeviceType,
0,
FALSE,
&newDeviceObject );
if (NT_SUCCESS( status )) {
//
// Set disk device object
//
newDevExt = newDeviceObject->DeviceExtension;
newDevExt->Flags = 0;
newDevExt->DiskDeviceObject = diskDeviceObject;
//
// Set Device Name
//
RtlInitEmptyUnicodeString( &newDevExt->DeviceName,
newDevExt->DeviceNameBuffer,
sizeof(newDevExt->DeviceNameBuffer) );
SpyGetObjectName( diskDeviceObject,
&newDevExt->DeviceName );
//
// We have done a lot of work since the last time
// we tested to see if we were already attached
// to this device object. Test again, this time
// with a lock, and attach if we are not attached.
// The lock is used to atomically test if we are
// attached, and then do the attach.
//
ExAcquireFastMutex( &gSpyAttachLock );
if (!SpyIsAttachedToDevice( devList[i], NULL )) {
//
// Attach to this device object
//
status = SpyAttachToMountedDevice( devList[i],
newDeviceObject );
//
// Handle normal vs error cases, but keep going
//
if (NT_SUCCESS( status )) {
//
// Finished all initialization of the new
// device object, so clear the initializing
// flag now. This allows other filters to
// now attach to our device object.
//
ClearFlag( newDeviceObject->Flags, DO_DEVICE_INITIALIZING );
} else {
//
// The attachment failed, cleanup. Note that
// we continue processing so we will cleanup
// the reference counts and try to attach to
// the rest of the volumes.
//
// One of the reasons this could have failed
// is because this volume is just being
// mounted as we are attaching and the
// DO_DEVICE_INITIALIZING flag has not yet
// been cleared. A filter could handle
// this situation by pausing for a short
// period of time and retrying the attachment.
//
SpyCleanupMountedDevice( newDeviceObject );
IoDeleteDevice( newDeviceObject );
}
} else {
//
// We were already attached, cleanup this
// device object.
//
SpyCleanupMountedDevice( newDeviceObject );
IoDeleteDevice( newDeviceObject );
}
//
// Release the lock
//
ExReleaseFastMutex( &gSpyAttachLock );
} else {
SPY_LOG_PRINT( SPYDEBUG_ERROR,
("FileSpy!SpyEnumberateFileSystemVolumes: Error creating volume device object, status=%08x\n",
status) );
}
//
// Remove reference added by IoGetDiskDeviceObject.
// We only need to hold this reference until we are
// successfully attached to the current volume. Once
// we are successfully attached to devList[i], the
// IO Manager will make sure that the underlying
// diskDeviceObject will not go away until the file
// system stack is torn down.
//
ObDereferenceObject( diskDeviceObject );
}
}
}
//
// Dereference the object (reference added by
// IoEnumerateDeviceObjectList)
//
ObDereferenceObject( devList[i] );
}
//
// We are going to ignore any errors received while loading. We
// simply won't be attached to those volumes if we get an error
//
status = STATUS_SUCCESS;
//
// Free the memory we allocated for the list
//
ExFreePoolWithTag( devList, FILESPY_POOL_TAG );
}
return status;
}
#endif
////////////////////////////////////////////////////////////////////////
// //
// Private Filespy IOCTLs helper routines //
// //
////////////////////////////////////////////////////////////////////////
NTSTATUS
SpyGetAttachList (
IN PVOID Buffer,
IN ULONG BufferSize,
OUT PULONG_PTR ReturnLength
)
/*++
Routine Description:
This returns an array of structure identifying all of the devices
we are currently physical attached to and whether logging is on or
off for the given device
Arguments:
buffer - buffer to receive the attachment list
bufferSize - total size in bytes of the return buffer
returnLength - receives number of bytes we actually return
Return Value:
NT Status code
--*/
{
PLIST_ENTRY link;
PFILESPY_DEVICE_EXTENSION devExt;
PATTACHED_DEVICE pAttDev;
ULONG retlen = 0;
UNICODE_STRING attachedDevName;
PAGED_CODE();
pAttDev = Buffer;
try {
ExAcquireFastMutex( &gSpyDeviceExtensionListLock );
for (link = gSpyDeviceExtensionList.Flink;
link != &gSpyDeviceExtensionList;
link = link->Flink) {
devExt = CONTAINING_RECORD(link, FILESPY_DEVICE_EXTENSION, NextFileSpyDeviceLink);
if (BufferSize < sizeof(ATTACHED_DEVICE)) {
break;
}
pAttDev->LoggingOn = BooleanFlagOn(devExt->Flags,LogThisDevice);
//
// We set up a unicode string to represent the buffer where we
// want to copy the device name. We will reserve space for the
// terminating NULL that the caller is expecting.
//
// NOTE: Since DeviceNames is an imbedded array in the
// ATTACHED_DEVICE structure, sizeof( pAttDev->DeviceNames )
// returns the correct size. RtlCopyUnicodeString ensure that the
// copy does not extend past the MaximumLength of our destination
// string.
//
attachedDevName.MaximumLength = sizeof( pAttDev->DeviceNames ) -
sizeof( UNICODE_NULL );
attachedDevName.Length = 0;
attachedDevName.Buffer = pAttDev->DeviceNames;
RtlCopyUnicodeString( &attachedDevName,
&devExt->DeviceName );
attachedDevName.Buffer[attachedDevName.Length/sizeof(WCHAR)] = UNICODE_NULL;
retlen += sizeof( ATTACHED_DEVICE );
BufferSize -= sizeof( ATTACHED_DEVICE );
pAttDev++;
}
} finally {
ExReleaseFastMutex( &gSpyDeviceExtensionListLock );
}
*ReturnLength = retlen;
return STATUS_SUCCESS;
}
VOID
SpyGetLog (
OUT PVOID OutputBuffer,
IN ULONG OutputBufferLength,
OUT PIO_STATUS_BLOCK IoStatus
)
/*++
Routine Description:
This function fills OutputBuffer with as many LOG_RECORDs as possible.
The LOG_RECORDs are variable sizes and are tightly packed in the
OutputBuffer.
Arguments:
OutputBuffer - the user's buffer to fill with the log data we have
collected
OutputBufferLength - the size in bytes of OutputBuffer
IoStatus - is set to the correct return status information for this
operation
Return Value:
None
--*/
{
PLIST_ENTRY pList = NULL;
ULONG length = OutputBufferLength;
PCHAR pOutBuffer = OutputBuffer;
PLOG_RECORD pLogRecord = NULL;
ULONG recordsAvailable = 0, logRecordLength;
PRECORD_LIST pRecordList;
KIRQL oldIrql;
IoStatus->Information = 0;
KeAcquireSpinLock(&gOutputBufferLock, &oldIrql);
while (!IsListEmpty( &gOutputBufferList ) && (length > 0)) {
pList = RemoveHeadList( &gOutputBufferList );
pRecordList = CONTAINING_RECORD( pList, RECORD_LIST, List );
pLogRecord = &pRecordList->LogRecord;
recordsAvailable++;
//
// Pack log records on PVOID boundaries to avoid alignment faults when accessing
// the packed buffer on 64-bit architectures
//
logRecordLength = ROUND_TO_SIZE( pLogRecord->Length, sizeof( PVOID ) );
if (length < logRecordLength) {
InsertHeadList( &gOutputBufferList, pList );
break;
}
KeReleaseSpinLock( &gOutputBufferLock, oldIrql );
//
// We wrap these logic in a try/finally because it is possible that our
// output buffer is invalid if we are coming through the FastIO path to
// service the FILESPY_GetLog IOCTLs. If this is the case, the
// exception will be caught in SpyFastIoDeviceControl, but we want to
// make sure that we don't lose this current record we are dealing
// with.
//
try {
//
// Copy of course the non-padded number of bytes
//
RtlCopyMemory( pOutBuffer, pLogRecord, pLogRecord->Length );
//
// Adjust the log-record length to the padded length in the copied record
//
((PLOG_RECORD) pOutBuffer)->Length = logRecordLength;
IoStatus->Information += logRecordLength;
length -= logRecordLength;
pOutBuffer += logRecordLength;
SpyFreeRecord( pRecordList );
pRecordList = NULL;
} finally {
if (pRecordList != NULL) {
//
// We did not successfully process this record, so put it back
// on the gOutputBufferList. The log records contain a sequence
// number, so the fact that this may disrupt the ordering of the
// log records is not a problem.
//
KeAcquireSpinLock( &gOutputBufferLock, &oldIrql );
InsertHeadList( &gOutputBufferList, &pRecordList->List );
KeReleaseSpinLock( &gOutputBufferLock, oldIrql );
}
}
KeAcquireSpinLock( &gOutputBufferLock, &oldIrql );
}
KeReleaseSpinLock( &gOutputBufferLock, oldIrql );
//
// no copies occurred
//
if (length == OutputBufferLength && recordsAvailable > 0) {
IoStatus->Status = STATUS_BUFFER_TOO_SMALL;
}
return;
}
VOID
SpyCloseControlDevice (
)
/*++
Routine Description:
This is the routine that is associated with IRP_MJ_
This routine does the cleanup involved in closing the ControlDevice.
On the close of the Control Device, we need to empty the queue of
logRecords that are waiting to be returned to the user.
Arguments:
None.
Return Value:
None.
--*/
{
PLIST_ENTRY pList;
PRECORD_LIST pRecordList;
KIRQL oldIrql;
//
// Set the gControlDeviceState to CLEANING_UP so that we can
// signal that we are cleaning up the device.
//
KeAcquireSpinLock( &gControlDeviceStateLock, &oldIrql );
gControlDeviceState = CLEANING_UP;
KeReleaseSpinLock( &gControlDeviceStateLock, oldIrql );
KeAcquireSpinLock( &gOutputBufferLock, &oldIrql );
while (!IsListEmpty( &gOutputBufferList )) {
pList = RemoveHeadList( &gOutputBufferList );
KeReleaseSpinLock( &gOutputBufferLock, oldIrql );
pRecordList = CONTAINING_RECORD( pList, RECORD_LIST, List );
SpyFreeRecord( pRecordList );
KeAcquireSpinLock( &gOutputBufferLock, &oldIrql );
}
KeReleaseSpinLock( &gOutputBufferLock, oldIrql );
SpyNameDeleteAllNames();
//
// All the cleanup is done, so set the gControlDeviceState
// to CLOSED.
//
KeAcquireSpinLock( &gControlDeviceStateLock, &oldIrql );
gControlDeviceState = CLOSED;
KeReleaseSpinLock( &gControlDeviceStateLock, oldIrql );
}
////////////////////////////////////////////////////////////////////////
// //
// Device name tracking helper routines //
// //
////////////////////////////////////////////////////////////////////////
VOID
SpyGetObjectName (
IN PVOID Object,
IN OUT PUNICODE_STRING Name
)
/*++
Routine Description:
This routine will return the name of the given object.
If a name can not be found an empty string will be returned.
Arguments:
Object - The object whose name we want
Name - A unicode string that is already initialized with a buffer
Return Value:
None
--*/
{
NTSTATUS status;
CHAR nibuf[512]; //buffer that receives NAME information and name
POBJECT_NAME_INFORMATION nameInfo = (POBJECT_NAME_INFORMATION)nibuf;
ULONG retLength;
PAGED_CODE();
status = ObQueryNameString( Object,
nameInfo,
sizeof(nibuf),
&retLength );
//
// Init current length, if we have an error a NULL string will be returned
//
Name->Length = 0;
if (NT_SUCCESS( status )) {
//
// Copy what we can of the name string
//
RtlCopyUnicodeString( Name, &nameInfo->Name );
}
}
//
// VERSION NOTE:
//
// This helper routine is only needed when enumerating all volumes in the
// system, which is only supported on Windows XP and later.
//
#if WINVER >= 0x0501
VOID
SpyGetBaseDeviceObjectName (
IN PDEVICE_OBJECT DeviceObject,
IN OUT PUNICODE_STRING Name
)
/*++
Routine Description:
This locates the base device object in the given attachment chain and then
returns the name of that object.
If no name can be found, an empty string is returned.
Arguments:
Object - The object whose name we want
Name - A unicode string that is already initialized with a buffer
Return Value:
None
--*/
{
PAGED_CODE();
//
// Get the base file system device object
//
ASSERT( NULL != gSpyDynamicFunctions.GetDeviceAttachmentBaseRef );
DeviceObject = (gSpyDynamicFunctions.GetDeviceAttachmentBaseRef)( DeviceObject );
//
// Get the name of that object
//
SpyGetObjectName( DeviceObject, Name );
//
// Remove the reference added by IoGetDeviceAttachmentBaseRef
//
ObDereferenceObject( DeviceObject );
}
#endif
BOOLEAN
SpyFindSubString (
IN PUNICODE_STRING String,
IN PUNICODE_STRING SubString
)
/*++
Routine Description:
This routine looks to see if SubString is a substring of String.
Arguments:
String - the string to search in
SubString - the substring to find in String
Return Value:
Returns TRUE if the substring is found in string and FALSE otherwise.
--*/
{
ULONG index;
//
// First, check to see if the strings are equal.
//
if (RtlEqualUnicodeString( String, SubString, TRUE )) {
return TRUE;
}
//
// String and SubString aren't equal, so now see if SubString
// is in String any where.
//
for (index = 0;
index + SubString->Length <= String->Length;
index++) {
if (_wcsnicmp( &String->Buffer[index], SubString->Buffer, SubString->Length ) == 0) {
//
// SubString is found in String, so return TRUE.
//
return TRUE;
}
}
return FALSE;
}
VOID
SpyStoreUserName (
IN PFILESPY_DEVICE_EXTENSION devExt,
IN PUNICODE_STRING UserName
)
/*++
Routine Description:
Stores the current device name in the device extension. If
this name is already in the device name list of this extension,
it will not be added. If there is already a name for this device,
the new device name is appended to the DeviceName in the device extension.
Arguments:
devExt - The device extension that will store the
device name.
UserName - The device name as specified by the user to be stored.
Return Value:
None
--*/
{
//
// See if this UserName is already in the list of user names filespy
// keeps in its device extension. If not, add it to the list.
//
if (!SpyFindSubString( &devExt->UserNames, UserName )) {
//
// We didn't find this name in the list, so if there are no names
// in the UserNames list, just append UserName. Otherwise, append a
// delimiter then append UserName.
//
if (devExt->UserNames.Length == 0) {
RtlAppendUnicodeStringToString( &devExt->UserNames, UserName );
} else {
RtlAppendUnicodeToString( &devExt->UserNames, L", " );
RtlAppendUnicodeStringToString( &devExt->UserNames, UserName );
}
}
//
// See if this UserName is already in the list of device names filespy
// keeps in its device extension. If not, add it to the list.
//
if (!SpyFindSubString( &devExt->DeviceName, UserName )) {
//
// We didn't find this name in the list, so if there are no names
// in the UserNames list, just append UserName. Otherwise, append a
// delimiter then append UserName.
//
if (devExt->DeviceName.Length == 0) {
RtlAppendUnicodeStringToString( &devExt->DeviceName, UserName );
} else {
RtlAppendUnicodeToString( &devExt->DeviceName, L", " );
RtlAppendUnicodeStringToString( &devExt->DeviceName, UserName );
}
}
}
////////////////////////////////////////////////////////////////////////
// //
// Debug support routines //
// //
////////////////////////////////////////////////////////////////////////
VOID
SpyDumpIrpOperation (
IN BOOLEAN InOriginatingPath,
IN PIRP Irp
)
/*++
Routine Description:
This routine is for debugging and prints out a string to the
debugger specifying what Irp operation is being seen.
Arguments:
InOriginatingPath - TRUE if we are in the originating path
for the IRP, FALSE if in the completion path.
Irp - The IRP for this operation.
Return Value:
None.
--*/
{
CHAR irpMajorString[OPERATION_NAME_BUFFER_SIZE];
CHAR irpMinorString[OPERATION_NAME_BUFFER_SIZE];
PIO_STACK_LOCATION irpSp = IoGetCurrentIrpStackLocation( Irp );
GetIrpName(irpSp->MajorFunction,
irpSp->MinorFunction,
irpSp->Parameters.FileSystemControl.FsControlCode,
irpMajorString,irpMinorString);
if (InOriginatingPath) {
DbgPrint( "FILESPY: Irp preoperation for %s %s\n", irpMajorString, irpMinorString );
} else {
DbgPrint( "FILESPY: Irp postoperation for %s %s\n", irpMajorString, irpMinorString );
}
}
VOID
SpyDumpFastIoOperation (
IN BOOLEAN InPreOperation,
IN FASTIO_TYPE FastIoOperation
)
/*++
Routine Description:
This routine is for debugging and prints out a string to the
debugger specifying what FsFilter operation is being seen.
Arguments:
InPreOperation - TRUE if we have not called down to the next
device in the stack, FALSE otherwise.
FastIoOperation - The code for the Fast Io operation.
Return Value:
None.
--*/
{
CHAR operationString[OPERATION_NAME_BUFFER_SIZE];
GetFastioName(FastIoOperation,
operationString);
if (InPreOperation) {
DbgPrint( "FILESPY: Fast IO preOperation for %s\n", operationString );
} else {
DbgPrint( "FILESPY: Fast IO postOperation for %s\n", operationString );
}
}
#if WINVER >= 0x0501 /* See comment in DriverEntry */
VOID
SpyDumpFsFilterOperation (
IN BOOLEAN InPreOperationCallback,
IN PFS_FILTER_CALLBACK_DATA Data
)
/*++
Routine Description:
This routine is for debugging and prints out a string to the
debugger specifying what FsFilter operation is being seen.
Arguments:
InPreOperationCallback - TRUE if we are in a preOperation
callback, FALSE otherwise.
Data - The FS_FILTER_CALLBACK_DATA structure for this
operation.
Return Value:
None.
--*/
{
CHAR operationString[OPERATION_NAME_BUFFER_SIZE];
GetFsFilterOperationName(Data->Operation,operationString);
if (InPreOperationCallback) {
DbgPrint( "FILESPY: FsFilter preOperation for %s\n", operationString );
} else {
DbgPrint( "FILESPY: FsFilter postOperation for %s\n", operationString );
}
}
#endif