User-Mode Interactions: Guidelines for Kernel-Mode Drivers July 10, 2006




Дата канвертавання22.04.2016
Памер90.73 Kb.



User-Mode Interactions: Guidelines for Kernel-Mode Drivers

July 10, 2006



Abstract

Many kernel-mode drivers for the Microsoft® Windows® family of operating systems must interact with user-mode components by sharing data, objects, and handles; passing notification and synchronization information; and using settings and properties supplied by a user. This paper provides guidelines for designing and implementing these interactions.

This information applies for the following operating systems:
Microsoft Windows Vista™
Microsoft Windows Server® 2003
Microsoft Windows XP
Microsoft Windows 2000

The current version of this paper is maintained on the Web at:


http://www.microsoft.com/whdc/driver/kernel/KM-UMGuide.mspx

References and resources discussed here are listed at the end of this paper.



Contents

Introduction 4

Design Guidelines for User-Mode Interaction 4

Techniques for Sharing Information 5

I/O Requests 5

Validating Buffer Lengths and Addresses 6

Verifying IOCTLs 8

Performing Security Checks 8

Synchronization and Notification 8

Shared Events 9

Driver-Defined IOCTLs 9

Plug and Play Notification 9

Shared Handles 13

Process and Kernel Handle Tables 13

Handles and Access Rights 14

Passing Handles from User Mode to Kernel Mode 14

Shared Memory 15

Mapped Memory Buffers 15

Section Objects and Shared Views 16

Installation and Update Issues 18

User Interaction during Installation 18

Device Settings and User Preferences 19

Writing Device-Specific Information in the Registry 19

Updating Drivers 20

Best Practices 20

Resources 21


Disclaimer

This is a preliminary document and may be changed substantially prior to final commercial release of the software described herein.


The information contained in this document represents the current view of Microsoft Corporation on the issues discussed as of the date of publication. Because Microsoft must respond to changing market conditions, it should not be interpreted to be a commitment on the part of Microsoft, and Microsoft cannot guarantee the accuracy of any information presented after the date of publication.
This White Paper is for informational purposes only. MICROSOFT MAKES NO WARRANTIES, EXPRESS, IMPLIED OR STATUTORY, AS TO THE INFORMATION IN THIS DOCUMENT.
Complying with all applicable copyright laws is the responsibility of the user. Without limiting the rights under copyright, no part of this document may be reproduced, stored in or introduced into a retrieval system, or transmitted in any form or by any means (electronic, mechanical, photocopying, recording, or otherwise), or for any purpose, without the express written permission of Microsoft Corporation.
Microsoft may have patents, patent applications, trademarks, copyrights, or other intellectual property rights covering subject matter in this document. Except as expressly provided in any written license agreement from Microsoft, the furnishing of this document does not give you any license to these patents, trademarks, copyrights, or other intellectual property.
Unless otherwise noted, the example companies, organizations, products, domain names, e-mail addresses, logos, people, places and events depicted herein are fictitious, and no association with any real company, organization, product, domain name, email address, logo, person, place or event is intended or should be inferred.
© 2006 Microsoft Corporation. All rights reserved.
Microsoft, Win32, Windows, Windows Server, and Windows Vista are either registered trademarks or trademarks of Microsoft Corporation in the United States and/or other countries.
The names of actual companies and products mentioned herein may be the trademarks of their respective owners.

Introduction


The Microsoft® Windows® operating system divides its use of the virtual address space into two distinct ranges for mapping. The user virtual address space (also called user space) maps the current user process and the kernel virtual address space (also called kernel space or system space) maps the operating system code and structures. User space occupies the lower range of the address space, and kernel space occupies the upper range. The exact size of each range depends on the version of Windows being run.

Windows also uses two processor operating modes that are defined by the hardware: user mode and kernel mode. Applications and services that run in user mode have access only to the user virtual address space. When a user-mode process requires data that resides in the kernel virtual address space, it calls a system service to return the data.

The operating system and most drivers run in kernel mode and have access to operating system structures, to all system memory, and to all processor instructions. Kernel-mode components implicitly trust each other—that is, they assume that addresses and parameters that they receive from other kernel-mode components are valid. Kernel-mode components must not, however, trust user-mode components. To safeguard system security, kernel-mode components must validate all data and addresses that are passed to them from user mode.

