Uninitialised memory disclosures

Uninitialised memory disclosures

TL;DR: A kernel routine allocates a buffer, fills only some fields, and copies the whole thing back to user mode — leftover heap or stack contents (often kernel pointers) leak across the boundary and hand the attacker a KASLR bypass.

What it is

Kernel components frequently allocate output buffers, populate part of them, and memcpy the result to user space via Irp->AssociatedIrp.SystemBuffer or directly through ProbeForWrite + raw user pointer. If padding bytes, unused struct fields, or alignment slots are never zero-initialised, the buffer carries whatever residual data the underlying pool/stack page held before reuse — typically prior kernel pointers, encryption keys, or path strings. j00ru’s long-running research found dozens of such bugs in win32k, NT executive, and third-party drivers.

Preconditions / where it applies

  • IOCTL or NT-syscall that returns a struct with sized output (OutputBufferLength ≥ struct size)
  • Struct contains unused fields, alignment padding, union members, or trailing reserved bytes
  • Allocator is not zero-on-free — kernel pool, stack, and contiguous physical allocations all qualify; ExAllocatePoolZero / RtlZeroMemory absent

Technique

Classic bug shape (simplified):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
typedef struct {
    ULONG Length;
    ULONG Flags;
    UCHAR Reserved[12];      // padding — never written
    HANDLE TargetHandle;
} OUTPUT;

NTSTATUS Handler(PIRP Irp) {
    OUTPUT* out = (OUTPUT*)Irp->AssociatedIrp.SystemBuffer;
    out->Length = sizeof(*out);
    out->Flags  = 0;
    out->TargetHandle = SomeHandle;
    // Reserved[] left at whatever was in the pool block
    Irp->IoStatus.Information = sizeof(*out);
    return STATUS_SUCCESS;
}

Attacker steps:

  1. Repeatedly call the IOCTL, recording the Reserved[] bytes per call
  2. Identify recognisable pointers (kernel-VA range 0xFFFF8…) or known sentinel values
  3. Cross-reference offsets with leaked function names to derive ntoskrnl base
  4. Feed the leak into kaslr-bypass follow-up — token swap, ROP, etc.

Common sources beyond IOCTLs:

  • NtQuerySystemInformation info classes returning struct arrays
  • ALPC port message replies leaking server-side stack
  • IRP_MJ_QUERY_INFORMATION paths that return union members for a non-active type
  • Graphics drivers returning shader compilation telemetry

Tooling: Microsoft’s PageHeap + Special Pool surface uninitialised reads as STATUS_DATATYPE_MISALIGNMENT/poison page faults; KFuzz harnesses with KMSAN-style shadow memory catch them under fuzz.

Detection and defence

  • Always allocate with ExAllocatePool2(POOL_FLAG_NON_PAGED | POOL_FLAG_ZERO_INIT, ...) (Win 10 2004+) or explicitly RtlZeroMemory before populating
  • Pack output structs with no padding (__pragma(pack(1))) or zero-init reserved fields
  • Driver Verifier with Force IRQL Checking + Special Pool catches some leaks; combine with KAFL/WTF fuzzers
  • Code review: any kernel function that takes OutputBufferLength from user and writes a struct must zero-init or assert full coverage

References

Related: kaslr-bypass, double-fetch, kernel-objects-and-irps, type-confusion-kernel