IOCTL (I/O Control Codes)
The IOCTL mechanism provides a way for user‑mode applications to send device‑specific commands to a driver. An IOCTL request is composed of a 32‑bit control code that encodes the device type, function, method, and access rights.
Windows defines a set of standard IOCTL codes; drivers can also define custom codes using the CTL_CODE
macro.
IOCTL Syntax
The macro used to define an IOCTL code is:
#define CTL_CODE( DeviceType, Function, Method, Access ) ( \
((DeviceType) << 16) | ((Access) << 14) | ((Function) << 2) | (Method) )
Key components:
Component | Size | Description |
---|---|---|
DeviceType | 16 bits | Identifies the class of device. |
Access | 2 bits | Specifies required access (FILE_ANY_ACCESS, FILE_READ_ACCESS, FILE_WRITE_ACCESS). |
Function | 12 bits | Driver‑specific function number (0‑4095). |
Method | 2 bits | How data is passed (METHOD_BUFFERED, METHOD_IN_DIRECT, METHOD_OUT_DIRECT, METHOD_NEITHER). |
Defining Custom IOCTL Codes
Typical driver header:
#include <winternl.h>
#define FILE_DEVICE_EXAMPLE 0x00008000
#define IOCTL_EXAMPLE_GET_INFO CTL_CODE(FILE_DEVICE_EXAMPLE, 0x800, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define IOCTL_EXAMPLE_SET_DATA CTL_CODE(FILE_DEVICE_EXAMPLE, 0x801, METHOD_IN_DIRECT, FILE_WRITE_ACCESS)
Example Usage (C++)
Sending an IOCTL from user mode:
#include <windows.h>
#include <iostream>
int main()
{
HANDLE hDevice = CreateFileW(L"\\\\.\\ExampleDevice",
GENERIC_READ | GENERIC_WRITE,
0, nullptr,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
nullptr);
if (hDevice == INVALID_HANDLE_VALUE) {
std::cerr << "Failed to open device: " << GetLastError() << std::endl;
return 1;
}
DWORD dummy = 0;
BOOL result = DeviceIoControl(hDevice,
IOCTL_EXAMPLE_GET_INFO,
nullptr, 0,
&dummy, sizeof(dummy),
nullptr,
nullptr);
if (!result) {
std::cerr << "IOCTL failed: " << GetLastError() << std::endl;
} else {
std::cout << "IOCTL succeeded, value: " << dummy << std::endl;
}
CloseHandle(hDevice);
return 0;
}
Remarks
- Always validate input buffers in kernel mode to prevent security vulnerabilities.
- Prefer
METHOD_BUFFERED
for simple data exchange; use direct I/O for large data transfers. - Use
IOCTL_CODE
macros consistently to avoid overlapping function numbers.