Every user I/O request involves an exchange of information between user mode and kernel mode. The layered Windows Driver Model (WDM) effectively shields many kernel-mode drivers from the details of mapping and validating user-mode data because the I/O manager and other system components perform these tasks before passing an I/O request to the driver. Some drivers, however, must work directly with data and addresses received from user-mode components.

Similar issues sometimes affect driver installation and update, too. In some situations, driver installation requires data or intervention from a user. Installation packages frequently record information in the system registry for later use by drivers and user-mode applications. Each installation package must obtain and secure such user data according to certain guidelines.

To write secure, reliable drivers that correctly handle these interactions with user mode, developers must understand:



  • Guidelines for designing user-mode interactions

  • Techniques for exchanging and validating user-mode data and addresses

  • Installation scenarios that might require user intervention



Design Guidelines for User-Mode Interaction


Drivers must follow these basic rules for interacting with user-mode components:

  • Kernel-mode components cannot call user-mode functions directly.

  • Kernel-mode components must validate all data and addresses that they receive from user-mode components.

If the initial driver design requires calls to user-mode functions or applications, carefully analyze the reasons for these calls. Such calls often indicate faulty assumptions underlying the design. Driver operations should be limited to device-specific and hardware-specific activities. Policy decisions and interactions with the user should be handled by user-mode components; policy issues should never reach the driver.

A driver that is closely coupled with a user-mode application should be designed so that either the application initiates communication by calling the driver or the driver uses a kernel-mode mechanism, such as Plug and Play notification, to initiate the communication.

Consider the following questions when designing a kernel-mode driver:



  • Which tasks must be performed in kernel mode, and which can be done in user mode?
    Kernel-mode code should perform only those tasks that cannot be done in user mode, and nothing more. Running in kernel mode does not necessarily improve performance. User-mode components are typically more reliable and are easier to develop, debug, and maintain.

  • What is the best way for the driver to communicate with user-mode components in a particular situation?
    The appropriate technique depends on the device, the nature of the interaction, and the amount of data involved.

  • What are the security consequences of the techniques the driver uses to share data?
    Whenever data passes between user mode and kernel mode, security breaches can occur. Drivers must validate all user-mode data and addresses.

Techniques for Sharing Information


During device and driver operation, interactions between kernel-mode drivers and user-mode components typically involve the following:

  • I/O requests

  • Synchronization and notification

  • Shared handles

  • Shared memory

I/O Requests


Kernel-mode drivers and user-mode applications exchange data whenever an application requests an I/O operation. Each request arrives in an I/O request packet (IRP); the driver parses and responds to the request.

To protect the system against errors—whether malicious or not—the driver must validate addresses, buffer lengths, and other data received from user mode. The extent of validation that is necessary depends on the data transfer method and the type of request (that is, the major IRP code).

Validation is important because the system could crash, memory could become corrupted, or a serious security breach could occur if a kernel-mode driver accesses an invalid user-space address or reads or writes a malformed buffer.

IMPORTANT: The following sections review issues related to user-mode data in I/O requests. For a thorough description of the validation required for each transfer and request type, see the white paper titled Common Driver Reliability Issues. Everyone who writes kernel-mode driver code should become familiar with the material in this paper.

Validating Buffer Lengths and Addresses


Drivers must ensure that the buffer lengths and addresses that are supplied in user I/O requests are valid. The I/O manager performs some validation on behalf of the driver, depending on the I/O transfer method. Windows supports three I/O transfer methods:

  • Buffered I/O

  • Direct I/O

  • Neither buffered nor direct I/O, also called Neither I/O

The transfer method that is used for a particular request depends on the type of request. For read and write requests (IRP_MJ_READ and IRP_MJ_WRITE), the driver specifies the transfer method in the Flags field of the device object, either DO_BUFFERED_IO or DO_DIRECT_IO. For buffered and direct I/O transfers, the I/O manager validates the user’s information as described in the following sections. If the Flags field does not contain a transfer type, the I/O manager validates the buffer address that is passed to it in the user’s request and passes the buffer address and length to the driver. The driver must validate the buffer length.

For IOCTLs—IRP_MJ_DEVICE_CONTROL and IRP_MJ_INTERNAL_DEVICE_CONTROL requests—the transfer method is defined in the I/O control code itself. Valid transfer methods for IOCTLS are shown in the following list.

