Page 2 of 2

Re: IoCallDriver return STATUS_PENDING, will it BSOD if NOT

PostPosted:Tue Nov 28, 2017 12:37 pm
by Vrtule
By calling IoCancelIrp, you are telling the driver currently owning the IRP that you wish to cancel it. It is up to the owning driver what it does with such an IRP. Eventually, it should complete it with STATUS_CANCELLED (or some other error status), so your completion routine is called (if you instructed the system to invoke it also on cancelled IRPs). So, nothing should change for you here.

The cancel routine may be registered only by the driver that currently owns the IRP. For example, such a driver stores the IRP in a queue where it waits for being serviced. The cancel operation may be implemented as removing the IRP from the queue and completing it with STATUS_CANCELLED.

To determine whether an IRP is cancelled, you may check its Cancel field.

Re: IoCallDriver return STATUS_PENDING, will it BSOD if NOT

PostPosted:Wed Nov 29, 2017 2:48 am
by myid
Vrtule wrote:By calling IoCancelIrp, you are telling the driver currently owning the IRP that you wish to cancel it. It is up to the owning driver what it does with such an IRP. Eventually, it should complete it with STATUS_CANCELLED (or some other error status), so your completion routine is called (if you instructed the system to invoke it also on cancelled IRPs). So, nothing should change for you here.

The cancel routine may be registered only by the driver that currently owns the IRP. For example, such a driver stores the IRP in a queue where it waits for being serviced. The cancel operation may be implemented as removing the IRP from the queue and completing it with STATUS_CANCELLED.

To determine whether an IRP is cancelled, you may check its Cancel field.
Maybe my description is not clear, I want to show you my pseudo-code:
Code: Select all
KEVENT event;
PIRP irp;
IO_STATUS_BLOCK iosb;
KeInitializeEvent(&event, NotificationEvent, FALSE);
//no Completion Routine
irp = BuildIrp(devObj, ..., &event, &iosb);
status = IoCallDriver(devObj, irp);
if (status == STATUS_PENDING)
{
   //wait for 3 seconds...
   LARGE_INTEGER interval = {...};
   status = KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, &interval);
   if(status==STATUS_SUCCESS)
   {
       status = iosb.Status;
   }
   else
   {
       //if this function return TRUE, is it means the IRP cancelled successfully?
       BOOLEAN b = IoCancelIrp(irp);
       status = STATUS_UNSUCCESSFUL;
   }
}
return status;

Re: IoCallDriver return STATUS_PENDING, will it BSOD if NOT

PostPosted:Wed Nov 29, 2017 12:35 pm
by Vrtule
IoCancelIrp returns TRUE if and only if the IRP has a cancel routine. The function calls the cancel routine and sets the cancel bit of the IRP.

I do not think that the cancel routine must complete the IRP (possibly with the STATUS_CANCELLED result). So, when the call to the IoCancelIrp returns, the IRP still might be in a not-completed state. However, it may be completed and freed at any time between IoCallDriver and IoCancelIrp (which means, IoCancelIrp may work with memory that is not occupied by the IRP).

So, I recommend to register the completion routine, so you will be always able to tell whether it is safe to touch the IRP.

Re: IoCallDriver return STATUS_PENDING, will it BSOD if NOT

PostPosted:Wed Nov 29, 2017 1:20 pm
by myid
Vrtule wrote:IoCancelIrp returns TRUE if and only if the IRP has a cancel routine. The function calls the cancel routine and sets the cancel bit of the IRP.

I do not think that the cancel routine must complete the IRP (possibly with the STATUS_CANCELLED result). So, when the call to the IoCancelIrp returns, the IRP still might be in a not-completed state. However, it may be completed and freed at any time between IoCallDriver and IoCancelIrp (which means, IoCancelIrp may work with memory that is not occupied by the IRP).

So, I recommend to register the completion routine, so you will be always able to tell whether it is safe to touch the IRP.
I try to set a completion routine, but if I do this, I cannot set the last parameter of KeWaitForSingleObject (must be set to NULL), or it will BSOD.

Re: IoCallDriver return STATUS_PENDING, will it BSOD if NOT

PostPosted:Wed Nov 29, 2017 1:36 pm
by Vrtule
This may happen when you call IoCancelIrp after the IRP is completed and freed. You need to syhcnronize the code of your completion routine with the call to IoCancelIrp, so you never touch (or cancel) the IRP after its completion.

Re: IoCallDriver return STATUS_PENDING, will it BSOD if NOT

