My device is gone. Why am I still getting IRPs?

Updated: September 4, 2005

A WDM driver might receive additional IRPs after it has processed a device-removal IRP and freed driver-allocated memory. Attempting to reference the freed memory while processing the additional IRPs can crash the system.

A driver can receive IRPs for a removed device for either of two reasons:

Another component might send I/O after the device has been removed. To send an IRP, a component gets a pointer to the target device (or file) object and takes out a reference on that device object (or the I/O manager takes out a reference on the component's behalf). The reference ensures that the target device or file object persists so that the target driver can access the device object and device extension. However, unless the component has registered for Plug and Play notifications on the target device, it cannot determine whether the device is still present.

An I/O request sent before the device-removal request might arrive after the target driver has processed the device-removal request. Whether this happens depends on which component sends the I/O, the target driver's position in the device stack, and the other I/O requests that are pending for the device.

The remove lock is a key part of safe IRP handling for the target driver. A driver typically allocates the remove lock in the device extension and initializes it in the AddDevice routine. Thereafter, the driver acquires the remove lock by calling IoAcquireRemoveLock from its dispatch routine each time it starts an I/O operation and releases it by calling IoReleaseRemoveLock each time it has completed an I/O operation. The remove lock maintains a reference count and can be recursively acquired.

When the driver should call IoReleaseRemoveLock depends on how it handles the IRP: by passing it to the next-lower driver (without setting a completion routine), by completing the IRP without passing it to the next-lower driver, or by passing down the IRP and setting a completion routine.

If the driver passes the IRP to the next-lower driver and does not set an IoCompletion routine, the driver should call IoReleaseRemoveLock after calling IoCallDriver to pass down the IRP. For example:

      NTSTATUS
      MyDispatchRoutine (
        IN PDEVICE_OBJECT DeviceObject,
        IN PIRP Irp
      )
      {
      PDEVICE_EXTENSION   devExt;
      NTSTATUS    status;
    
      devExt = (PDEVICE_EXTENSION) DeviceObject->DeviceExtension;
      
      status = IoAcquireRemoveLock (&devExt->RemoveLock, Irp);
      if (!NT_SUCCESS (status)) { // maybe device is being removed.
        Irp->IoStatus.Information = 0;
        Irp->IoStatus.Status = status;
        IoCompleteRequest (Irp, IO_NO_INCREMENT);
        return status;
        }

      // Do request-specific processing
      . . . 

      // Pass down the IRP and release the lock.
      IoSkipCurrentIrpStackLocation (Irp);
      status = IoCallDriver (devExt->NextLowerDriver, Irp);
      IoReleaseRemoveLock (&devExt->RemoveLock, Irp);       
      return status;
      }

If the driver completes the IRP and does not pass it to the next-lower driver, the driver should call IoReleaseRemoveLock after calling IoCompleteRequest, as in the following example:

      NTSTATUS
      MyDispatchRoutine (
        IN PDEVICE_OBJECT DeviceObject,
        IN PIRP Irp
      )
      {
      PDEVICE_EXTENSION   devExt;
      NTSTATUS    status;
    
      devExt = 
         (PDEVICE_EXTENSION) DeviceObject->DeviceExtension;
      
      status = IoAcquireRemoveLock (&devExt->RemoveLock, Irp);
      if (!NT_SUCCESS (status)) { // maybe device is being removed.
        Irp->IoStatus.Information = 0;
        Irp->IoStatus.Status = status;
        IoCompleteRequest (Irp, IO_NO_INCREMENT);
        return status;
        }

      // Do request-specific processing
      . . . 

      // Request-specific processing is done. Complete the IRP
      // and release the lock.
      Irp->IoStatus.Status = status;
      IoCompleteRequest (Irp, IO_NO_INCREMENT );
      IoReleaseRemoveLock (&devExt->RemoveLock, Irp);       
      return status;
      }

If the driver passes the IRP to the next-lower driver and sets an IoCompletion routine, the driver should call IoReleaseRemoveLock from the IoCompletion routine, as the following shows:

      NTSTATUS
      MyCompletionRoutine (
        IN PDEVICE_OBJECT DeviceObject,
        IN PIRP Irp,
        IN PVOID Context
        )
      {
        PDEVICE_EXTENSION   data;
        UNREFERENCED_PARAMETER (DeviceObject);

        data = (PDEVICE_EXTENSION) Context;

        IoReleaseRemoveLock (&data->RemoveLock, Irp);
        return STATUS_SUCCESS;
      }

When handling a device-removal request (IRP_MN_REMOVE_DEVICE), a driver releases the remove lock that it acquired in its DispatchPnP routine by calling IoReleaseRemoveLockAndWait. This call does not return until the reference count associated with the remove lock reaches zero, which indicates that all other acquisitions of the remove lock have been released. After IoReleaseRemoveLockAndWait returns, the driver passes the IRP down its device stack (if necessary), calls IoDetachDevice to remove its device object from the device stack, and then frees resources such as pool memory that it allocated in its AddDevice routine. Finally, the driver calls IoDeleteDevice to mark the device object for deletion. For example:

    // Wait for all outstanding requests to complete
    IoReleaseRemoveLockAndWait(&devExt->RemoveLock, Irp);

    // Pass the remove IRP to the next lower driver
    Irp->IoStatus.Status = STATUS_SUCCESS;
    IoSkipCurrentIrpStackLocation(Irp);
    status = IoCallDriver(devExt->NextLowerDriver, Irp);

    //Detach from the stack, free resources, and 
    //  delete the device object.
    IoDetachDevice(devExt->NextLowerDriver); 
    RtlFreeUnicodeString (&devExt ->SymbolicLinkName);
    ExFreePool (devExt ->InputData);
    IoDeleteDevice(DeviceObject);

The I/O manager does not actually delete the device object until all references to it have been released. Therefore, it is possible for a valid device object and device extension to be present after the driver's device-removal processing has completed. However, the driver has already freed its resources, thus invalidating any pointers to those resources that were stored in the device extension.

A problem can occur if the driver receives another I/O request after its remove-device processing has completed but before the I/O manager deletes the device object. When the driver handles the request, it might attempt to dereference one of the invalid pointers from the device extension, which causes the system to crash.

To prevent this problem, the driver should acquire the remove lock for all types of I/O requests, not just Plug and Play and power requests. Most drivers already acquire the remove lock in their DispatchPnP and DispatchPower routines, thus preventing removal while handling Plug and Play and power IRPs. However, because other types of IRP could arrive after device removal, drivers should also acquire the remove lock in dispatch routines for other types of I/O requests.

The simplest approach is to call IoAcquireRemoveLock in the I/O dispatch routine upon IRP delivery. IoAcquireRemoveLock returns STATUS_DELETE_PENDING to indicate that the device is being removed. If IoAcquireRemoveLock returns this status - or any status other than STATUS_SUCCESS - the driver should fail the I/O request.

What should you do?

Acquire the remove lock before dereferencing any pointers that are stored in the device extension by calling IoAcquireRemoveLock in the I/O dispatch routine upon IRP delivery.

If IoAcquireRemoveLock does not return STATUS_SUCCESS, fail the I/O request.

Call IoReleaseRemoveLock when the driver is finished handling the IRP.

Call IoReleaseRemoveLockAndWait during device-removal processing before calling IoDetachDevice and IoDeleteDevice.

For more information:
Windows DDK
Using Remove Locks



Was This Information Useful?