Exposed IOCTL with Insufficient Access Control
Description
Exposed IOCTL with Insufficient Access Control is a kernel-mode vulnerability where a device driver implements an IOCTL (Input/Output Control) interface with functionality that should be restricted to privileged users, but fails to properly enforce access control. IOCTLs provide a way for user-mode applications to communicate with kernel-mode drivers. When sensitive operations (like memory access, privilege modification, or hardware control) are exposed through IOCTLs without proper access restrictions, any user or process that can open a handle to the device can invoke these privileged operations.
Risk
Insufficiently protected IOCTLs are a severe vulnerability in kernel drivers. Attackers can exploit exposed IOCTLs to achieve various malicious goals: reading or writing arbitrary memory, disabling security software, escalating privileges to SYSTEM level, causing denial of service, or accessing sensitive hardware. Developers often assume only trusted applications will communicate with drivers, leading to inadequate validation and access control. Since IOCTLs run in kernel mode, successful exploitation gives attackers the highest level of system access. This vulnerability class has been exploited in numerous real-world attacks, including privilege escalation in anti-virus products and security tools.
Solution
Implement proper access control for device objects and IOCTLs. In Windows, use security descriptors on device objects to restrict which users can open handles. Set the FILE_DEVICE_SECURE_OPEN flag and create symbolic links in protected namespaces. Check caller privileges within IOCTL handlers using IoIs32bitProcess(), SeSinglePrivilegeCheck(), or similar functions. Minimize the attack surface by only exposing necessary functionality. Validate all input parameters thoroughly, even when access control is in place. Consider using separate device objects with different access levels for privileged and unprivileged operations.
Common Consequences
| Impact | Details |
|---|---|
| Integrity | Scope: Integrity Modify Application Data - Attackers can invoke IOCTL functionality to modify memory or system state. |
| Confidentiality | Scope: Confidentiality Read Application Data - Exposed IOCTLs may allow reading sensitive kernel or hardware data. |
| Availability | Scope: Availability DoS: Crash - Malicious IOCTL calls can crash the system. |
| Access Control | Scope: Access Control Gain Privileges - Attackers can escalate to kernel/SYSTEM privileges through exposed functionality. |
Example Code
Vulnerable Code
// Vulnerable: Device created with permissive access control
#include <ntddk.h>
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
NTSTATUS status;
PDEVICE_OBJECT deviceObject;
UNICODE_STRING deviceName = RTL_CONSTANT_STRING(L"\\Device\\VulnerableDriver");
UNICODE_STRING symLink = RTL_CONSTANT_STRING(L"\\DosDevices\\VulnerableDriver");
// Vulnerable: Default security allows all users to open device
status = IoCreateDevice(
DriverObject,
0,
&deviceName,
FILE_DEVICE_UNKNOWN,
0, // Vulnerable: No FILE_DEVICE_SECURE_OPEN flag
FALSE,
&deviceObject
);
if (!NT_SUCCESS(status)) return status;
// Vulnerable: Symbolic link in \\DosDevices accessible to all users
status = IoCreateSymbolicLink(&symLink, &deviceName);
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = VulnerableDeviceControl;
DriverObject->MajorFunction[IRP_MJ_CREATE] = CreateClose;
DriverObject->MajorFunction[IRP_MJ_CLOSE] = CreateClose;
return STATUS_SUCCESS;
}
// Vulnerable: IOCTL handler with no access control checks
NTSTATUS VulnerableDeviceControl(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);
NTSTATUS status = STATUS_SUCCESS;
switch (IrpSp->Parameters.DeviceIoControl.IoControlCode)
{
case IOCTL_READ_KERNEL_MEMORY:
// Vulnerable: Any user can read kernel memory
status = ReadKernelMemory(Irp);
break;
case IOCTL_WRITE_KERNEL_MEMORY:
// Vulnerable: Any user can write kernel memory
status = WriteKernelMemory(Irp);
break;
case IOCTL_DISABLE_PROTECTION:
// Vulnerable: Any user can disable security features
status = DisableSecurityFeature(Irp);
break;
}
Irp->IoStatus.Status = status;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return status;
}
// Vulnerable: Trusting caller without verification
NTSTATUS VulnerableTrustingHandler(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);
PIOCTL_REQUEST request = Irp->AssociatedIrp.SystemBuffer;
switch (IrpSp->Parameters.DeviceIoControl.IoControlCode)
{
case IOCTL_PRIVILEGED_OPERATION:
// Vulnerable: Assumes only privileged callers will invoke this
// No verification of caller identity or privileges
PerformPrivilegedOperation(request);
break;
}
return STATUS_SUCCESS;
}
Fixed Code
// Fixed: Device created with proper security descriptor
#include <ntddk.h>
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
NTSTATUS status;
PDEVICE_OBJECT deviceObject;
UNICODE_STRING deviceName = RTL_CONSTANT_STRING(L"\\Device\\SecureDriver");
UNICODE_STRING symLink = RTL_CONSTANT_STRING(L"\\DosDevices\\SecureDriver");
// Fixed: Use explicit security descriptor
UNICODE_STRING sddl = RTL_CONSTANT_STRING(
L"D:P(A;;GA;;;SY)(A;;GA;;;BA)" // SYSTEM and Admin only
);
status = IoCreateDeviceSecure(
DriverObject,
0,
&deviceName,
FILE_DEVICE_UNKNOWN,
FILE_DEVICE_SECURE_OPEN, // Fixed: Require open checks
FALSE,
&sddl, // Fixed: Security descriptor restricts access
NULL,
&deviceObject
);
if (!NT_SUCCESS(status)) return status;
// Alternative: Create in protected namespace
// L"\\Device\\VulnerableDriver" without symbolic link
status = IoCreateSymbolicLink(&symLink, &deviceName);
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = SecureDeviceControl;
DriverObject->MajorFunction[IRP_MJ_CREATE] = SecureCreate;
DriverObject->MajorFunction[IRP_MJ_CLOSE] = CreateClose;
return STATUS_SUCCESS;
}
// Fixed: IOCTL handler with privilege checks
NTSTATUS SecureDeviceControl(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);
NTSTATUS status = STATUS_SUCCESS;
switch (IrpSp->Parameters.DeviceIoControl.IoControlCode)
{
case IOCTL_READ_KERNEL_MEMORY:
case IOCTL_WRITE_KERNEL_MEMORY:
// Fixed: Verify caller has required privileges
if (!IsCallerPrivileged()) {
status = STATUS_ACCESS_DENIED;
break;
}
status = HandleKernelMemoryAccess(Irp);
break;
case IOCTL_REGULAR_OPERATION:
// Less privileged operations still available
status = HandleRegularOperation(Irp);
break;
}
Irp->IoStatus.Status = status;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return status;
}
// Fixed: Helper to check caller privileges
BOOLEAN IsCallerPrivileged(void)
{
SECURITY_SUBJECT_CONTEXT SubjectContext;
BOOLEAN isPrivileged = FALSE;
SeCaptureSubjectContext(&SubjectContext);
// Check for specific privilege
isPrivileged = SePrivilegeCheck(
&SeLoadDriverPrivilege,
&SubjectContext,
UserMode
);
SeReleaseSubjectContext(&SubjectContext);
return isPrivileged;
}
// Fixed: Create handler that can perform additional checks
NTSTATUS SecureCreate(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);
NTSTATUS status = STATUS_SUCCESS;
// Fixed: Additional access validation on open
// Can check process name, token privileges, etc.
if (!IsCallerAllowed()) {
status = STATUS_ACCESS_DENIED;
}
Irp->IoStatus.Status = status;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return status;
}
// Fixed: Separate devices for different privilege levels
NTSTATUS CreateDualDevices(PDRIVER_OBJECT DriverObject)
{
// Privileged device - restricted access
UNICODE_STRING sddlPriv = RTL_CONSTANT_STRING(
L"D:P(A;;GA;;;SY)(A;;GA;;;BA)"
);
IoCreateDeviceSecure(..., L"\\Device\\MyDriverPriv", &sddlPriv, ...);
// Public device - limited functionality
UNICODE_STRING sddlPublic = RTL_CONSTANT_STRING(
L"D:P(A;;GA;;;SY)(A;;GA;;;BA)(A;;GRGW;;;WD)" // World can R/W
);
IoCreateDeviceSecure(..., L"\\Device\\MyDriverPublic", &sddlPublic, ...);
// Route IOCTLs based on which device was opened
}
CVE Examples
- CVE-2009-2208: OS failed to enforce permissions on network settings IOCTL, allowing unprivileged changes.
- CVE-2008-3831: Direct rendering manager driver lacked IOCTL access restrictions.
- CVE-2008-3525: IOCTL missing required capability checks allowed privilege escalation.
- CVE-2008-0322: Insecure device permissions enabled arbitrary memory overwriting via IOCTL.
- CVE-2007-4277: Anti-virus product with weak device permissions exposed buffer overflow.
- CVE-1999-0728: Unprivileged users could disable keyboard/mouse through privileged IOCTL.
References
- MITRE Corporation. "CWE-782: Exposed IOCTL with Insufficient Access Control." https://cwe.mitre.org/data/definitions/782.html
- Microsoft. "Controlling Device Access." Windows Driver Documentation.
- Microsoft. "Security Descriptors for Device Objects." Windows Driver Documentation.