The NT Insider

Emerging Issues in IoCancelFileOpen
(By: The NT Insider, Vol 10, Issue 4, July-August 2003 | Published: 06-May-03| Modified: 27-Aug-03)

At the April 2003 Microsoft IFS Plugfest an issue was identified that could arise when two file system filter drivers interact in an unexpected fashion during the processing of IRP_MJ_CREATE.

This situation arises when the lower filter performs an IRP_MJ_READ or IRP_MJ_WRITE operation after the IRP_MJ_CREATE has been satisfied by the underlying file system and then the higher filter calls IoCancelFileOpen because it has decided to disallow the file open. 

What actually happens is that the lower filter's call will cause the virtual memory system (cache manager and memory manager) to increment the reference count of the file object passed in the IRP_MJ_READ.  If this is the IRP_MJ_READ from the original IRP_MJ_CREATE, the reference count on that file object is incremented.

When the higher filter calls IoCancelFileOpen the I/O Manager issues an IRP_MJ_CLEANUP and IRP_MJ_CLOSE down to the lower filter and then to the file system - as if the reference count had in fact dropped to zero.  The higher filter then returns an error to the I/O Manager (or other OS component) and that component then discards the file object, even though there is a dangling reference from the virtual memory system.  Thus, later when the virtual memory system releases its reference to the "file object" it is now acting on a potentially arbitrary location in memory.  This can trigger random failures.

In the initial analysis, Microsoft developers indicated that this appears to be a bug in IoCancelFileOpen and the way it is used within Windows. On further investigation, there does not appear to be a fix that can be made that would only affect IoCancelFileOpen to address 100% of the issues that can arise, but Microsoft developers are exploring this issue in search of  a good solution.

OSR developers have suggested that in fact this is a bug in the lower filter because it is never safe to perform a cached I/O call IRP_MJ_READ or IRP_MJ_WRITE with the file object from the IRP_MJ_CREATE.  This is because some paths within the file system create fake (stack based) file objects in order to improve performance for operations that would not invoke the virtual memory system. Note: with NTFS, a non-cached I/O request is not honored in the case of compressed files.  Other (third party) file systems may similarly ignore a request for non-cached I/O.

At the present time our suggested work-around is for the filter to use IoCreateStreamFile, IoCreateStreamFileLite, or IoCreateStreamFileEx to create its own stream file object.  This new stream file object may then be used when setting up the next I/O stack location in the IRP.  The filter should then forward the IRP to the next driver synchoronously (this is normally done by blocking and waiting on a notification event that is set in the completion routine).  The stream file object may then be used for IRP_MJ_READ and IRP_MJ_WRITE operations safely.  When the filter is done using the stream file object, it may be dereferenced (potentially causing an IRP_MJ_CLEANUP and IRP_MJ_CLOSE) and Windows will delete it when appropriate.  If the original create is to be allowed to proceed, the IRP can then be sent (with the original file object) to the underlying driver for further processing.  Initial reports from developers are promising regarding this work around.

Regardless, we know this is an emerging issue.  Stay tuned for further information.

Additional Information (May 9, 2003)

Microsoft has indicated that the following calls may use stack-based file objects:

NtDeleteFile
NtQueryAttributesFile
NtQueryFullAttributesFile
IoFastQueryNetworkAttributes

And that these fake file objects may appear for the following operations:

IRP_MJ_CREATE
IRP_MJ_CLEANUP
IRP_MJ_CLOSE
IRP_MJ_QUERY_INFORMATION
FastIoQueryOpen
FastIoQueryBasicInfo
FastIoQueryStandardInfo
FastIoQueryNetworkOpenInfo

Further, the information from the Microsoft team indicates that this situation is unlikely to change in the forseeable future.

The previously described solution remains OSR's suggested solution.

Additional Information (July 10, 2003)

The Microsoft filter driver team has provided an alternative solution as well:

To work around this issue, the filter doing cached I/O to the file during IRP_MJ_CREATE processing should do the following:

  • Check to see if the file object address is within the current stack limits (IoGetStackLimits)
  • If the file object address is within the current stack limits, the filter should allocate its own file object (IoCreateStreamFileObject, or IoCreateStreamFileObjectLite), pass this stream file object down to the file system to be opened, then do the cached I/O against this file object.  This is a well-formed file object which will be cleaned up safely if this is the file object referenced by either the cache manager or the memory manager.

