A forum for reverse engineering, OS internals and malware analysis 

Forum for discussion about kernel-mode development.
 #31071  by Vrtule
 Tue Nov 28, 2017 12:37 pm
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.
 #31079  by myid
 Wed Nov 29, 2017 2:48 am
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;
 #31082  by Vrtule
 Wed Nov 29, 2017 12:35 pm
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.
 #31083  by myid
 Wed Nov 29, 2017 1:20 pm
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.
 #31084  by Vrtule
 Wed Nov 29, 2017 1:36 pm
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.
 #31085  by myid
 Wed Nov 29, 2017 4:25 pm
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.
 #31086  by Vrtule
 Wed Nov 29, 2017 7:36 pm
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.
 #31087  by myid
 Thu Nov 30, 2017 2:39 am
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.
 #31088  by Vrtule
 Thu Nov 30, 2017 12:25 pm
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.).
 #31089  by myid
 Thu Nov 30, 2017 1:14 pm
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.