Monday 14 February 2011

Driver and application intercommunication

One of the most fundamental principles of the ring protection model in modern CPUs is that intercommunication between processes in different rings has to be done in a very specific way as to provide a guarantee that code has a chance to check and sanitize incoming data. For example, the ring model would be near useless if a user-mode process could push items to the ring0 stack directly, or create kernel threads at arbitrary addresses. Mechanisms such as call gates and I/O Request Packets (IRPs) provide a way to interoperate between user- and kernel-mode.

It is possible, through a Windows API called DeviceIoControl, to send IRPs from user-mode to kernel-mode. Each driver has a list of handler functions that it can bind a function to, or not if it does not wish to handle that particular functionality. These functions are called 'major functions' and are assigned through the DRIVER_OBJECT->MajorFunction array. One of these major functions is designated IRP_MJ_DEVICE_CONTROL, which is called whenever an IRP is sent to the driver. When pointed at an appropriate handler function, the driver intercepts this IRP and can process it however it wants. The most important parts of the IRP structure (at least when using buffered IO) are the IoControlCode and the SystemBuffer parameters. When the user-mode application sends an IRP to the kernel-mode driver, it specifies a control code to define what it is asking the driver to do. It may also provide a buffer for input or output data. The driver performs the task specified by the control code given, utilising SystemBuffer to take parameters or send back data to the user-mode application.

In AetherAV there is a global event object defined under BaseNamedObjects that both user- and kernel-mode code has access to. When a new event occurs, the kernel-mode driver signals this event and the user-mode application sends a DeviceIoControl request back to the driver in order to find out certain information about the event, and finally invokes a managed event to tell the rest of the application about the event.

One issue we have to handle is preemption - e.g. new thread event occurs, timer signalled, application sends IRP to ask for new thread ID, driver sends value back, a second new thread event occurs and overwrites thread ID and process ID values, application sends IRP to ask for new thread's associated process ID, wrong process ID sent back. To stop this, we set up a kernel-only event object that is waited upon until the application has asked for all the required information about the event. This prevents new updates from occuring before the application has asked about the event. If new thread/process starts have occured since then, the system creates a backlog queue of IRPs to the notification handler and the driver will repeatedly signal the user-mode application about these new events until the backlog has been cleared.

No comments:

Post a Comment

Note: only a member of this blog may post a comment.