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.
756 lines
18 KiB
756 lines
18 KiB
/*++
|
|
|
|
Copyright (c) 1989-1999 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
filespy.c
|
|
|
|
Abstract:
|
|
|
|
This module contains all of the routines for tracking names by
|
|
hashing the fileObject. This cache is limited in size by the
|
|
following registry setting "MaxNames".
|
|
|
|
Environment:
|
|
|
|
Kernel mode
|
|
|
|
// @@BEGIN_DDKSPLIT
|
|
|
|
Author:
|
|
|
|
Neal Christiansen (nealch) 04-Jul-2001
|
|
|
|
Revision History:
|
|
|
|
Ravisankar Pudipeddi (ravisp) 07-May-2002
|
|
Make it work on IA64
|
|
|
|
// @@END_DDKSPLIT
|
|
--*/
|
|
|
|
#include <ntifs.h>
|
|
#include "filespy.h"
|
|
#include "fspyKern.h"
|
|
|
|
|
|
#if !USE_STREAM_CONTEXTS
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Local definitions
|
|
//
|
|
////////////////////////////////////////////////////////////////////////
|
|
|
|
#define HASH_FUNC(FileObject) \
|
|
(((UINT_PTR)(FileObject) >> 8) & (HASH_SIZE - 1))
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Global Variables
|
|
//
|
|
////////////////////////////////////////////////////////////////////////
|
|
|
|
//
|
|
// NOTE: Must use KSPIN_LOCKs to synchronize access to hash buckets since
|
|
// we may try to acquire them at DISPATCH_LEVEL.
|
|
//
|
|
|
|
LIST_ENTRY gHashTable[HASH_SIZE];
|
|
KSPIN_LOCK gHashLockTable[HASH_SIZE];
|
|
ULONG gHashMaxCounters[HASH_SIZE];
|
|
ULONG gHashCurrentCounters[HASH_SIZE];
|
|
|
|
UNICODE_STRING OutOfBuffers = CONSTANT_UNICODE_STRING(L"[-=Out Of Buffers=-]");
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Local prototypes
|
|
//
|
|
////////////////////////////////////////////////////////////////////////
|
|
|
|
VOID
|
|
SpyDeleteContextCallback(
|
|
IN PVOID Context
|
|
);
|
|
|
|
|
|
//
|
|
// linker commands
|
|
//
|
|
|
|
#ifdef ALLOC_PRAGMA
|
|
|
|
#pragma alloc_text( PAGE, SpyInitNamingEnvironment)
|
|
#pragma alloc_text( PAGE, SpyInitDeviceNamingEnvironment)
|
|
#pragma alloc_text( PAGE, SpyCleanupDeviceNamingEnvironment)
|
|
|
|
#endif // ALLOC_PRAGMA
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Main routines
|
|
//
|
|
////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
VOID
|
|
SpyInitNamingEnvironment(
|
|
VOID
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Init global variables
|
|
|
|
Arguments:
|
|
|
|
None
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
{
|
|
int i;
|
|
|
|
//
|
|
// Initialize the hash table
|
|
//
|
|
|
|
for (i = 0; i < HASH_SIZE; i++){
|
|
|
|
InitializeListHead(&gHashTable[i]);
|
|
KeInitializeSpinLock(&gHashLockTable[i]);
|
|
}
|
|
}
|
|
|
|
|
|
VOID
|
|
SpyInitDeviceNamingEnvironment (
|
|
IN PDEVICE_OBJECT DeviceObject
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Initialize the per DeviceObject naming environment
|
|
|
|
Arguments:
|
|
|
|
DeviceObject - The device object to initialize
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
{
|
|
UNREFERENCED_PARAMETER( DeviceObject );
|
|
}
|
|
|
|
|
|
VOID
|
|
SpyCleanupDeviceNamingEnvironment (
|
|
IN PDEVICE_OBJECT DeviceObject
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Initialize the per DeviceObject naming environment
|
|
|
|
Arguments:
|
|
|
|
DeviceObject - The device object to initialize
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
{
|
|
UNREFERENCED_PARAMETER( DeviceObject );
|
|
}
|
|
|
|
|
|
VOID
|
|
SpyLogIrp (
|
|
IN PIRP Irp,
|
|
OUT PRECORD_LIST RecordList
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Records the Irp necessary information according to LoggingFlags in
|
|
RecordList. For any activity on the Irp path of a device being
|
|
logged, this function should get called twice: once on the Irp's
|
|
originating path and once on the Irp's completion path.
|
|
|
|
Arguments:
|
|
|
|
Irp - The Irp that contains the information we want to record.
|
|
LoggingFlags - The flags that say what to log.
|
|
RecordList - The PRECORD_LIST in which the Irp information is stored.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
{
|
|
PIO_STACK_LOCATION pIrpStack;
|
|
PRECORD_IRP pRecordIrp;
|
|
ULONG lookupFlags;
|
|
|
|
pRecordIrp = &RecordList->LogRecord.Record.RecordIrp;
|
|
|
|
pIrpStack = IoGetCurrentIrpStackLocation(Irp);
|
|
|
|
//
|
|
// 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_IRP );
|
|
|
|
pRecordIrp->IrpMajor = pIrpStack->MajorFunction;
|
|
pRecordIrp->IrpMinor = pIrpStack->MinorFunction;
|
|
pRecordIrp->IrpFlags = Irp->Flags;
|
|
pRecordIrp->FileObject = (FILE_ID)pIrpStack->FileObject;
|
|
pRecordIrp->DeviceObject = (FILE_ID)pIrpStack->DeviceObject;
|
|
pRecordIrp->ProcessId = (FILE_ID)PsGetCurrentProcessId();
|
|
pRecordIrp->ThreadId = (FILE_ID)PsGetCurrentThreadId();
|
|
pRecordIrp->Argument1 = pIrpStack->Parameters.Others.Argument1;
|
|
pRecordIrp->Argument2 = pIrpStack->Parameters.Others.Argument2;
|
|
pRecordIrp->Argument3 = pIrpStack->Parameters.Others.Argument3;
|
|
pRecordIrp->Argument4 = pIrpStack->Parameters.Others.Argument4;
|
|
|
|
if (IRP_MJ_CREATE == pRecordIrp->IrpMajor) {
|
|
|
|
//
|
|
// Only record the desired access if this is a CREATE irp.
|
|
//
|
|
|
|
pRecordIrp->DesiredAccess = pIrpStack->Parameters.Create.SecurityContext->DesiredAccess;
|
|
}
|
|
|
|
KeQuerySystemTime(&(pRecordIrp->OriginatingTime));
|
|
|
|
lookupFlags = 0;
|
|
|
|
switch (pIrpStack->MajorFunction) {
|
|
|
|
case IRP_MJ_CREATE:
|
|
|
|
//
|
|
// This is a CREATE so we need to invalidate the name currently
|
|
// stored in the name cache for this FileObject.
|
|
//
|
|
|
|
SpyNameDelete(pIrpStack->FileObject);
|
|
|
|
//
|
|
// Flag in Create
|
|
//
|
|
|
|
SetFlag( lookupFlags, NLFL_IN_CREATE );
|
|
|
|
//
|
|
// Flag if opening the directory of the given file
|
|
//
|
|
|
|
if (FlagOn( pIrpStack->Flags, SL_OPEN_TARGET_DIRECTORY )) {
|
|
|
|
SetFlag( lookupFlags, NLFL_OPEN_TARGET_DIR );
|
|
}
|
|
|
|
//
|
|
// Flag if opening by ID
|
|
//
|
|
|
|
if (FlagOn( pIrpStack->Parameters.Create.Options, FILE_OPEN_BY_FILE_ID )) {
|
|
|
|
SetFlag( lookupFlags, NLFL_OPEN_BY_ID );
|
|
}
|
|
break;
|
|
|
|
case IRP_MJ_CLOSE:
|
|
//
|
|
// We can only look up the name in the name cache if this is a CLOSE.
|
|
// It is possible that the close could be occurring during a cleanup
|
|
// operation in the file system (i.e., before we have received the
|
|
// cleanup completion) and requesting the name would cause a deadlock
|
|
// in the file system.
|
|
//
|
|
|
|
SetFlag( lookupFlags, NLFL_ONLY_CHECK_CACHE );
|
|
break;
|
|
}
|
|
|
|
//
|
|
// If the flag IRP_PAGING_IO is set in this IRP, we cannot query the name
|
|
// because it can lead to deadlocks. Therefore, add in the flag so that
|
|
// we will only try to find the name in our cache.
|
|
//
|
|
|
|
if (FlagOn( Irp->Flags, IRP_PAGING_IO )) {
|
|
|
|
ASSERT( !FlagOn( lookupFlags, NLFL_NO_LOOKUP ) );
|
|
|
|
SetFlag( lookupFlags, NLFL_ONLY_CHECK_CACHE );
|
|
}
|
|
|
|
SpySetName( RecordList,
|
|
pIrpStack->DeviceObject,
|
|
pIrpStack->FileObject,
|
|
lookupFlags,
|
|
NULL);
|
|
}
|
|
|
|
|
|
VOID
|
|
SpyLogIrpCompletion (
|
|
IN PIRP Irp,
|
|
OUT PRECORD_LIST RecordList
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Records the Irp necessary information according to LoggingFlags in
|
|
RecordList. For any activity on the Irp path of a device being
|
|
logged, this function should get called twice: once on the Irp's
|
|
originating path and once on the Irp's completion path.
|
|
|
|
Arguments:
|
|
|
|
Irp - The Irp that contains the information we want to record.
|
|
LoggingFlags - The flags that say what to log.
|
|
RecordList - The PRECORD_LIST in which the Irp information is stored.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
{
|
|
PIO_STACK_LOCATION pIrpStack = IoGetCurrentIrpStackLocation(Irp);
|
|
PDEVICE_OBJECT deviceObject = pIrpStack->DeviceObject;
|
|
PRECORD_IRP pRecordIrp;
|
|
|
|
//
|
|
// Process the log record
|
|
//
|
|
|
|
if (SHOULD_LOG( deviceObject )) {
|
|
|
|
pRecordIrp = &RecordList->LogRecord.Record.RecordIrp;
|
|
|
|
//
|
|
// Record the information we use for a completion Irp.
|
|
//
|
|
|
|
pRecordIrp->ReturnStatus = Irp->IoStatus.Status;
|
|
pRecordIrp->ReturnInformation = Irp->IoStatus.Information;
|
|
KeQuerySystemTime(&pRecordIrp->CompletionTime);
|
|
|
|
//
|
|
// Add recordList to our gOutputBufferList so that it gets up to
|
|
// the user
|
|
//
|
|
|
|
SpyLog( RecordList );
|
|
|
|
} else {
|
|
|
|
if (RecordList) {
|
|
|
|
//
|
|
// Context is set with a RECORD_LIST, but we are no longer
|
|
// logging so free this record.
|
|
//
|
|
|
|
SpyFreeRecord( RecordList );
|
|
}
|
|
}
|
|
|
|
switch (pIrpStack->MajorFunction) {
|
|
|
|
case IRP_MJ_CREATE:
|
|
//
|
|
// If the operation failed remove the name from the cache because
|
|
// it is stale
|
|
//
|
|
|
|
if (!NT_SUCCESS(Irp->IoStatus.Status) &&
|
|
(pIrpStack->FileObject != NULL)) {
|
|
|
|
SpyNameDelete(pIrpStack->FileObject);
|
|
}
|
|
break;
|
|
|
|
case IRP_MJ_CLOSE:
|
|
|
|
//
|
|
// Always remove the name on close
|
|
//
|
|
|
|
SpyNameDelete(pIrpStack->FileObject);
|
|
break;
|
|
|
|
|
|
case IRP_MJ_SET_INFORMATION:
|
|
//
|
|
// If the operation was successful and it was a rename, always
|
|
// remove the name. They can re-get it next time.
|
|
//
|
|
|
|
if (NT_SUCCESS(Irp->IoStatus.Status) &&
|
|
(FileRenameInformation ==
|
|
pIrpStack->Parameters.SetFile.FileInformationClass)) {
|
|
|
|
SpyNameDelete(pIrpStack->FileObject);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// FileName cache routines
|
|
//
|
|
////////////////////////////////////////////////////////////////////////
|
|
|
|
PHASH_ENTRY
|
|
SpyHashBucketLookup (
|
|
IN PLIST_ENTRY ListHead,
|
|
IN PFILE_OBJECT FileObject
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine looks up the FileObject in the give hash bucket. This routine
|
|
does NOT lock the hash bucket.
|
|
|
|
Arguments:
|
|
|
|
ListHead - hash list to search
|
|
FileObject - the FileObject to look up.
|
|
|
|
Return Value:
|
|
|
|
A pointer to the hash table entry. NULL if not found
|
|
|
|
--*/
|
|
{
|
|
PHASH_ENTRY pHash;
|
|
PLIST_ENTRY pList;
|
|
|
|
pList = ListHead->Flink;
|
|
|
|
while (pList != ListHead){
|
|
|
|
pHash = CONTAINING_RECORD( pList, HASH_ENTRY, List );
|
|
|
|
if (FileObject == pHash->FileObject) {
|
|
|
|
return pHash;
|
|
}
|
|
|
|
pList = pList->Flink;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
VOID
|
|
SpySetName (
|
|
IN PRECORD_LIST RecordList,
|
|
IN PDEVICE_OBJECT DeviceObject,
|
|
IN PFILE_OBJECT FileObject,
|
|
IN ULONG LookupFlags,
|
|
IN PVOID Context OPTIONAL
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine looks up the FileObject in the hash table. If the FileObject
|
|
is found in the hash table, copy the associated file name to RecordList.
|
|
Otherwise, calls SpyGetFullPathName to try to get the name of the FileObject.
|
|
If successful, copy the file name to the RecordList and insert into hash
|
|
table.
|
|
|
|
Arguments:
|
|
|
|
RecordList - RecordList to copy name to.
|
|
FileObject - the FileObject to look up.
|
|
LookInFileObject - see routine description
|
|
DeviceExtension - contains the volume name (e.g., "c:") and
|
|
the next device object which may be needed.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
{
|
|
PFILESPY_DEVICE_EXTENSION devExt = DeviceObject->DeviceExtension;
|
|
UINT_PTR hashIndex;
|
|
KIRQL oldIrql;
|
|
PHASH_ENTRY pHash;
|
|
PHASH_ENTRY newHash;
|
|
PLIST_ENTRY listHead;
|
|
PUNICODE_STRING newName;
|
|
PCHAR buffer;
|
|
|
|
UNREFERENCED_PARAMETER( Context );
|
|
|
|
if (FileObject == NULL) {
|
|
|
|
return;
|
|
}
|
|
|
|
hashIndex = HASH_FUNC(FileObject);
|
|
|
|
INC_STATS(TotalContextSearches);
|
|
|
|
listHead = &gHashTable[hashIndex];
|
|
|
|
//
|
|
// Don't bother checking the hash if we are in create, we must always
|
|
// generate a name
|
|
//
|
|
|
|
if (!FlagOn( LookupFlags, NLFL_IN_CREATE )) {
|
|
|
|
KeAcquireSpinLock( &gHashLockTable[hashIndex], &oldIrql );
|
|
|
|
pHash = SpyHashBucketLookup(&gHashTable[hashIndex], FileObject);
|
|
|
|
if (pHash != NULL) {
|
|
|
|
//
|
|
// Copy the file name to the LogRecord, make sure that it is NULL terminated,
|
|
// and increment the length of the LogRecord.
|
|
//
|
|
|
|
SpyCopyFileNameToLogRecord( &RecordList->LogRecord, &pHash->Name );
|
|
|
|
KeReleaseSpinLock(&gHashLockTable[hashIndex], oldIrql);
|
|
|
|
INC_STATS(TotalContextFound);
|
|
|
|
return;
|
|
}
|
|
|
|
KeReleaseSpinLock(&gHashLockTable[hashIndex], oldIrql);
|
|
}
|
|
|
|
//
|
|
// If it is not in the table, try to add it. We will not be able to look
|
|
// up the name if we are at DISPATCH_LEVEL.
|
|
//
|
|
|
|
buffer = SpyAllocateBuffer(&gNamesAllocated, gMaxNamesToAllocate, NULL);
|
|
|
|
if (buffer != NULL) {
|
|
|
|
newHash = (PHASH_ENTRY) buffer;
|
|
newName = &newHash->Name;
|
|
|
|
RtlInitEmptyUnicodeString(
|
|
newName,
|
|
(PWCHAR)(buffer + sizeof(HASH_ENTRY)),
|
|
RECORD_SIZE - sizeof(HASH_ENTRY) );
|
|
|
|
if (SpyGetFullPathName( FileObject, newName, devExt, LookupFlags )) {
|
|
|
|
newHash->FileObject = FileObject;
|
|
KeAcquireSpinLock(&gHashLockTable[hashIndex], &oldIrql);
|
|
|
|
//
|
|
// Search again because it may have been stored in the
|
|
// hash table since we dropped the lock.
|
|
//
|
|
|
|
pHash = SpyHashBucketLookup(&gHashTable[hashIndex], FileObject);
|
|
|
|
if (pHash != NULL) {
|
|
|
|
//
|
|
// We found it in the hash table this time, so
|
|
// write the name we found to the LogRecord.
|
|
//
|
|
|
|
//
|
|
// Copy the file name to the LogRecord, make sure that it is NULL terminated,
|
|
// and increment the length of the LogRecord.
|
|
//
|
|
|
|
SpyCopyFileNameToLogRecord( &RecordList->LogRecord, &pHash->Name );
|
|
|
|
KeReleaseSpinLock(&gHashLockTable[hashIndex], oldIrql);
|
|
|
|
SpyFreeBuffer(buffer, &gNamesAllocated);
|
|
|
|
return;
|
|
}
|
|
|
|
//
|
|
// It wasn't found, add the new entry
|
|
//
|
|
|
|
//
|
|
// Copy the file name to the LogRecord, make sure that it is NULL terminated,
|
|
// and increment the length of the LogRecord.
|
|
//
|
|
|
|
SpyCopyFileNameToLogRecord( &RecordList->LogRecord, newName );
|
|
|
|
InsertHeadList(listHead, &newHash->List);
|
|
|
|
gHashCurrentCounters[hashIndex]++;
|
|
|
|
if (gHashCurrentCounters[hashIndex] > gHashMaxCounters[hashIndex]) {
|
|
|
|
gHashMaxCounters[hashIndex] = gHashCurrentCounters[hashIndex];
|
|
}
|
|
|
|
KeReleaseSpinLock(&gHashLockTable[hashIndex], oldIrql);
|
|
|
|
} else {
|
|
|
|
//
|
|
// We are not supposed to keep the log record entry, copy
|
|
// what ever they gave us in
|
|
//
|
|
|
|
SpyCopyFileNameToLogRecord( &RecordList->LogRecord, newName );
|
|
|
|
INC_STATS(TotalContextTemporary);
|
|
SpyFreeBuffer (buffer, &gNamesAllocated);
|
|
}
|
|
|
|
} else {
|
|
|
|
//
|
|
// Set a default string even if there is no buffer
|
|
//
|
|
|
|
SpyCopyFileNameToLogRecord( &RecordList->LogRecord, &OutOfBuffers );
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
VOID
|
|
SpyNameDeleteAllNames (
|
|
VOID
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This will free all entries from the hash table
|
|
|
|
Arguments:
|
|
|
|
None
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
|
|
--*/
|
|
{
|
|
KIRQL oldIrql;
|
|
PHASH_ENTRY pHash;
|
|
PLIST_ENTRY pList;
|
|
ULONG i;
|
|
|
|
INC_STATS(TotalContextDeleteAlls);
|
|
for (i=0;i < HASH_SIZE;i++) {
|
|
|
|
KeAcquireSpinLock(&gHashLockTable[i], &oldIrql);
|
|
|
|
while (!IsListEmpty(&gHashTable[i])) {
|
|
|
|
pList = RemoveHeadList(&gHashTable[i]);
|
|
pHash = CONTAINING_RECORD( pList, HASH_ENTRY, List );
|
|
SpyFreeBuffer( pHash, &gNamesAllocated);
|
|
}
|
|
|
|
gHashCurrentCounters[i] = 0;
|
|
|
|
KeReleaseSpinLock(&gHashLockTable[i], oldIrql);
|
|
}
|
|
}
|
|
|
|
VOID
|
|
SpyNameDelete (
|
|
IN PFILE_OBJECT FileObject
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine looks up the FileObject in the hash table. If it is found,
|
|
it deletes it and frees the memory.
|
|
|
|
Arguments:
|
|
|
|
FileObject - the FileObject to look up.
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
|
|
--*/
|
|
{
|
|
UINT_PTR hashIndex;
|
|
KIRQL oldIrql;
|
|
PHASH_ENTRY pHash;
|
|
PLIST_ENTRY pList;
|
|
PLIST_ENTRY listHead;
|
|
|
|
hashIndex = HASH_FUNC(FileObject);
|
|
|
|
KeAcquireSpinLock(&gHashLockTable[hashIndex], &oldIrql);
|
|
|
|
listHead = &gHashTable[hashIndex];
|
|
|
|
pList = listHead->Flink;
|
|
|
|
while(pList != listHead){
|
|
|
|
pHash = CONTAINING_RECORD( pList, HASH_ENTRY, List );
|
|
|
|
if (FileObject == pHash->FileObject) {
|
|
|
|
INC_STATS(TotalContextNonDeferredFrees);
|
|
gHashCurrentCounters[hashIndex]--;
|
|
RemoveEntryList(pList);
|
|
SpyFreeBuffer( pHash, &gNamesAllocated );
|
|
break;
|
|
}
|
|
|
|
pList = pList->Flink;
|
|
}
|
|
|
|
KeReleaseSpinLock(&gHashLockTable[hashIndex], oldIrql);
|
|
}
|
|
|
|
#endif
|