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

ImpactDetails
IntegrityScope: Integrity

Modify Application Data - Attackers can invoke IOCTL functionality to modify memory or system state.
ConfidentialityScope: Confidentiality

Read Application Data - Exposed IOCTLs may allow reading sensitive kernel or hardware data.
AvailabilityScope: Availability

DoS: Crash - Malicious IOCTL calls can crash the system.
Access ControlScope: 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

  1. MITRE Corporation. "CWE-782: Exposed IOCTL with Insufficient Access Control." https://cwe.mitre.org/data/definitions/782.html
  2. Microsoft. "Controlling Device Access." Windows Driver Documentation.
  3. Microsoft. "Security Descriptors for Device Objects." Windows Driver Documentation.