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.
The following example shows how a driver
that is running in the context of the user-mode process can validate for read
access a buffer that is passed in an IOCTL request:
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.
Most drivers, however, cannot depend
upon being in the user process context when they read or write a buffer that
was passed in an IOCTL. Any driver that might need to access the buffer from an
arbitrary thread context must map the buffer into system space as follows:
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).
The following example shows how to
perform these steps:
// 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.
When the driver has finished using the
buffer, it must unlock the pages and free the MDL, as follows:
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