What Is Accessing Memory for I/O Buffers?

For some types of I/O, the I/O manager allocates buffers and maps them for use by the driver. For others, the driver must perform additional mapping.

Buffered I/O requests (DO_BUFFERED_IO and METHOD_BUFFERED) include a pointer to a system-space buffer that contains a copy of the data that was passed in the caller’s user-space buffer. The driver can read and write to the buffer directly through this pointer. The driver does not need to allocate buffer space.

Direct I/O requests (DO_DIRECT_IO, METHOD_IN_DIRECT, METHOD_OUT_DIRECT, METHOD_DIRECT_TO_DEVICE, and METHOD_DIRECT_FROM_DEVICE) include an MDL that describes a user-space buffer that has been locked into physical memory. The MDL maps the virtual addresses in user space to the processor-relative physical addresses that describe the buffer. To read or write to the buffer, the driver can use the user-space buffer pointer only while it is running in the context of the calling process, and it must take measures to prevent security breaches. Typically, however, the driver is running in an arbitrary process context and must instead use system space virtual addresses. To obtain the system-space virtual addresses that correspond to the addresses in the MDL, the driver passes the MDL pointer to MmGetSystemAddressForMdlSafe. The returned value is a system-space virtual address through which the driver can access the buffer.

Requests for neither buffered nor direct I/O (METHOD_NEITHER) include an unvalidated pointer to a buffer in user space. If the driver will access the buffer only from the context of the requesting user-mode thread, it can do so through the user-space address (the pointer)—but only after validating the address and access method and only within an exception handler. If the driver will access the buffer from an arbitrary thread context, it should create an MDL to describe the buffer, validate the user-space address, lock the buffer into the physical address space, and then map the buffer into system space.

Although the dispatch routines of many drivers run in the context of the calling thread, you can assume that this is true only in file-system drivers and other highest-level drivers. In other drivers, you must acquire a system-space virtual address for the user-space buffer. For more information about the context in which specific standard driver routines are called, see “Scheduling, Thread Context, and IRQL,” which is listed in the Resources section at the end of this paper.

A driver that accesses a user-mode buffer in the context of the user-mode thread must do so carefully to avoid corrupting memory and causing system crashes. The driver must wrap all attempts to access the buffer in an exception handler because another thread might free the buffer or change the access rights while the driver is accessing it. Furthermore, the driver must access the buffer only at IRQL PASSIVE_LEVEL or APC_LEVEL because the pages in the buffer are not locked into memory.
To validate the buffer, the driver calls ProbeForRead or ProbeForWrite, passing a pointer to the buffer, its length, and its alignment. Both of these macros raise a STATUS_ACCESS_VIOLATION exception if the address and length do not specify a valid range in user space and a STATUS_DATATYPE_MISALIGNMENT exception if the beginning of the address range is not aligned on the specified byte boundary. In addition, ProbeForWrite raises the STATUS_ACCESS_VIOLATION exception if the buffer is not available for write access.
inBuf = irpSp->Parameters.DeviceIoControl.Type3InputBuffer;
inBufLength =
     irpSp->Parameters.DeviceIoControl.InputBufferLength;

try {
    ProbeForRead( inBuf, inBufLength, sizeof( UCHAR ) ); 
}
except(EXCEPTION_EXECUTE_HANDLER)
{

    ntStatus = GetExceptionCode();
    SIOCTL_KDPRINT((
        "Exception while accessing inBuf 0X%08X in “
        “METHOD_NEITHER\n",
         ntStatus));
    break;
}


Validating the buffer once upon receiving the I/O request is not enough. The driver must enclose every access in an exception handler and use ProbeForRead or ProbeForWrite before reading or writing the buffer.

1.   Allocate and initialize an MDL that is large enough to describe the buffer.
2.   Call MmProbeAndLockPages to probe the buffer for validity and access mode. If the buffer is valid, MmProbeAndLockPages locks its pages into memory.
3.   Map the locked pages into system space by calling MmGetSystemAddressForMdlSafe. Even though the pages are resident in memory, the driver must access them through a system-space address because the user-space address could become invalid at any time either because of legitimate system actions (such as trimming the page from the working set) or inadvertent or malicious user actions (such as deleting the buffer in another thread).

// Allocate MDL, passing the virtual address supplied in the
// IRP, its length, FALSE to indicate that it is not a
// secondary buffer, TRUE to indicate that the pages should
// be charged against the quota of the requesting process,
// and NULL to indicate that the MDL is not directly
// associated with an I/O request. 

mdl = IoAllocateMdl(inBuf, inBufLength, FALSE, TRUE, NULL);
if(!mdl)
{
    ntStatus = STATUS_INSUFFICIENT_RESOURCES;
    return;
}
try
{   
    MmProbeAndLockPages(mdl, UserMode, IoReadAccess);
}
except(EXCEPTION_EXECUTE_HANDLER)
{
    ntStatus = GetExceptionCode();
    SIOCTL_KDPRINT((
        "Exception while locking inBuf 0X%08X in "
        "METHOD_NEITHER\n",
         ntStatus));
    IoFreeMdl(mdl);
}

//
// Map the physical pages described by the MDL into system
// space. Note that mapping a large buffer this way
// incurs a lot of system overhead.
//

buffer =
      MmGetSystemAddressForMdlSafe(mdl, NormalPagePriority );

if(!buffer) {
        ntStatus = STATUS_INSUFFICIENT_RESOURCES;
        MmUnlockPages(mdl);
        IoFreeMdl(mdl);
}


The buffer pointer that MmGetSystemAddressForMdlSafe returns is a valid system-space pointer to the user’s buffer. Because the pages in the buffer are resident (locked) in memory, the driver can now access them without an exception handler.

MmUnlockPages(mdl);
IoFreeMdl(mdl);     


The driver must call these functions in the order shown. Freeing the MDL before unlocking the pages could result in a system crash because the list of pages to be unlocked is freed along with the MDL and is thus not guaranteed to be accessible after the IoFreeMdl has returned. 

0 komentar:

Posting Komentar

 

Serba Ada Blog Copyright © 2011-2012 | Powered by Blogger