VNX-781: Improper Address Validation in IOCTL with METHOD_NEITHER I/O Control Code
Windows driver reads of a METHOD_NEITHER Type3InputBuffer, Irp->UserBuffer or KMDF unsafe user buffer with no ProbeForRead, ProbeForWrite, MmProbeAndLockPages or WdfRequestProbeAndLock call nearby.
| Field | Value |
|---|---|
| Rule ID | VNX-781 |
| Severity | Medium |
| SARIF level | warning |
| Kind | sast |
| Languages | C, C++ |
| CWE | CWE-781 |
| CAPEC | CAPEC-97 |
| ATT&CK | T1557 |
| CWSS vector | CWSS:1.0/TI:M/AP:RU/AL:S/IC:N/FC:LT/RP:RU/RL:S/AV:L/AS:N/IN:A/SC:A/BI:M/DI:H/EX:M/EC:N/P:C |
| Tags | windows-driver ioctl method-neither cwe-781 |
| Source | internal/sast/rules/vnx-781.rego |
Overview
This rule flags C and C++ Windows driver code that reads Parameters.DeviceIoControl.Type3InputBuffer or Irp->UserBuffer (the raw user-mode pointers of a METHOD_NEITHER IOCTL), or calls KMDF WdfRequestRetrieveUnsafeUserInputBuffer/OutputBuffer, when no ProbeForRead, ProbeForWrite, MmProbeAndLockPages or WdfRequestProbeAndLockUserBufferForRead/ForWrite call appears in the 30 lines above or 40 below. Buffered and direct-I/O buffers (SystemBuffer, MdlAddress) and user-mode DeviceIoControl callers are not reported; whether the probe covers the same pointer and length is not checked. Without validation, a malicious user-mode process can supply kernel-space addresses, causing the driver to read from or write to kernel memory under attacker control. This maps to CWE-781: Improper Address Validation in IOCTL with METHOD_NEITHER I/O Control Code.
Why This Matters
Kernel drivers operate at ring 0 with full access to all memory. An IOCTL handler that accepts a user-provided address without validation can be exploited to read kernel memory (information disclosure including KASLR bypass) or write to kernel memory (privilege escalation to SYSTEM). This vulnerability class has been extensively exploited by local privilege escalation exploits.
What Gets Flagged
// FLAGGED: IOCTL handler without ProbeForRead/ProbeForWrite
NTSTATUS DriverDispatch(PDEVICE_OBJECT DeviceObject, PIRP Irp) {
PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp);
ULONG code = stack->Parameters.DeviceIoControl.IoControlCode;
if (code == IOCTL_MY_CODE) { // METHOD_NEITHER
PVOID inputBuffer = stack->Parameters.DeviceIoControl.Type3InputBuffer;
// MISSING: ProbeForRead(inputBuffer, length, alignment)
RtlCopyMemory(localBuf, inputBuffer, length); // dangerous
}
}
Remediation
// SAFE: validate user-space addresses before access
NTSTATUS DriverDispatch(PDEVICE_OBJECT DeviceObject, PIRP Irp) {
PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp);
PVOID inputBuffer = stack->Parameters.DeviceIoControl.Type3InputBuffer;
ULONG inputLength = stack->Parameters.DeviceIoControl.InputBufferLength;
__try {
ProbeForRead(inputBuffer, inputLength, sizeof(UCHAR));
RtlCopyMemory(localBuf, inputBuffer, inputLength);
} __except (EXCEPTION_EXECUTE_HANDLER) {
return GetExceptionCode();
}
}
Use METHOD_BUFFERED or METHOD_IN_DIRECT/METHOD_OUT_DIRECT IOCTL types where the I/O Manager handles buffering and validation automatically.
References
Suppressing this rule
Put a nosec comment on the line, or on line 1 to cover the whole file. Use the comment marker of the file’s language (#, //, -- or ;):
# nosec vnx-781
// nosec vnx-781
Or record an ignore rule: vulnetix ignore add --rule vnx-781 --reason "...". See Suppressing findings.