Internal IO Control Operations

Internal I/O Control Operations

The system uses IRP_MJ_DEVICE_CONTROL to implement a DeviceIoControl call from user mode. Drivers sometimes need to talk to each other too, and they use the related IRP_MJ_INTERNAL_DEVICE_CONTROL to do so. A typical code sequence is as follows:

ASSERT(KeGetCurrentIrql() == PASSIVE_LEVEL); KEVENT event; KeInitializeEvent(&event, NotificationEvent, FALSE); IO_STATUS_BLOCK iostatus; PIRP Irp = IoBuildDeviceIoControlRequest(IoControlCode, DeviceObject, pInBuffer, cbInBuffer, pOutBuffer, cbOutBuffer, TRUE, &event, &iostatus); NTSTATUS status = IoCallDriver(DeviceObject, Irp); if (NT_SUCCESS(status)) KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL);

Being at PASSIVE_LEVEL is a requirement for calling IoBuildDeviceIoControlRequest as well as for blocking on the event object as shown here.

The IoControlCode argument to IoBuildDeviceIoControlRequest is a control code expressing the operation you want the target device driver to perform. This code is the same kind of code you use with regular control operations. DeviceObject is a pointer to the DEVICE_OBJECT whose driver will perform the indicated operation. The input and output buffer parameters serve the same purpose as their counterparts in a user-mode DeviceIoControl call. The seventh argument, which I specified as TRUE in this fragment, indicates that you re building an internal control operation. (You could say FALSE here to create an IRP_MJ_DEVICE_CONTROL instead.) I ll describe the purpose of the event and iostatus arguments in a bit.

IoBuildDeviceIoControlRequest builds an IRP and initializes the first stack location to describe the operation code and buffers you specify. It returns the IRP pointer to you so that you can do any additional initialization that might be required. In Chapter 12, for example, I ll show you how to use an internal control request to submit a USB request block (URB) to the USB driver. Part of that process involves setting a stack parameter field to point to the URB. You then call IoCallDriver to send the IRP to the target device. If the return value passes the NT_SUCCESS test, you wait on the event object you specified as the eighth argument to IoBuildDeviceIoControlRequest. The I/O Manager will set the event when the IRP finishes, and it will also fill in your iostatus structure with the ending status and information values. Finally it will call IoFreeIrp to release the IRP. Consequently, you don t want to access the IRP pointer at all after you call IoCallDriver.

CAUTION

When you use automatic variables for the event and status-block arguments to IoBuildDeviceIoControlRequest or IoBuildSynchronousFsdRequest, you must wait for the event to be signaled if IoCall Driver returns STATUS_PENDING. Otherwise, you risk allowing the event and status block to pass out of scope before the I/O Manager finishes completing the IRP. You can wait if IoCallDriver returns a success code, but the wait ought to complete immediately because, in that case, the IRP has already been totally completed. Don t wait, however, if IoCallDriver returns an error code because, in an error case, the I/O Manager doesn t set the event and doesn t touch the status block when it completes the IRP.

Since internal control operations require cooperation between two drivers, fewer rules about sending them exist than you d guess from what I ve just described. You don t have to use IoBuildDeviceIoControlRequest to create one of them, for example: you can just call IoAllocateIrp and perform your own initialization. Provided the target driver isn t expecting to handle internal control operations solely at PASSIVE_LEVEL, you can also send one of these IRPs at DISPATCH_LEVEL, say, from inside an I/O completion or a deferred procedure call (DPC) routine. (Of course, you can t use IoBuildDeviceIoControlRequest in such a case, and you can t wait for the IRP to finish. But you can send the IRP because IoAllocateIrp and IoCallDriver can run at DISPATCH_LEVEL or below.) You don t even have to use the I/O stack parameter fields exactly as you would for a regular IOCTL. In fact, calls to the USB driver use the field that would ordinarily be the output buffer length to hold the URB pointer. So if you re designing an internal control protocol for two of your own drivers, just think of IRP_MJ_INTERNAL_DEVICE_CONTROL as being an envelope for whatever kind of message you want to send.

It s not a good idea to use the same dispatch routine for internal and external control operations, by the way, at least not without checking the major function code of the IRP. Here s an example of why not: Suppose your driver has an external control interface that allows an application to query the version number of your driver and an internal control interface that allows a trusted kernel-mode caller to determine a vital secret that you don t want to share with user-mode programs. Then suppose you use one routine to handle both interfaces, as in this example:

NTSTATUS DriverEntry(...) { DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DispatchControl; DriverObject->MajorFunction[IRP_MJ_INTERNAL_DEVICE_CONTROL] = DispatchControl;  } NTSTATUS DispatchControl(...) {  switch (code) { case IOCTL_GET_VERSION:  case IOCTL_INTERNAL_GET_SECRET:  // <== exposed for user-mode calls } }

If an application is able to somehow determine the numeric value of IOCTL_INTERNAL_GET_SECRET, it can issue a regular DeviceIoControl call and bypass the intended security on that function.



Programming the Microsoft Windows Driver Model
Programming the Microsoft Windows Driver Model
ISBN: 0735618038
EAN: 2147483647
Year: 2003
Pages: 119
Authors: Walter Oney

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net