Here is the pseudo code for a filter that cancels create operations.  In your IRP_MJ_CREATE dispatch routine, before the operation has been passed down to the file system, if this is a create that you may wish to cancel, do the following:

//

// Pseudo code assumptions:

//

// Variables:

// NameToOpen ? A WCHAR buffer that contains the name the filter

// wants to open; most likely the same name specified by the

// user in the file object passed into the CREATE operation.

// The filter?s private file object must have its own copy of

// the file name buffer. In the code below, the NameToOpen

// is a full-path from the volume root.

// NameToOpenLength ? The length in bytes of NameToOpen

// IrpSp ? Pointer to the current IRP stack location

// UsersFileObject ? Local to store the file object passed in via

// the CREATE IRP.

//

//

// This API will create a FileObject and then issue the IRP_MJ_CLEANUP

// on this handle. This API is only available on Windows 2000 and later.

// On earlier OS versions, you can use IoCreateStreamFileObject and issue

// your own IRP_MJ_CLEANUP irp.

//

SwapFileObject = IoCreateStreamFileObjectLite( IrpSp->FileObject, NULL );

if (SwapFileObject != NULL) {

//

// We now need to initialize the file object just created. In most

// cases we can just to a full copy of the user?s file object and

// change the name fields.

//

RtlCopyMemory( SwapFileObject,

IrpSp->FileObject,

sizeof( FILE_OBJECT ) );

//

// This copy cleared the FO_STREAM_FILE flag that was set. It also

// cleared the FO_CLEANUP_COMPLETE flag that was set by the

// IoCreateStreamFileObjectLite processing (remember this API will

// send down the IRP_MJ_CLEANUP to the file system for this file object),

// but we want to leave that cleared so that FAT will allow us to

// open this file object.

//

SetFlag( SwapFileObject, FO_STREAM_FILE_OBJECT );

//

// Now setup the new name buffer in the FileObject.

//

SwapFileObject->FileName.Length =

SwapFileObject->FileName.MaximumLength =

NameToOpenLength;

SwapFileObject->FileName.Buffer = NameToOpen;

SwapFileObject->RelatedFileObject = NULL;

//

// Now replace the user?s file object with our new file object

//

UsersFileObject = IrpSp->FileObject;

IrpSp->FileObject = SwapFileObject;

//

// Now is also the time to save off any of the original IRP

// parameters and set your desired IRP parameters.

//

//

// < Insert code here to issue create and synchronize back >

// < to dispatch to process completion. >

//

//

// Post-create processing

//

//

// Remember up above when we didn?t restore this flag to the stream

// file object we allocated? It is now safe to reset it.

//

SetFlag( SwapFileObject->Flags, FO_CLEANUP_COMPLETE );

if (Status = STATUS_SUCCESS) {

//

// < Do whatever processing you need to on this opened file object >

// < before canceling the create. >

//

if (SwapFileObject->PrivateCacheMap != NULL) {

//

// Caching was setup on this file object. Since the

// CLEANUP has already been issued on this FileObject,

// the file system will not uninitialize the cache map

// for us. Do this now.

//

CcUninitializeCacheMap( SwapFileObject );

}

//

// < Reinitialize the IRP. >

//

//

// Now restore the user?s file object into the IRP so that the original

// create can be sent down.

//

IrpSp->FileObject = UsersFileObject;

//

// Finally, reissue the CREATE irp.

//

Status = IoCallDriver( TargetDeviceObject, Irp );

//

// < Insert the desired logic here to do the filter?s desired post- >

// < IoCallDriver processing of a CREATE. >

//

}

//

// We are now finished with our stream file object, so dereference it. The

// IRP_MJ_CLOSE will be issued to the file system when the last

// reference is released.

//

ObDereferenceObject( SwapFileObject );

This article was printed from OSR Online http://www.osronline.com

Copyright 2017 OSR Open Systems Resources, Inc.