I/O transfer method

Valid transfer for IOCTLs

Buffered I/O

METHOD_BUFFERED

Direct I/O

METHOD_IN_DIRECT
METHOD_DIRECT_TO_HARDWARE
METHOD_OUT_DIRECT
METHOD_DIRECT_FROM_HARDWARE

Neither I/O

METHOD_NEITHER

For all of these methods, the I/O manager proceeds as described in the following sections.


Validation for Buffered I/O Transfers


Buffered I/O requires minimal validation by the driver. Buffered I/O involves a set of buffers in the user address space and a second, separate set of buffers in the kernel address space. The user-mode application reads and writes only the buffers in the user space, and the kernel-mode driver reads and writes only the buffers in the kernel space. The I/O manager copies data from one set of buffers to the other and validates buffer pointers. However, the driver is responsible for verifying buffer lengths and zeroing unused bytes in output buffers.

Validation for Direct I/O Transfers


Direct I/O requires an intermediate level of driver validation. Drivers that perform direct I/O must do the following:

  • Correctly handle zero-length buffers.

  • Validate buffer lengths.

  • Verify buffer contents, if possible.

A user-mode application that requests direct I/O allocates an I/O buffer. If the length of the buffer is nonzero, the I/O manager creates a memory descriptor list (MDL) that describes where the buffer resides in physical memory. That is, the MDL maps the user-space virtual addresses to the physical memory pages that comprise the buffer. The I/O manager validates the user-space virtual addresses, checks the caller’s access rights to the buffer, and locks (or “pins”) the corresponding physical pages in memory. When it passes the IRP to the driver, the I/O manager provides a pointer to the MDL at Irp->MdlAddress.

The driver’s actions differ depending on whether it performs direct memory access (DMA) or programmed I/O (PIO). In drivers that perform DMA, the driver’s AdapterControl routine passes the MDL pointer to MmGetMdlVirtualAddress to get the base virtual address for the data transfer. The MapTransfer routine uses this address as an index into the buffer at which to start the data transfer. If the request is large and must be split into several smaller DMA transfers, the driver must update the address before starting each individual DMA operation.

Drivers that perform PIO typically access the data directly in the buffer and therefore must use a kernel-space virtual address. To obtain kernel-space virtual addresses that correspond to the physical addresses in the MDL, the driver passes the MDL pointer to MmGetSystemAddressForMdlSafe. Consequently, the user-mode application accesses the buffer by using user-space addresses, and the kernel-mode driver accesses the buffer by using kernel-space addresses.

Although the location of the buffer is fixed in memory, the user-mode process can change the contents of the buffer at any time. For this reason, kernel-mode drivers should avoid consecutive reads (“double fetches”) from the same location. Instead, the driver should copy the contents of the buffer into one or more internal structures and then validate the data.

Validation for Neither I/O Transfers


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 ProbeForRead and ProbeForWrite in a structured exception handler (a try/except block).

  • Validate all buffer lengths.

  • Enclose every access to the user buffer within a structured exception handler.

  • Access the buffer only at IRQL PASSIVE_LEVEL or APC_LEVEL. Because the buffer is not locked into memory, the system can page it out at any time. Causing a page fault at a higher IRQL can crash the system.

  • Map and lock the buffer if it must be accessible to driver routines that are called in an arbitrary thread context or to any driver code that runs at IRQL DISPATCH_LEVEL or higher.

To map and lock the buffer for use in an arbitrary thread context or at IRQL>=DISPATCH_LEVEL, the driver routine must do the following:

1. Create an MDL to describe the buffer by calling IoAllocateMdl.

2. Lock the pages into memory by calling MmProbeAndLockPages within a structured exception handler.

3. Map the user-mode buffer into the kernel address space by calling MmGetSystemAddressForMdlSafe.

4. Process the request as appropriate.

5. Unlock the buffer by calling MmUnlockPages after all kernel-mode components have finished using it. (MmUnlockPages automatically unmaps the MDL if it was mapped into the kernel-mode address space.)

Verifying IOCTLs


