Skip to content

Patch Patterns

What Microsoft's kernel fixes look like for each bug class -- with before/after pseudocode and AutoPiff rule mapping.

Why Patches Are Predictable

Kernel patches tend to be surgical. A typical Patch Tuesday fix adds 5-20 lines of validation code without restructuring the surrounding function. This conservatism is intentional: Microsoft needs to fix the vulnerability without introducing regressions or changing driver behavior for legitimate callers. The result is that patches follow a small number of recognizable shapes, each corresponding to a specific vulnerability class.

Recognizing these shapes is a practical skill. When you diff a binary update, the patch pattern tells you what the vulnerability was, often before the CVE details are public. When you audit a driver, knowing the fix shapes tells you what to look for in the original code. And when you build automated analysis (like AutoPiff), the patterns become detection rules that can classify patches at scale.

This page catalogs the 7 most common patch shapes across the KernelSight corpus. Each pattern maps to AutoPiff detection rules and references the CVEs where Microsoft applied that specific fix.

Patch Categories

1. Added Bounds Check

Pattern: A new if (len > max) return STATUS_... guard appears before a copy, allocation, or pointer arithmetic operation.

Where it appears: Buffer overflow and out-of-bounds access fixes. This is the most common patch type in the corpus, reflecting the dominance of unvalidated input as a root cause (see Secure Driver Anatomy, anti-pattern 1).

Before:

// No validation -- copies whatever length the caller provides
RtlCopyMemory(dst, src, header->DataSize);

After:

if (header->DataSize > AllocationSize - FIELD_OFFSET(MY_STRUCT, Data))
    return STATUS_INVALID_PARAMETER;
RtlCopyMemory(dst, src, header->DataSize);

CVE examples:

2. Added Lock / Synchronization

Pattern: A spin lock, push lock, or InterlockedCompareExchange guard wraps a previously unprotected read-modify-write sequence on shared state.

Where it appears: Race condition and some UAF fixes. The second most common patch type, reflecting the prevalence of synchronization bugs in multi-threaded kernel code.

Before:

// No lock -- another thread can free Object between check and use
if (Object->State == ACTIVE) {
    ProcessObject(Object);
}

After:

KeAcquireSpinLock(&Object->Lock, &OldIrql);
if (Object->State == ACTIVE) {
    ProcessObject(Object);
}
KeReleaseSpinLock(&Object->Lock, OldIrql);

CVE examples:

  • CVE-2024-38106 -- ntoskrnl lock added around VslpEnterIumSecureMode
  • CVE-2026-21241 -- afd.sys spinlock scope extended to cover notification teardown
  • CVE-2025-62215 -- ntoskrnl synchronization for double-free prevention

3. Added Probe / Capture

Pattern: ProbeForRead/ProbeForWrite followed by a single-copy capture replaces direct dereference of a user-mode pointer. The kernel validates and captures user data once instead of reading it multiple times.

Where it appears: Double-fetch and TOCTOU fixes. This pattern eliminates the race window by ensuring the kernel only reads from user memory once, then operates on its own copy.

Before:

// Reads user buffer twice -- attacker can change value between reads
if (UserBuffer->Length <= MAX_LEN) {
    RtlCopyMemory(dst, UserBuffer->Data, UserBuffer->Length);  // re-read
}

After:

__try {
    ProbeForRead(UserBuffer, sizeof(MY_REQUEST), __alignof(MY_REQUEST));
    CapturedLength = UserBuffer->Length;  // single capture
} __except (EXCEPTION_EXECUTE_HANDLER) {
    return GetExceptionCode();
}
if (CapturedLength <= MAX_LEN) {
    RtlCopyMemory(dst, src, CapturedLength);  // uses captured value
}

CVE examples:

  • CVE-2024-30088 -- ntoskrnl probe-and-capture for security attributes
  • CVE-2024-11616 -- epdlpdrv.sys single-copy capture replacing double-fetch

4. Added IOCTL Access Control

Pattern: The patch adds caller identity checks to an IOCTL dispatcher: SeSinglePrivilegeCheck, PsGetCurrentProcessSessionId, or replaces IoCreateDevice with IoCreateDeviceSecure and a restrictive SDDL string.

Where it appears: Authorization bypass fixes. Common in inbox drivers where the original developer did not anticipate adversarial callers, and dominant in BYOVD driver patches where the fix is restricting who can reach the dangerous functionality.

Before:

// Any process can open the device and send this IOCTL
case IOCTL_DANGEROUS_OPERATION:
    DoTheDangerousThing(Irp);
    break;

After:

case IOCTL_DANGEROUS_OPERATION:
    if (!SeSinglePrivilegeCheck(SeLoadDriverPrivilege,
                                Irp->RequestorMode)) {
        status = STATUS_ACCESS_DENIED;
        break;
    }
    DoTheDangerousThing(Irp);
    break;

CVE examples:

5. Added Reference Counting

Pattern: InterlockedIncrement/InterlockedDecrement pairs appear around object acquisition and release paths. The object is only freed when the reference count reaches zero, preventing premature deallocation.

Where it appears: UAF fixes where the root cause is freeing an object while other code paths still hold stale pointers to it.

Before:

// Free without checking if anyone still holds a reference
RemoveFromList(Object);
ExFreePoolWithTag(Object, TAG);

After:

RemoveFromList(Object);
if (InterlockedDecrement(&Object->RefCount) == 0) {
    ExFreePoolWithTag(Object, TAG);
}

CVE examples:

6. Removed Dangerous Functionality

Pattern: An entire IOCTL handler, code path, or exported function is deleted. The patch does not fix the vulnerability; it removes the attack surface entirely.

Where it appears: BYOVD drivers after disclosure. Microsoft's Vulnerable Driver Blocklist entries also fall into this category, preventing the driver from loading at all. This is the bluntest patch pattern, and the only correct response when the functionality is dangerous by design. There is no safe way to expose physical memory mapping to userland.

CVE examples:

7. Added Type / Object Validation

Pattern: A type check, object signature validation, or runtime tag comparison appears before a pointer cast or object dereference. The patch rejects objects that do not match the expected type.

Where it appears: Type confusion fixes, primarily in win32k and Kernel Streaming drivers where objects from different categories share similar interfaces.

Before:

// Cast without checking -- wrong object type causes corruption
PWINDOW_OBJECT Window = (PWINDOW_OBJECT)Object;
Window->ExtraData = NewValue;

After:

if (Object->Type != OBJECT_TYPE_WINDOW) {
    return STATUS_OBJECT_TYPE_MISMATCH;
}
PWINDOW_OBJECT Window = (PWINDOW_OBJECT)Object;
Window->ExtraData = NewValue;

CVE examples:

AutoPiff Rule Mapping

AutoPiff detects these patch patterns automatically during binary diff analysis. Each pattern maps to one or more detection rules that fire when the corresponding code change is identified in a function diff.

Patch Pattern AutoPiff Rule(s) Example CVE
Added Bounds Check added_length_check, added_offset_bounds_check CVE-2024-49138
Added Lock / Sync added_spinlock_acquire, added_interlocked_op CVE-2024-38106
Added Probe / Capture added_probe_for_read, added_user_capture CVE-2024-30088
Added IOCTL Access Control added_access_check, modified_device_create CVE-2024-21338
Added Reference Counting added_ref_count, modified_object_free CVE-2024-30089
Removed Functionality removed_ioctl_handler, deleted_code_path CVE-2025-3464
Added Type Validation added_type_check, added_object_tag_check CVE-2022-21882

See AutoPiff Integration for setup and rule configuration.

Cross-References