Who owns this IRP? (Process ID)

February 5th, 2007 - Fernando Roberto

There are cases that you need to know which process have launched a given IRP. This is very common in Firewalls or other security programs, which intercept  I/O operations to check in their databases whether a certain process has or not access to a particular resource or service. But how can I know which process an IRP belongs to?

Well, I can start thinking it’s very easy to do. As we know, IoManager gives us the IRPs in the process context that made the request. Thus, knowing the API PsGetCurrentProcessId() we can get the process ID that launched the IRP. See how simple it is:

***     OnDispatchProc
**      A generic name that tells absolutely nothing...
NTSTATUS OnDispatchProc(PDEVICE_OBJECT pDeviceObject,
                        PIRP           pIrp)
    //-f--> I'm thinking it is easy, don't copy.
    HANDLE hProcessID;
    //-f--> Get the current process ID.
    hProcessID = PsGetCurrentProcessId();

Can you see how simple it is to think that everything is right and even being mistaken?

In fact, IoManager delivers the IRPs in the process context of which is doing the I/O. However, what if a third filter got attached to your driver? Yes, it is possible, and when these drivers receive these IRPs, it is not guaranteed that they will transfer them to our driver at the same process context. Suppose we write a driver for a device:

  • An application asks a write operation to the device.
  • IoManager creates and sends the IRP to our driver.
  • A filter attached to our device receives the IRP.
  • The filter performs an asynchronous task, possibly using ExQueueWorkItem().
  • While this task has not being ended up, the filter will mark the IRP as pending and return STATUS_PENDING.
  • The asynchronous operation, in this case, is performed by a system thread and at the end of the task, the filter forwards the IRP to the driver below it that in case, it is our driver.
  • Our driver then receives the IRP in the system context and not in the process context that originated the IRP.

When the filter keeps the IRP pending and returns from the Dispatch function, the thread that originated the IRP goes ahead and will perform other tasks. The original thread can still return to the process that started the whole operation in case the OVERLAPPED structures were used when calling the driver.

The IRP will now be executed in the process context that made a call to IoCallDriver passing the IRP that was pending as a parameter. In our example, the process that will do this is the System process. Using the code above, we obtain the System PID in place of the PID of the process that actually initiated the IRP.

To properly obtain the information we’re looking for, we’ll have to stroll a bit bigger. When every IRP is created, it enters the pending IRP list of the thread that created it. To get this thread from the IRP pointer, we use the field pIrp->Tail.Overlay.Thread. This field has a pointer to the ETHREAD structure, which refers to the thread that created this IRP. To get the process from the thread, we can use the IoThreadToProcess() API. See the excerpt below.

***     OnDispatchProc
**      A generic name that tells absolutely nothing...      
NTSTATUS OnDispatchProc(PDEVICE_OBJECT pDeviceObject,
                        PIRP           pIrp)
    PEPROCESS   pEProcess;
    PETHREAD    pEThread;
    //-f--> Here we get the thread pointer.
    //      The one responsible for creating this IRP.
    pEThread = pIrp->Tail.Overlay.Thread;
    //-f--> Now we get the process that owns
    //      this thread.
    pEProcess = IoThreadToProcess(pEThread);

Okay, see that wonder. Now you have in your hands EPROCESS structure, that according to Microsoft documentation it is an opaque structure used internally by the operating system.

“The EPROCESS structure is an opaque data structure used internally by the operating system.”

Now what can I do with this?

Although I’m sure you’ve thought of something I could do with this structure, I have a better suggestion, and why not saying much more appropriate? Even because there might have children reading this. Despite EPROCESS mean nothing, it can still bring us some useful information. We can get the handle of the process identified by this structure and thus, get additional information from it, such as its PID. See the example below.

//-f--> ZwQueryInformationProcess from
//      Windows NT/2000 Native API Reference
//      ISBN-10: 1578701996 
//      ISBN-13: 978-1578701995
    IN  HANDLE            ProcessHandle,
    IN  PROCESSINFOCLASS  ProcessInformationClass,
    OUT PVOID             ProcessInformation,
    IN  ULONG             ProcessInformationLength,
    OUT PULONG            ReturnLength OPTIONAL
***     MyGetProcessID
**      Get the process ID from its EPROCESS structure.
**      Please, use your imagination to create a better name
**      for this routine.
MyGetProcessID(IN  PEPROCESS    pEProcess,
               OUT PHANDLE      phProcessId)
    NTSTATUS                    nts = STATUS_SUCCESS;
    HANDLE                      hProcess = NULL;
    ULONG                       ulSize;
    //-f--> Zw routines are usually called
    //      from User-Mode, so to call them
    //      from Kernel, we'll need, at least,
    //      to be at PASSIVE_LEVEL.
    ASSERT(KeGetCurrentIrql() == PASSIVE_LEVEL);
        //-f--> Initialize the output parameter.
        *phProcessId = 0;
        //-f--> Now we get the handle of the process
        //      identified by this EPROCESS pointer.
        nts = ObOpenObjectByPointer(pEProcess,
        if (!NT_SUCCESS(nts))
        //-f--> To use non-documented API, it is enough to
        //      declare its prototype as it was made in the
        //      beginning os this example
        nts = ZwQueryInformationProcess(hProcess,
        if (NT_SUCCESS(nts))
        //-f--> Everybody is alive so far; now just set the output
        //      parameter with the correct information.
        *phProcessId = (HANDLE)ProcessInfo.UniqueProcessId;
        //-f--> Ops... Something got wrong!
        nts = GetExceptionCode();
    //-f--> Release the process handle that we got.
    //      In this way, your manager will not want to kill you
    //      when, although the processes have finished,
    //      there will still be EPROCESS structures spread in RAM.
    if (hProcess)
    //-f--> And all lived happily ever after.
    //      (including your manager)
    return nts;

You can get extensive information from the process handle. There are even ways to get the full process Path from its handle, but let’s leave that game to the next post.

See you… 🙂

3 Responses to “Who owns this IRP? (Process ID)”

  1. Koushal says:

    Hey Fernando,

    Thanks for the article. Very nicely explained.
    Appreciate your time and efforts.


  2. Scott Lundgren says:

    This may not be a complete solution. See http://www.osronline.com/showThread.cfm?link=90019 for one possible example.

Leave a Reply