Greetings,
Awhile back there was a thread that described a technique for using
FileObjects created with IoCreateStreamFileObjectXxx and rolling create
irps. I didn’t pay much attention then but it seems that some unnamed
antivirus product is using this technique (or something very similar)
and it does not align well with the context tracking techniques
described in the OSR articles. Specifically, the technique described (I
included the code below) creates a FileObject with
IoCreateStreamFileObjectXxx, clears the FO_STREAM_FILE flag, rolls and
sends a create IRP, does whatever with the FileObject, rolls and sends a
cleanup, (this is the important part) sets the FO_STREAM_FILE flag and
dereferences the FileObject. The net effect in the context tracking is
that the context reference is incremented when the create irp is
processed (sans FO_STREAM_FILE) but is not decremented at close time
since the FO_STREAM_FILE flag is now set. The context reference never
goes to 0 and, in fact, is now ‘stale’.
I’ve included the code from the previous thread. The original post has
the line that cleared the FO_STREAM_FILE flag commented out but text
posted along with the code indicated that it should be included. Also,
just to be sure to give credit where credit is due, this was posted by
Ted Hess.
Any suggestions on how to distinguish a real FileObject from a stream
FileObject masquerading as a real FileObject? As near as I can tell,
unless that distinction can be made, the only way around this is to
always and only maintain a FileObject table for context tracking -
reference counting cannot be accurate. I guess another option would be
to decrement the reference count at close time regardless of the
FO_STREAM_FILE flag and re-create the context next time the FsContext is
detected. Neither method is nearly as elegant as the original described
in the OSR articles. If I’ve missed something obvious feel free to flog
as necessary.
Sorry for the long post but I wanted to include the original code.
John Moore
NTSTATUS
xxxPreCreateCheck(PDEVICE_OBJECT targetDeviceObject, PIRP Irp) {
NTSTATUS Status, RetStatus = STATUS_SUCCESS;
UNICODE_STRING fileName;
PUNICODE_STRING pFOName;
PFILE_OBJECT fileObject, cloneFileObject;
PIO_STACK_LOCATION irpSp = IoGetCurrentIrpStackLocation(Irp);
PIO_STACK_LOCATION nxtSp = IoGetNextIrpStackLocation(Irp);
ACCESS_MASK desiredAccess =
irpSp->Parameters.Create.SecurityContext->DesiredAccess;
ACCESS_MASK desiredAccessCreate = desiredAccess;
ULONG createOptions = irpSp->Parameters.Create.Options &
FILE_VALID_OPTION_FLAGS;
ULONG createDisposition = (irpSp->Parameters.Create.Options >> 24) &
0xFF;
// The subject of this excercise (save it)
fileObject = irpSp->FileObject;
// Make a copy of the callers buffer here 'cause we don’t know who’ll
free it
fileName.Length = fileObject->FileName.Length;
fileName.MaximumLength = fileObject->FileName.MaximumLength;
fileName.Buffer = ExAllocatePool(PagedPool, fileName.MaximumLength);
// Bag it if no memory available
if (!fileName.Buffer) {
return STATUS_INSUFFICIENT_RESOURCES;
}
// Copy original contents into temp buffer
RtlCopyMemory(fileName.Buffer, fileObject->FileName.Buffer,
fileName.MaximumLength);
// Allocate a new, temporary FO
#if defined(NT_40)
// Watch out for the gratuitous CLEANUP IRP!
cloneFileObject = IoCreateStreamFileObject(fileObject, NULL); #else
cloneFileObject = IoCreateStreamFileObjectLite(fileObject, NULL);
#endif
// (Re-)init newly created FO
// cloneFileObject->Flags &= ~(FO_STREAM_FILE | FO_HANDLE_CREATED |
FO_CLEANUP_COMPLETE);
cloneFileObject->Flags = fileObject->Flags;
cloneFileObject->RelatedFileObject = fileObject->RelatedFileObject;
// Give it a name
pFOName = &cloneFileObject->FileName;
*pFOName = fileName;
// Initialize embedded event structures
KeInitializeEvent(&cloneFileObject->Lock, SynchronizationEvent,
FALSE);
KeInitializeEvent(&cloneFileObject->Event, NotificationEvent, FALSE);
// Make a copy of the create irp (preserve flags)
RtlCopyMemory(nxtSp, irpSp, sizeof(IO_STACK_LOCATION));
nxtSp->Control = 0;
// Use temporary FO and underlying DO
nxtSp->FileObject = cloneFileObject;
nxtSp->DeviceObject = DeviceObject;
// Setup completion routine
IoSetCompletionRoutine (Irp, xxxSimpleEventCompletion,
&cloneFileObject->Event, TRUE, TRUE, TRUE);
//
// Ensure that the create disposition requested
// indicates all of the appropriate “rights” necessary
// for the given operation.
//
if (createDisposition == FILE_SUPERSEDE) {
desiredAccessCreate |= DELETE;
}
if ((createDisposition == FILE_OVERWRITE) ||
(createDisposition == FILE_OVERWRITE_IF)) {
desiredAccessCreate |= (FILE_WRITE_DATA | FILE_WRITE_EA |
FILE_WRITE_ATTRIBUTES);
}
// Setup our desired access
nxtSp->Parameters.Create.SecurityContext->DesiredAccess =
desiredAccessCreate & ~SYNCHRONIZE;
nxtSp->Parameters.Create.ShareAccess = FILE_SHARE_VALID_FLAGS;
// Specify open only (no create/supersede)
nxtSp->Parameters.Create.Options = (FILE_OPEN << 24) |
(createOptions & (FILE_OPEN_NO_RECALL |
FILE_OPEN_FOR_BACKUP_INTENT |
FILE_COMPLETE_IF_OPLOCKED | FILE_NON_DIRECTORY_FILE |
FILE_DIRECTORY_FILE | FILE_OPEN_REPARSE_POINT)) |
FILE_NO_INTERMEDIATE_BUFFERING;
//
// Chuck the request to the underlying driver
//
Status = IoCallDriver(DeviceObject, Irp);
if (Status == STATUS_PENDING) {
// Wait for event if pended
KeWaitForSingleObject(&cloneFileObject->Event, Executive,
KernelMode, FALSE, 0);
Status = Irp->IoStatus.Status;
}
// Now check for file open failures
if(!NT_SUCCESS(Status) || (Irp->IoStatus.Status == STATUS_REPARSE)) {
// Restore original IRP parameters
irpSp->Parameters.Create.SecurityContext->DesiredAccess =
desiredAccess;
// Free temp (or FSD) buffer if still allocated
if (pFOName->Buffer) {
ExFreePool(pFOName->Buffer);
pFOName->Buffer = NULL;
pFOName->Length = 0;
pFOName->MaximumLength = 0;
}
// Reset FO stuff
cloneFileObject->Flags |= FO_STREAM_FILE;
cloneFileObject->RelatedFileObject = NULL;
// Toss temp FO
ObDereferenceObject(cloneFileObject);
// Nothing has happened yet
Irp->PendingReturned = FALSE;
// OK to proceed if cannot open file
return STATUS_SUCCESS;
}
//
// *** OK - we now have an opened FileObject with a valid FsContext
pointer
//
// RetStatus = <<< *** Insert your desired actions here *** >>>
//
// *** Done with FileObject
//
// Free temp (or FSD) buffer if still allocated
if (pFOName->Buffer) {
ExFreePool(pFOName->Buffer);
pFOName->Buffer = NULL;
pFOName->Length = 0;
pFOName->MaximumLength = 0;
}
// Need to light “handle created” flag
cloneFileObject->Flags |= FO_HANDLE_CREATED;
// Send a CLEANUP down to the FSD
nxtSp->MajorFunction = IRP_MJ_CLEANUP;
nxtSp->MinorFunction = 0;
nxtSp->Flags = 0;
nxtSp->Control = 0;
nxtSp->DeviceObject = DeviceObject;
nxtSp->FileObject = cloneFileObject;
// Init our completion event
KeInitializeEvent(&cloneFileObject->Event, NotificationEvent, FALSE);
// Setup completion routine
IoSetCompletionRoutine (Irp, xxxSimpleEventCompletion,
&cloneFileObject->Event, TRUE, TRUE, TRUE);
//
// Call the FSD to release any share access, cache refs, etc.
//
Status = IoCallDriver(DeviceObject, Irp);
//
// Wait for the cleanup to finish
//
if (Status == STATUS_PENDING) {
KeWaitForSingleObject(&cloneFileObject->Event, Executive,
KernelMode, FALSE, 0);
}
// Reset FO stuff
cloneFileObject->Flags |= FO_STREAM_FILE;
cloneFileObject->RelatedFileObject = NULL;
// We allocated the FO, now we must free it
ObDereferenceObject(cloneFileObject);
// Restore original IRP parameters
irpSp->Parameters.Create.SecurityContext->DesiredAccess =
desiredAccess;
// Nothing has happened yet
Irp->PendingReturned = FALSE;
Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = 0;
// return STATUS_SUCCESS if it is OK to go ahead and let the caller
open the file
return RetStatus;
}