PostPosted:Wed Nov 29, 2017 4:25 pm
by myid
Vrtule wrote:This may happen when you call IoCancelIrp after the IRP is completed and freed. You need to syhcnronize the code of your completion routine with the call to IoCancelIrp, so you never touch (or cancel) the IRP after its completion.
I have read the source code of IoCancelIrp, it return after the CancelRoutine has been executed.
Code: Select all
NTKERNELAPI BOOLEAN IoCancelIrp  ( IN PIRP  Irp   )  
{
    PDRIVER_CANCEL cancelRoutine;
    KIRQL irql;
    BOOLEAN returnValue;
    ASSERT( Irp->Type == IO_TYPE_IRP );
    if (IopVerifierOn) 
	{
        if (IOV_CANCEL_IRP(Irp, &returnValue)) 
		{
            return returnValue;
        }
    }
    //
    // Acquire the cancel spin lock.
    //
    IoAcquireCancelSpinLock( &irql );
    //
    // Set the cancel flag in the IRP.
    //
    Irp->Cancel = TRUE;
    //
    // Obtain the address of the cancel routine, and if one was specified,
    // invoke it.
    //
    cancelRoutine = (PDRIVER_CANCEL) (ULONG_PTR) InterlockedExchangePointer( (PVOID *) &Irp->CancelRoutine, NULL );
    if (cancelRoutine) 
	{
        if (Irp->CurrentLocation > (CCHAR) (Irp->StackCount + 1)) 
		{
            KeBugCheckEx( CANCEL_STATE_IN_COMPLETED_IRP, (ULONG_PTR) Irp, (ULONG_PTR) cancelRoutine, 0, 0 );
        }
        Irp->CancelIrql = irql;
        cancelRoutine( Irp->Tail.Overlay.CurrentStackLocation->DeviceObject, Irp );
        //
        // The cancel spinlock should have been released by the cancel routine.
        //
        return(TRUE);
    } 
	else 
	{
        //
        // There was no cancel routine, so release the cancel spinlock and
        // return indicating the Irp was not currently cancelable.
        //
        IoReleaseCancelSpinLock( irql );
        return(FALSE);
    }
}
So, I think it is safe to free the event object after IoCancelIrp return TRUE.

Re: IoCallDriver return STATUS_PENDING, will it BSOD if NOT

PostPosted:Wed Nov 29, 2017 7:36 pm
by Vrtule
So, I think it is safe to free the event object after IoCancelIrp return TRUE.
That would be true if the cancel routine had a requirement to complete the IRP. As far as I remember, the documentation does not mention this requirement.

Re: IoCallDriver return STATUS_PENDING, will it BSOD if NOT

PostPosted:Thu Nov 30, 2017 2:39 am
by myid
Vrtule wrote:
So, I think it is safe to free the event object after IoCancelIrp return TRUE.
That would be true if the cancel routine had a requirement to complete the IRP. As far as I remember, the documentation does not mention this requirement.
My IRP will send to a system driver. I have found its source code in NT4SRC, read the code of related CancelRoutine, I found that it use IoCompleteRequest.
So I think the IRP has been cancelled successfully.

Re: IoCallDriver return STATUS_PENDING, will it BSOD if NOT

PostPosted:Thu Nov 30, 2017 12:25 pm
by Vrtule
Are you sending the IRP directly to that system driver, or to the highest device in its device stack? If the latter, be aware that filter drivers may also register a completion routine and may postpone IRP completion by returning STATUS_MORE_PROCESSING_REQUIRED. So, the cancel routine returns and the IRP is not yet completed (at least, completion routine of your driver was not called yet).

Also, it is best to check whether the system driver behaves the same even these days (the old source may point you where exactly to look in IDA etc.).

Re: IoCallDriver return STATUS_PENDING, will it BSOD if NOT

PostPosted:Thu Nov 30, 2017 1:14 pm
by myid
Vrtule wrote:Are you sending the IRP directly to that system driver, or to the highest device in its device stack? If the latter, be aware that filter drivers may also register a completion routine and may postpone IRP completion by returning STATUS_MORE_PROCESSING_REQUIRED. So, the cancel routine returns and the IRP is not yet completed (at least, completion routine of your driver was not called yet).

Also, it is best to check whether the system driver behaves the same even these days (the old source may point you where exactly to look in IDA etc.).
Yes, I send the IRP to that system driver directly, bypass all filter drivers. Thanks for your detailed replies again.