When a driver receives an I/O request that specifies IRP_MJ_DEVICE_CONTROL or IRP_MJ_INTERNAL_DEVICE_CONTROL, it should validate the DeviceType, Access, Function, and Method fields of the 32-bit I/O control code, and not the Function field alone. Testing all of these fields ensures that the caller-supplied request matches the IOCTL definition.

When defining IOCTLs, drivers should always specify the minimum access that is required to perform the task. Remember that FILE_ANY_ACCESS provides unrestricted access to any caller that has a valid handle to the device (that is, to the file object that represents the device object). To require read and write access, specify FILE_READ_DATA | FILE_WRITE_DATA.


Performing Security Checks


Drivers must sometimes perform security checks to ensure that the caller has adequate privilege to perform the requested operations. The type and extent of required authentication depend on the type of operation that the caller requested. For example, requests to open the device namespace require more extensive checking than requests to open an object within that namespace.

For more information about checking security in kernel-mode drivers, see the white paper titled Windows Security Model: What Every Driver Writer Needs to Know.


Synchronization and Notification


Most drivers do not require any special synchronization with or notification of user-mode applications. Typically, a driver receives an I/O request, processes the request, and returns results in an output buffer, I/O status variable, or both. For routine I/O requests, no additional synchronization or notification is necessary.

Drivers that handle atypical I/O scenarios, however, might require additional synchronization. For example, if a user-mode application continuously produces data that the driver copies internally and then processes asynchronously, the driver might be required to notify the application when it has completely emptied a buffer so that the application can insert more data.

Some situations require notification rather than synchronization. If changes in the hardware require action by a user, the driver might need to notify an application so that the application, in turn, can prompt the user to intervene.

The driver cannot call a function in the application to notify or synchronize with it because Windows does not allow function calls from kernel mode to user mode. Instead, the driver and application must use one of the following mechanisms:



  • A shared event

  • An IOCTL that is defined by the driver

  • Plug and Play notification


Note: The Windows Driver Kit (WDK) includes a sample driver (src\general\event) that demonstrates the use of shared events and driver-defined IOCTLs. For detailed information about both of these techniques, see the white paper titled Locks, Deadlocks, and Synchronization.

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.

By sharing an event, a driver and a user-mode application can implement synchronization or mutual notification. Windows provides several ways to do this. The best technique is to create the event in user mode and send the event handle to the driver in an IOCTL by calling DeviceIoControl.

After receiving the handle, the driver calls ObReferenceObjectByHandle to validate the handle and get a pointer to the associated event object. ObReferenceObjectByHandle also removes a reference on the event object, thus preventing the system from deleting the event until the driver has explicitly dereferenced it, even if the user-mode application closes the handle. After the driver has a pointer to the event, it can signal the event from any arbitrary thread context 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.


Plug and Play Notification


In addition to shared events and driver-defined IOCTLS, WDM drivers can use Plug and Play notification. By using this mechanism, any number of user-mode or kernel-mode components can register to be notified whenever a specific type of Plug and Play event occurs for a particular device. For example, a music transfer service might register for notification whenever the user plugs in an MP3 player.

Plug and Play notification provides one-way communication from the driver to the registered components. If the driver requires acknowledgment or status, using Plug and Play notification is not appropriate. A driver has no way to determine which, if any, applications have registered for notification, nor how they have responded. If two-way communication is necessary, use a shared event or driver-defined IOCTL instead.


Plug and Play Event Types


The system defines several types of Plug and Play events; drivers can also define custom events. Event types include the following:

  • Device interface change events, which occur upon the arrival of a new device interface or removal of an existing device interface for a registered device interface class.

  • Hardware profile change events, which occur when changes to a hardware profile are proposed, canceled, or completed.

  • Target device change events, which occur when removal of a device is proposed, canceled, or completed. All custom events are also considered target device change events.

Although notification is a Plug and Play mechanism, its use is not limited to hardware-related events. A driver can use a custom target device change event to notify registered components of any discrete device-related occurrence that the driver can define. An example is GUID_IO_MEDIA_ARRIVAL, which is sent as a custom event for a removable media device (such as a CD-ROM) when media has been inserted.


Using Notification in a Kernel-Mode Driver


To use Plug and Play notification to communicate with a user-mode application, a kernel-mode driver must do the following:

