Programming the Microsoft Windows Driver Model
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
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.
NTSTATUS DriverEntry(...) { DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DispatchControl; DriverObject->MajorFunction[IRP_MJ_INTERNAL_DEVICE_CONTROL] = DispatchControl;
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.