Too long, too short, or just right? Buffer size matters

The wrong size could mean a crash, corruption, or a security hole

Failing to check the size of buffers is perhaps the most common driver error in I/O, and it can cause trouble for both your driver and the system. Drivers that fail to check buffer sizes increase the risk of faults and memory pool corruption that can crash the system. They also create security holes that an attacker could exploit.

Any time a driver writes off the end of a buffer—either before or after—the driver is using an invalid pointer and might overwrite somebody else's memory, which can crash the system.

Failing to validate a variable-length buffer (such as those used for structures with a fixed-size header and trailing variable-length data) can cause integer overflow or underflow, which also results in an invalid pointer.

Drivers that fail to check buffer size for IOCTL or FSCTL requests for a device make it possible for any caller that has access to such requests to read or write data beyond the end of the buffer. Whether the caller does this accidentally or deliberately, it represents a security risk.

Drivers that fail to check for zero-length buffers in direct I/O make it possible for the caller to write data beyond the end of the buffer or the end of the system page (depending on where the buffer is allocated), which is both a security risk and a source of hard-to-find bugs in your driver.

Drivers that fail to check buffer sizes run the risk of returning kernel-mode data in uninitialized bytes of output buffers, another security risk.

Another common area where buffer overruns occur is in string manipulation operations. The standard string manipulation functions supplied by the C/C++ language runtime libraries (strcat, strcpy, sprintf, and so on) do not prevent writing beyond the end of buffers.

What should you do?

Always check input and output buffer sizes in all I/O transfer requests to ensure that the buffers can hold all of the requested data.

Always validate variable-length buffers to avoid integer overflows and underflows.

Use MmGetSystemAddressForMdlSafe to map the address space for a direct I/O request, and fail the request if this routine returns NULL.

Always initialize all output buffers with zeros before returning them to the caller to avoid returning kernel-mode data in uninitialized bytes.

Use the safe string functions (RtlStringXxx) in ntstrsafe.h to manipulate Unicode and ANSI strings in kernel-mode drivers.

Test your drivers with tools such as Device Path Exerciser and PREfast, which include checks for correct handling of buffers.

The Common Driver Reliability Issues paper on WHDC describes techniques for validating buffer lengths, with code fragments that illustrate problems and how to fix them. Here's an example from that paper showing how to validate a variable-length buffer by subtracting the offset from the buffer length:

typedef struct _WAIT_FOR_BUFFER {
   LARGE_INTEGER Timeout;
   ULONG NameLength;
   BOOLEAN TimeoutSpecified;
   WCHAR Name[1];
   } WAIT_FOR_BUFFER, *PWAIT_FOR_BUFFER;

if (InputBufferLength < sizeof(WAIT_FOR_BUFFER)) {
    IoCompleteRequest( Irp, STATUS_INVALID_PARAMETER );
    return( STATUS_INVALID_PARAMETER );
   }

WaitBuffer = Irp->AssociatedIrp.SystemBuffer;

if ((InputBufferLength –
     FIELD_OFFSET(WAIT_FOR_BUFFER, Name[0])  >
       WaitBuffer->NameLength) {
    IoCompleteRequest( Irp, STATUS_INVALID_PARAMETER );
    return( STATUS_INVALID_PARAMETER );
   }
			

For more information:

Common Driver Reliability Issues
Driver I/O Methods and Their Tradeoffs

User-Mode Interactions: Guidelines for Kernel-Mode Drivers
Validating Buffer Lengths and Addresses

PREfast with Driver-Specific Rules

Windows DDK
Errors in Buffered I/O
Errors in Direct I/O
Device Path Exerciser
Using Safe String Functions

Writing Secure Code
Chapter 5, Public Enemy #1: The Buffer Overrun



Was This Information Useful?