1. Create a global unique identifier (GUID) for the event, using guidgen or uuidgen.

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));
pNotify = (PTARGET_DEVICE_CUSTOM_NOTIFICATION) 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 = (EVENT_INFO UNALIGNED *)

&pNotify->CustomDataBuffer[0];


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);

}

Receiving Notification in a User-Mode Component


To receive notification, a user-mode component must do the following:

1. Obtain a handle to the device for notification about custom target device change events.

2. Register for notification by calling RegisterDeviceNotification, passing a notification filter that describes the type of notification requested. For a custom event, this is a DEV_BROADCAST_HANDLE notification filter.

3. Include code to parse and handle the notification.



Registering for Notification. The following code sample shows how a user-mode application would register for notification of a custom event.

BOOL DoRegisterDeviceEventNotify(

GUID DeviceEventGuid,

HANDLE hDevice,

HDEVNOTIFY *hDevNotify

)

{



DEV_BROADCAST_HANDLE NotificationFilter;

DWORD Err;


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;
*hDevNotify = RegisterDeviceNotification( hWnd,

&NotificationFilter,

DEVICE_NOTIFY_WINDOW_HANDLE

);
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;
pHdrHandle = (PDEV_BROADCAST_HANDLE) Hdr;
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 }

}

When Notification Occurs


For most system-defined events and all custom events, the system notifies user-mode applications before notifying kernel-mode components.

Because the system notifies user-mode applications through the standard Windows messaging mechanism, the driver does not require code to handle Fast User Switching (FUS). FUS is a feature of Microsoft Windows XP and later that enables multiple users to be logged on to the same machine. FUS works by allowing multiple virtual display drivers to run at one time.

Remember, however, that notification is sent to all applications that have registered for it. Therefore, if two users are logged in and two instances of a registered application are running, the system notifies both instances.

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.

Process and Kernel Handle Tables


Every handle is an index into a handle table. The system maintains a unique handle table for each process. By default, the system associates each handle with the process that created it. Therefore, if a driver creates a handle while running in the context of a user-mode process, the handle indexes the handle table for that process. A handle that is listed in the process handle table can be used only in the context of the process that created it. Any additional threads in that process can also access the object by using that handle.

A separate kernel handle table lists handles that are accessible only from kernel mode. Kernel handles can be used in any process context. Handles that are created while a driver is running in the context of the system process, such as in the DriverEntry or AddDevice routine, are by default kernel handles.

A driver that is running in the context of a user-mode process can create a kernel handle by setting the OBJ_KERNEL_HANDLE attribute for the handle. To set the attributes, the driver calls InitializeObjectAttributes before opening the handle. Handles with the OBJ_KERNEL_HANDLE attribute are listed in the kernel handle table and therefore cannot be used in user mode. Setting this attribute ensures that a user-mode process cannot access, close, or replace the handle.

It’s a good programming practice to set the OBJ_KERNEL_HANDLE attribute for handles that are created in the system process context, too. Although such handles are by default kernel-mode handles, setting the attribute makes the intended use clear to anyone who might maintain the code later.

Setting the OBJ_KERNEL_HANDLE attribute is especially important for handles that are passed to the ZwXxx routines. These calls translate to functions with the previous processor mode set to KernelMode. Consequently, the system does not validate access rights. Setting the OBJ_KERNEL_HANDLE attribute protects the handle and the system object it represents from access by the user-mode process in whose context they are running.

Handles and Access Rights


When the system creates a handle, it records the process’s access rights with the handle. When a user-mode caller tries to use the handle to access the object, the system checks the process’s rights against the rights that are required for the requested operation. For example, a user-mode caller to WriteFile must have GENERIC_WRITE access to the file. The system does not check access for kernel-mode callers.

The OBJ_FORCE_ACCESS_CHECK attribute causes the system to perform all access and quota checks on the object being opened, even if the request originates from kernel mode. If this attribute is not set, the system bypasses these checks for kernel-mode handles.


Passing Handles from User Mode to Kernel Mode


In some situations, a driver and a closely-coupled user-mode application must share a handle. The best technique is to create the handle in user mode and then pass it to the driver. If the user-mode application creates the handle, the system checks access rights and enforces quotas.

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.


Mapped Memory Buffers


The simplest and most secure way for a driver to share memory with a user-mode application is to pass a buffer in an IOCTL. In short:

  • The driver defines an IOCTL with one of these transfer types:

  • 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.


