Aes 1 DR
Aes 1 DR
METHOD_NEITHER I/O, which applies only to IOCTLs, requires the most driver
validation. In METHOD_NEITHER transfers, the I/O manager neither allocates
buffers nor validates buffer addresses or lengths. The driver receives a user-space
address; it must validate this address by probing and must verify the length of the
buffer.
Because the buffer is allocated in user space, only driver routines that are called in
the context of the requesting process, such as a dispatch routine in a file-system or
other highest-level driver, can use the buffer addresses that are passed in the I/O
request. Such driver routines must do the following:
Probe all user-space addresses and validate the alignment of all buffers, by
using
Shared Events
Events are kernel-dispatcher objects that can be created and used in both kernel
mode and user mode. Events can be either named or unnamed. Drivers typically
use named events only to synchronize with external components, such as a user-
mode application or another driver.
at IRQL less than or equal to DISPATCH_LEVEL. When the driver no longer needs
the event, it must call ObDereferenceObject to remove its reference.
For additional details about sharing events, see “Shared Handles” later in this
paper.
Driver-Defined IOCTLs
Driver-defined IOCTLs are another way to implement communication between user
mode and kernel mode. Using this technique, the driver defines an IOCTL
specifically for the communication. The user-mode application creates a dedicated
thread that sends the DeviceIoControl request to the driver, which returns
STATUS_PENDING. To notify the user-mode application, the driver completes the
I/O request.
Depending upon the details of the particular driver and application, using a driver-
defined IOCTL can be somewhat inefficient because it requires a dedicated user-
mode thread that could potentially have an I/O request pending indefinitely.
However, this technique provides a significant advantage: the system handles all
synchronization with other system and device-related occurrences, such as Plug
and Play notifications and driver unloads, just as it does for any I/O request. This
advantage typically outweighs any potential inefficiency.
2. Publish the GUID in a header file for use by components that require
notification.
3. Call IoReportTargetDeviceChangeAsynchronous when the event occurs,
passing a structure that contains the GUID and other event information. For a
custom target device change event, the driver passes a
TARGET_DEVICE_CUSTOM_NOTIFICATION structure. In response, the
operating system notifies each registered user-mode component and then
notifies each registered kernel-mode component.
Drivers that use Plug and Play notification with user-mode applications should
always use IoReportTargetDeviceChangeAsynchronous instead of
IoReportTargetDeviceChange. IoReportTargetDeviceChange completes
synchronously; that is, it does not return until all registered components have been
notified. If one or more of the notified routines calls additional notification routines or
blocks for some reason, deadlocks can occur. Calling
IoReportTargetDeviceChangeAsynchronous prevents such deadlocks.
The following code sample shows how a kernel-mode driver would use
IoReportTargetDeviceChangeAsynchronous to broadcast notification of an event
—in this case, GUID_SOME_PNP_EVENT, which has previously been defined
elsewhere. The event information consists of the message "Hello."
typedef struct _EVENT_INFO {
ULONG Count;
WCHAR Message[32];
} EVENT_INFO;
UCHAR buffer[sizeof(TARGET_DEVICE_CUSTOM_NOTIFICATION)
+ sizeof(EVENT_INFO)];
PTARGET_DEVICE_CUSTOM_NOTIFICATION pNotify;
UNALIGNED EVENT_INFO * pInfo;
RtlZeroMemory(buffer, sizeof(buffer));
RtlCopyMemory(&pNotify->Event, &GUID_SOME_PNP_EVENT,
sizeof(GUID));
pNotify->NameBufferOffset = -1;
pNotify->Version = 1;
pNotify->Size = sizeof(*pNotify)
- sizeof(pNotify->CustomDataBuffer)
+ sizeof(*pInfo);
pInfo->Count = pdx->Count;
status = RtlStringCchCopyW(pInfo->Message,
sizeof(pInfo->Message)/sizeof(pInfo->Message[0]),
L"Hello!");
if (NT_SUCCESS(status)) {
IoReportTargetDeviceChangeAsynchronous(pdx->Pdo,
pNotify, NULL, NULL);
}
ZeroMemory(&NotificationFilter,
sizeof(NotificationFilter));
NotificationFilter.dbch_size =
sizeof(DEV_BROADCAST_HANDLE);
NotificationFilter.dbch_handle = hDevice;
NotificationFilter.dbch_devicetype = DBT_DEVTYP_HANDLE;
NotificationFilter.dbch_event = DeviceEventGuid;
if(!*hDevNotify)
{
Err = GetLastError();
printf( "RegisterDeviceNotification failed: %lx.\n",
Err);
return FALSE;
}
return TRUE;
}
Handling a Custom Event. The notification message and the data that is returned
with it depend on the type of requested notification and the type of user-mode
component that registered for notification. For custom event types, user-mode
applications receive WM_DEVICECHANGE messages and services receive
SERVICE_CONTROL_DEVICEEVENT controls. Structures that are returned with
the notification message contain the driver-defined GUID for the event and can also
include additional driver-defined data.
The following code sample shows how a user-mode application would handle the
WM_DEVICECHANGE message for a custom event. The application calls the
routine OnDeviceChange in a switch statement that handles
WM_DEVICE_CHANGE as follows:
case WM_DEVICECHANGE:
OnDeviceChange(Wparam, (_DEV_BROADCAST_HEADER *) Lparam);
break;
The following shows the source code for the OnDeviceChange routine:
void
OnDeviceChange(
ULONG EventCode,
_DEV_BROADCAST_HEADER *Hdr
)
{
PDEV_BROADCAST_HANDLE pHdrHandle;
if (EventCode == DBT_CUSTOMEVENT) {
if (memcmp(&pHdrHandle->dbch_eventguid,
&GUID_SOME_PNP_EVENT,
sizeof(GUID)) == 0) {
PEVENT_INFO pEventInfo = (PEVENT_INFO)
pHdrHandle->dbch_data;
...
}
else { // handle other event guids here }
}
else { // handle other EventCodes here }
}
Shared Handles
Windows provides handles, instead of pointers, through which user-mode and
kernel-mode components can access some system-defined (and typically opaque)
objects, such as files, events, and symbolic links. Functions that create and open
such objects return a handle to the object and increment the object’s reference
count. When the driver or application has finished using the object, it closes the
handle. The system, in turn, decrements the reference count. When the reference
count reaches zero, the system can delete the object.
The user-mode code creates the handle and calls DeviceIoControl to pass it to the
driver in an IOCTL buffer. The driver must then call ObReferenceObjectByHandle
to verify the handle and get a pointer to the underlying object. In the call, the driver
passes the following:
The handle it received from user mode
The type of access requested
The type of object to which the handle refers, either *IoFileObjectType or
*ExEventObjectType
The access mode (UserMode)
The call succeeds if:
The type of access requested is valid for an object of the specified type.
The object type that the driver specifies matches the type of object that the
handle references.
The access mode (UserMode) permits the type of requested access.
If the call is successful, it returns a pointer to the underlying object and increments
the object’s reference count. When the driver has finished using the object, it must
call ObDeferenceObject to decrement the reference count.
This technique ensures that the driver has a reference to a valid object, that the
object cannot be deleted while the driver is using it, and that system quota charges
are enforced for the user-mode process.
A driver should validate every handle it receives from user mode because user-
mode handles are not secure. For example:
The user-mode code could close and reopen the handle before the driver
accesses the object, leading to a security breach or a system crash.
When ZwXxx routines are called from kernel mode, the system performs
no access checks. A driver could successfully call ZwWriteFile, for example,
passing a handle that a user supplies who does not have write access to the
file.
Shared Memory
User-mode components cannot allocate virtual memory in the kernel address
space. Although it is possible to map kernel memory into user mode, a driver should
never do so for security reasons. Therefore, drivers and user-mode components
must use other strategies for sharing memory. Such strategies typically involve:
Mapped memory buffers.
Section objects with shared views.
As a general rule, drivers should avoid mapping device memory and registers into
user space. Whenever possible, they should share memory only through mapped
buffers that are passed in IOCTLs. The potential performance improvements that
might result from sharing the memory directly are usually outweighed by the
additional code, verification, and testing that such sharing makes necessary and the
risk of security and reliability problems.
METHOD_IN_DIRECT (same as
METHOD_DIRECT_TO_HARDWARE)
METHOD_OUT_DIRECT (same as
METHOD_DIRECT_FROM_HARDWARE)
METHOD_NEITHER
The user-mode application allocates a buffer and then calls
DeviceIoControl, supplying the driver-defined I/O control code and describing
the buffer. In response, the system builds an IRP_MJ_DEVICE_CONTROL
request and sends it to the driver. The IRP contains the IOCTL code, the buffer
length, and the I/O transfer type.
If the transfer type is METHOD_IN_DIRECT or METHOD_OUT_DIRECT, the
system checks the address and size of the buffer. If these are valid, the system
builds an MDL that describes the physical pages that comprise the buffer, and then
it locks (or “pins”) those pages in physical memory. The pages will be unlocked later
when the MDL is freed.
Upon receiving the IRP_MJ_DEVICE_CONTROL request, the driver proceeds as
follows to access the shared memory that is represented by the buffer:
1. If the MDL pointer in the IRP is not NULL, the driver calls
MmGetSystemAddressForMdlSafe to map the pages that are described by
the MDL into the kernel virtual address space, so that the driver can access
them. This mapping is created in a portion of the kernel virtual address space
that allows the driver to refer to the user data buffer in any process context.
2. The driver uses the kernel virtual address to access the buffer. The driver can
read and write the buffer at any IRQL and in any thread context because the
pages that comprise the user buffer are locked into memory.
If the transfer type is METHOD_NEITHER, the driver can use the user-space virtual
address to access the buffer in the context of the requesting process. Using user-
space addresses in kernel mode imposes several restrictions:
The driver must validate all user-space addresses. To validate an address,
the driver must call ProbeForRead or ProbeForWrite within a structured
exception handler.
The driver must enclose every access to the user-space buffer in a
structured exception handler.
The driver can access the buffer only within the context of the requesting
process. Within the context of any other process, the user-space addresses
could reference the wrong data or could be invalid.
The driver can access the buffer only at IRQL PASSIVE_LEVEL. Because
the buffer is allocated in user space and is not locked into memory, the system
can page it out at any time. Causing a page fault at IRQL DISPATCH_LEVEL or
higher can crash the system.
If the driver requires access to the buffer in an arbitrary thread context or at
DISPATCH_LEVEL or higher, it must build an MDL and lock the buffer into
memory, as described earlier in “Validating Buffer Lengths and Addresses.”
Regardless of the I/O transfer type, a driver that shares memory in a buffer must not
complete the IRP_MJ_DEVICE_CONTROL request until it has completely finished
using the buffer. In other words, the IRP must remain pending until all driver access
to the buffer is complete. If the driver attempts to read or write the buffer after
completing the IOCTL, the system might already have reallocated the memory to
some other process. At best, if the driver probes this address in a structured
exception handler, the system handles the error gracefully. At worst, the driver
could overwrite memory that belongs to a different application, causing the
application or system to crash.
Property pages are intended for setting device characteristics that are normally the
same for all users, but should be changeable by an administrator if necessary.
Avoid using property pages or finish-install pages to set user preferences.
Updating Drivers
A driver’s update procedure is normally similar to its installation procedure, with one
important difference:
When upgrading or updating a driver, co-installers should not supply finish-
install pages unless the updated driver requires additional settings that were not
available in the earlier version.
If possible, the update procedure should use the same settings that were obtained
during the previous installation.
Best Practices
Limit driver operations to device-specific and hardware-specific activities.
Handle all policy decisions and use interactions in user-mode components;
policy issues should never reach the driver.
Consider security, appropriateness, and ease of use for user-mode clients
when designing and implementing user-mode interactions in drivers.
Do not trust any data that is received directly from user-mode applications.
Validate all buffer lengths, probe all buffer pointers, and verify buffer contents (if
possible) before use.
Collect information from the user with a user-mode application.
Use Plug and Play notification to notify applications or services of changes
in device state.
If a kernel-mode driver and user-mode application must share a handle, the
user-mode application should create it and the driver should validate it.
Avoid mapping device memory and registers into the user virtual address
space.
Create installation procedures that can run without user intervention. If user
intervention is unavoidable, keep it to a minimum. Remember that Administrator
privilege is required at installation; do not prompt for user preferences during
installation.
Differentiate device settings from user preferences. Store the former in the
registry and store the latter in a user-mode file.
Before storing device-specific information in the registry, convert the
information to a form that is easily manipulated by a kernel-mode driver. Avoid
storing long strings or file names that a kernel-mode component must parse.
Resources
Security References:
Common Driver Reliability Issues
http://www.microsoft.com/whdc/driver/security/drvqa.mspx
Windows Security Model: What Every Driver Writer Needs to Know
http://www.microsoft.com/whdc/driver/security/drvsecure.mspx
Locks, Deadlocks, and Synchronization
http://www.microsoft.com/whdc/driver/kernel/locks.mspx
Related Information: