Improper Address Validation in IOCTL with METHOD_NEITHER I/O Control Code

Description

Improper Address Validation in IOCTL with METHOD_NEITHER I/O Control Code is a kernel-mode vulnerability specific to Windows drivers where an IOCTL handler using METHOD_NEITHER fails to properly validate user-supplied memory addresses. When METHOD_NEITHER is specified in an IOCTL control code, the I/O Manager passes raw user-mode pointers directly to the driver without any buffering or validation. The driver becomes fully responsible for verifying that the addresses are valid user-mode addresses before accessing them. Failure to validate allows attackers to specify arbitrary memory addresses, potentially reading or writing kernel memory.

Risk

This vulnerability enables severe security attacks. If a driver accepts arbitrary addresses without validation, attackers can read sensitive kernel memory (including credentials, encryption keys, or kernel data structures) or write to arbitrary kernel memory locations. Writing to kernel memory can enable complete system compromise through code execution at the highest privilege level (SYSTEM/kernel). Even without achieving code execution, attackers can crash the system (BSOD) by causing the driver to access invalid addresses. This vulnerability class has been exploited in numerous real-world attacks against Windows systems.

Solution

Always validate user-space addresses when using METHOD_NEITHER. Use ProbeForRead() before reading from user buffers and ProbeForWrite() before writing to user buffers. These routines verify that addresses are in valid user-mode address space. Wrap all user buffer accesses in try-except blocks to handle access violations gracefully. Prefer using METHOD_BUFFERED, METHOD_IN_DIRECT, or METHOD_OUT_DIRECT instead of METHOD_NEITHER when possible, as these transfer methods have the I/O Manager perform validation automatically. If METHOD_NEITHER is required, minimize the attack surface by restricting access to the device object using proper security descriptors. Ensure the driver only operates with user-mode addresses, never kernel addresses passed from user mode.

Common Consequences

ImpactDetails
ConfidentialityScope: Confidentiality

Read Memory - Attackers can read arbitrary kernel memory including sensitive data and credentials.
IntegrityScope: Integrity

Modify Memory - Attackers can write to arbitrary memory, corrupting kernel data structures.
AvailabilityScope: Availability

DoS: Crash - Invalid memory access causes system crash (BSOD).
Access ControlScope: Access Control

Gain Privileges - Arbitrary kernel write enables SYSTEM-level code execution.

Example Code

Vulnerable Code

// Vulnerable: No address validation with METHOD_NEITHER
#include <ntddk.h>

#define IOCTL_VULNERABLE_READ CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, \
    METHOD_NEITHER, FILE_ANY_ACCESS)

NTSTATUS VulnerableDeviceControl(
    PDEVICE_OBJECT DeviceObject,
    PIRP Irp)
{
    PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);
    NTSTATUS status = STATUS_SUCCESS;

    if (IrpSp->Parameters.DeviceIoControl.IoControlCode == IOCTL_VULNERABLE_READ)
    {
        // With METHOD_NEITHER, these are raw user pointers
        PVOID userInputBuffer = IrpSp->Parameters.DeviceIoControl.Type3InputBuffer;
        PVOID userOutputBuffer = Irp->UserBuffer;

        // Vulnerable: No validation of user addresses
        // Attacker could provide kernel address!
        ULONG dataToRead = *(PULONG)userInputBuffer;  // Arbitrary read!

        // Vulnerable: Writing to attacker-controlled address
        *(PULONG)userOutputBuffer = SensitiveKernelData;  // Arbitrary write!
    }

    Irp->IoStatus.Status = status;
    IoCompleteRequest(Irp, IO_NO_INCREMENT);
    return status;
}
// Vulnerable: Insufficient validation
NTSTATUS VulnerablePartialValidation(PIRP Irp)
{
    PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);
    PVOID userBuffer = IrpSp->Parameters.DeviceIoControl.Type3InputBuffer;
    ULONG bufferSize = IrpSp->Parameters.DeviceIoControl.InputBufferLength;

    // Vulnerable: Only checks if pointer is non-null, not if it's valid
    if (userBuffer == NULL) {
        return STATUS_INVALID_PARAMETER;
    }

    // Vulnerable: Doesn't verify address is in user space
    // Attacker can provide kernel address
    RtlCopyMemory(KernelBuffer, userBuffer, bufferSize);

    return STATUS_SUCCESS;
}
// Vulnerable: Missing exception handling
NTSTATUS VulnerableNoExceptionHandling(PIRP Irp)
{
    PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);
    PVOID userBuffer = Irp->UserBuffer;

    // Even with ProbeForWrite, need exception handling
    ProbeForWrite(userBuffer, sizeof(ULONG), sizeof(ULONG));

    // Vulnerable: No try-except block
    // User could unmap memory between probe and access
    *(PULONG)userBuffer = ResultData;  // May crash

    return STATUS_SUCCESS;
}

Fixed Code

// Fixed: Proper address validation with METHOD_NEITHER
#include <ntddk.h>

#define IOCTL_FIXED_READ CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, \
    METHOD_NEITHER, FILE_ANY_ACCESS)