Section Objects and Shared Views


A section object describes an area of memory that two or more processes can potentially share. In the Microsoft Win32® API, section objects are called file-mapping objects. Windows uses section objects to implement shared memory and to map disk files into memory.

Although user-mode processes commonly use section objects to share memory with each other, kernel-mode drivers should avoid using them to share memory with user-mode components unless necessary. Whenever possible, drivers and user-mode applications should implement shared memory by using buffers, as described in the previous section. However, high bandwidth requirements sometimes make passing data in IOCTL buffers impractical.

Every section object is backed by either the system paging file or a file on disk. Drivers that use section objects to share memory with a user-mode application typically map an area that is backed by the system paging file. An application or driver routine that creates a section object can map the entire backing file if the address space has enough free space for the entire object to fit. If the file is too large, however, the application or driver routine can map a portion of it. This portion is called the view. If two processes share a section object, each can have its own view of the object or they can share the same view.

Creating and Sharing a Section Object


Windows provides several ways for a driver and user-mode application to share a section object. Using the technique described here, both a driver and a user-mode application open a named section object and each maps a separate view of that section object. Mapping separate views reduces the security risks of sharing the section object between user mode and kernel mode.

The user-mode application maps a view into the user virtual address space and the driver maps a view into the kernel virtual address space. In return, the user-mode application receives a handle that is valid only in user mode and has been checked for security and access rights. The driver receives a handle that is valid only in kernel mode.

Because the view is mapped into the address space of the current process, the driver must open the section object and map the view while running in the context of the system process, such as in a DriverEntry or AddDevice routine.

To share a section with a user-mode application, a driver must do the following:

1. Call InitializeObjectAttributes to create an attribute structure that specifies the name of the section object and sets the OBJ_FORCE_ACCESS_CHECK and OBJ_KERNEL_HANDLE attributes.

2. Call ZwCreateSection to create a new section object or open an existing one, specifying the size of the section, the backing file, and the attribute structure. In addition, the driver specifies the following:



  • A bit mask that describes the desired access to the section. Drivers should always specify SECTION_QUERY, which allows queries about the section access. Drivers that share a section with a user-mode application should specify the minimum required access and should avoid using SECTION_ALL_ACCESS.

  • A bit mask that describes the section’s attributes, including whether it can be cached and whether physical storage is allocated for it.

  • The required protection for the view when the file is mapped. The view can be read-only, read-write, or copy-on-write.

ZwCreateSection returns a handle to the section object.

3. Call ZwMapViewOfSection to map a view of the section into the process address space, specifying NtCurrentProcess (a macro that is defined in wdm.h and ntddk.h) as the handle to the process. This call maps the view into the user virtual address space of the specified process—in this case, the system process. Mapping the driver’s view into the system process prevents user-mode applications from tampering with the view and ensures that the driver’s handle is accessible only from kernel mode.



ZwMapViewOfSection returns the base address and the size of the view.

4. Call ZwUnmapViewOfSection to unmap the view when it has finished using that view.

5. Call ZwClose to close the handle and delete the object when it has completed using the section.
In the user-mode application, do the following:

1. Call CreateFileMapping to open a new or existing file mapping object by name and specify the required access to the file. If the driver has already opened the object, this function returns a handle to the open object.

2. Call MapViewOfFile or MapViewOfFileEx to map a view of the file. These functions return the starting address of the mapped view.

3. Use the returned starting address to read or write the view.


Note: Drivers should not use ZwMapViewOfSection to map a memory range in \Device\PhysicalMemory into user mode. Doing so directly maps physical memory. A user-mode component could write over pages that belong to a different process, thus causing system corruption and crash.

Guidelines for Using Section Objects


Sharing memory with a user-mode application poses certain security risks. Although the technique that is described in this paper creates separate views and therefore reduces many of the risks, a driver should still verify any data, such as embedded pointers, that it reads from the shared section.

If shared views are implemented instead of separate views, the driver must do the following:



  • Set the OBJ_FORCE_ACCESS_CHECK attribute when opening a handle to the section object.

  • Create the section object and map the views.

  • Ensure that the view is mapped in the address space of the correct user-mode process. A highest level driver can guarantee that the view is mapped in the current process context by mapping the view in a dispatch routine. Dispatch routines of lower level drivers run in an arbitrary thread and process context, and thus cannot safely map a view in a dispatch routine.

  • Protect all attempts to access the view with a structured exception handler. A malicious user-mode application could unmap the view or change the protection state of the view.

  • Validate the contents of the view as necessary.

Installation and Update Issues


Most driver installation components run in user mode but supply information that will be used later by a kernel-mode driver. In addition, many installation procedures require user intervention—to insert media, to specify file locations, and to set device, driver, or application characteristics.

User Interaction during Installation


To create the best possible user experience, the driver package should support a “server-side” installation procedure. A server-side installation requires no user prompts; the system’s Plug and Play components handle the entire installation. Server-side installations do not require an administrator to be logged on, and no action is required of a user other than plugging in the hardware.

In many cases, however, a server-side installation is not possible. For example, if the device-specific application is shipped that must be installed along with the driver or if a user must specify a location from which to install one or more items, user interaction is necessary. Most user prompts are handled by a device co-installer; however, a custom device installation application might also interact with the user. The following table lists the required installation components for each of these tasks.



To perform this task:

Supply this component:

Install a device-specific application, such as imaging software for a camera

Custom device installation application that is invoked by Autorun on a product CD

Use system-specific configuration information to set up the device or driver

Co-installer (a Win32 DLL)

Provide property pages for the device

Co-installer

Prompt the user for additional device or installation parameters

Co-installer and finish-install pages that the co-installer displays

Co-installers and custom device installation applications run as part of the system’s Setup component. At the end of installation, Setup displays the finish-install pages that are supplied by a device’s co-installers or class installer to prompt the user for information, such as whether to install a device-specific application. Property pages are displayed by Device Manager, so that users with Administrator privilege can change device characteristics.

Setup requires Administrator privilege on the local machine to load driver software from a CD or to respond to finish-install pages. Therefore, limit any prompts in finish-install pages and custom device installation applications to information that is required to complete the installation or that applies to all users of the device.

Device Settings and User Preferences


Device settings apply for all users and should usually be controlled by an administrator. In contrast, user preferences vary from user to user and should be controlled by the individual users themselves.

If the package includes a device-specific application, privilege issues at installation can be avoided by deferring prompts for user preferences. The first time the application runs, it should check for the existence of a preferences file for the current user. If no such file exists, it should prompt the user for settings or use a default file.

If the settings are per-device, rather than per-user, consider installing the device and driver with default settings and supplying property pages that an Administrator can change later, instead of using finish-install pages to prompt for device settings. 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.


Writing Device-Specific Information in the Registry


If the driver requires system-specific or device-specific configuration information, a co-installer can gather this information and write it to the registry, by using SetupDiXxx APIs such as SetupDiOpenDeviceRegistryKey. The driver can access these same registry keys by using IoXxx routines, such as IoOpenDeviceRegistryKey. The co-installer should parse the information and convert it to a format that is easily read and manipulated by a kernel-mode driver. For example, the co-installer should avoid writing a file name in the registry; instead, it should record a checksum, which is both more secure and more easily managed by a kernel-mode driver.

Both user-mode applications and kernel-mode drivers should treat registry contents as untrusted, modifiable information. If one of the driver components writes information to the registry and another component reads it later, do not assume that the information has not been modified in the meantime. Whenever possible, validate information from the registry before using it.


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:


Microsoft Hardware and Driver Developer Information

http://www.microsoft.com/whdc/default.mspx

Microsoft Windows Driver Kit (WDK)

http://www.microsoft.com/whdc/driver/WDK/aboutWDK.mspx

Microsoft Windows Platform SDK

http://msdn.microsoft.com/library/default.asp
See the Platform SDK section under Windows Development.

Microsoft Windows Internals, Fourth Edition

Solomon, David A., and Mark Russinovich. Redmond, WA: Microsoft Press, 2005.



Designed for Microsoft Windows XP Application Specification

http://www.microsoft.com/winlogo/software/windowsxp-sw.mspx

Microsoft Windows Logo Program System and Device Requirements

http://www.microsoft.com/whdc/winlogo/default.mspx



База данных защищена авторским правом ©shkola.of.by 2016
звярнуцца да адміністрацыі

    Галоўная старонка