NTSTATUS FixedDeviceControl(
    PDEVICE_OBJECT DeviceObject,
    PIRP Irp)
{
    PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);
    NTSTATUS status = STATUS_SUCCESS;
    ULONG dataToRead = 0;

    if (IrpSp->Parameters.DeviceIoControl.IoControlCode == IOCTL_FIXED_READ)
    {
        PVOID userInputBuffer = IrpSp->Parameters.DeviceIoControl.Type3InputBuffer;
        PVOID userOutputBuffer = Irp->UserBuffer;
        ULONG inputLen = IrpSp->Parameters.DeviceIoControl.InputBufferLength;
        ULONG outputLen = IrpSp->Parameters.DeviceIoControl.OutputBufferLength;

        // Fixed: Validate buffer sizes
        if (inputLen < sizeof(ULONG) || outputLen < sizeof(ULONG)) {
            status = STATUS_BUFFER_TOO_SMALL;
            goto Complete;
        }

        __try {
            // Fixed: Probe addresses are in user space
            ProbeForRead(userInputBuffer, sizeof(ULONG), sizeof(ULONG));
            ProbeForWrite(userOutputBuffer, sizeof(ULONG), sizeof(ULONG));

            // Fixed: Access within try block in case page is unmapped
            dataToRead = *(PULONG)userInputBuffer;

            // Process the request
            ULONG result = ProcessRequest(dataToRead);

            // Fixed: Write result within try block
            *(PULONG)userOutputBuffer = result;

            Irp->IoStatus.Information = sizeof(ULONG);
        }
        __except (EXCEPTION_EXECUTE_HANDLER) {
            // Fixed: Handle access violation gracefully
            status = GetExceptionCode();
            DbgPrint("Exception accessing user buffer: 0x%X\n", status);
        }
    }

Complete:
    Irp->IoStatus.Status = status;
    IoCompleteRequest(Irp, IO_NO_INCREMENT);
    return status;
}
// Fixed: Using METHOD_BUFFERED instead (preferred)
#include <ntddk.h>

// METHOD_BUFFERED - I/O Manager handles validation
#define IOCTL_SAFE_BUFFERED CTL_CODE(FILE_DEVICE_UNKNOWN, 0x801, \
    METHOD_BUFFERED, FILE_ANY_ACCESS)

NTSTATUS SafeBufferedIoctl(
    PDEVICE_OBJECT DeviceObject,
    PIRP Irp)
{
    PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);
    NTSTATUS status = STATUS_SUCCESS;

    if (IrpSp->Parameters.DeviceIoControl.IoControlCode == IOCTL_SAFE_BUFFERED)
    {
        // With METHOD_BUFFERED, I/O Manager allocates kernel buffer
        // and copies data - no user address validation needed
        PVOID systemBuffer = Irp->AssociatedIrp.SystemBuffer;
        ULONG inputLen = IrpSp->Parameters.DeviceIoControl.InputBufferLength;
        ULONG outputLen = IrpSp->Parameters.DeviceIoControl.OutputBufferLength;

        if (inputLen < sizeof(ULONG)) {
            status = STATUS_BUFFER_TOO_SMALL;
            goto Complete;
        }

        // Safe: systemBuffer is kernel memory, already validated
        ULONG inputData = *(PULONG)systemBuffer;

        ULONG result = ProcessRequest(inputData);

        if (outputLen >= sizeof(ULONG)) {
            *(PULONG)systemBuffer = result;
            Irp->IoStatus.Information = sizeof(ULONG);
        }
    }

Complete:
    Irp->IoStatus.Status = status;
    IoCompleteRequest(Irp, IO_NO_INCREMENT);
    return status;
}
// Fixed: Helper function for safe METHOD_NEITHER access
NTSTATUS SafeCopyFromUser(
    PVOID KernelBuffer,
    PVOID UserBuffer,
    SIZE_T Length)
{
    NTSTATUS status = STATUS_SUCCESS;

    __try {
        // Verify address is in user space and accessible
        ProbeForRead(UserBuffer, Length, sizeof(UCHAR));

        // Copy with exception handling
        RtlCopyMemory(KernelBuffer, UserBuffer, Length);
    }
    __except (EXCEPTION_EXECUTE_HANDLER) {
        status = GetExceptionCode();
    }

    return status;
}

NTSTATUS SafeCopyToUser(
    PVOID UserBuffer,
    PVOID KernelBuffer,
    SIZE_T Length)
{
    NTSTATUS status = STATUS_SUCCESS;

    __try {
        ProbeForWrite(UserBuffer, Length, sizeof(UCHAR));
        RtlCopyMemory(UserBuffer, KernelBuffer, Length);
    }
    __except (EXCEPTION_EXECUTE_HANDLER) {
        status = GetExceptionCode();
    }

    return status;
}

CVE Examples

  • CVE-2006-2373: Windows file-sharing protocol driver allowed arbitrary code execution via METHOD_NEITHER IOCTL.
  • CVE-2009-0686: Anti-virus product vulnerable to privilege escalation through improper address validation.
  • CVE-2009-0824: DVD software crash via unvalidated IOCTL addresses.
  • CVE-2008-5724: Personal firewall allowed SYSTEM privilege escalation via improper IOCTL handling.
  • CVE-2007-5756: Packet-capturing software array index error from unvalidated IOCTL input.

References

  1. MITRE Corporation. "CWE-781: Improper Address Validation in IOCTL with METHOD_NEITHER I/O Control Code." https://cwe.mitre.org/data/definitions/781.html
  2. Microsoft. "Buffer Descriptions for I/O Control Codes." Windows Driver Documentation.
  3. Microsoft. "Using Neither Buffered Nor Direct I/O." Windows Driver Documentation.