Double fetch / TOCTOU

Double fetch / TOCTOU

TL;DR: A kernel function reads the same user-mode value twice (size, length, pointer); a second thread flips it between the reads so validation sees one value and the copy uses another.

What it is

A time-of-check / time-of-use bug specific to the kernel/user boundary. Driver code reads a field from a user buffer to validate it (size ≤ N, pointer in range), then re-reads the same field to perform the operation. Because user-mode memory is mutable from another thread, the attacker swaps the value between fetches, smuggling an out-of-range value past validation.

Preconditions / where it applies

  • Kernel routine accesses a user-mode pointer multiple times without first copying to a local kernel buffer. Classic pattern: if (user->Length < MAX) memcpy(kbuf, user->Data, user->Length);
  • Attacker has a second thread on a different CPU to race the kernel; SMP machine.
  • Driver does not wrap accesses in ProbeForRead+memcpy to a local, or uses __try/__except only for fault handling, not coherence.

Technique

Pattern.

1
2
3
4
5
6
// Vulnerable kernel pseudo-code
NTSTATUS IoctlHandler(PIRP irp, PIO_STACK_LOCATION isl) {
    PUSER_REQ req = isl->Parameters.DeviceIoControl.Type3InputBuffer;
    if (req->size > MAX_SIZE) return STATUS_INVALID_PARAMETER;  // first fetch
    RtlCopyMemory(local, req->data, req->size);                  // second fetch
}

Race.

  1. Thread A: DeviceIoControl(..., &req, ...) in a tight loop.
  2. Thread B: pinned to a different core, flips req->size between a small (passes check) and large (overflows local) value as fast as possible.
  3. Eventually fetch1 reads small and fetch2 reads large → controlled OOB write.

Reliability tricks.

  • Pin threads with SetThreadAffinityMask so racing cores match.
  • Use a memory range that crosses a page boundary; the flip happens on the second page mid-copy.
  • Cache-line bouncing: place the racing field on its own cache line and write with _mm_stream_si32 to encourage propagation.
  • Some authors use a single-fetch read followed by an unmapped page on the second access — kernel takes the exception, exposing a different bug.

Outcome. Most double fetches give a controlled-size OOB write into a kernel buffer = stack or pool overflow. See kernel-stack-overflow and arbitrary-read-write-primitives for the follow-on.

Detection and defence

  • Capture-first pattern: __try { ProbeForRead(...); local_size = *user_size; } __except (...) { ... } then use local_size exclusively.
  • Compile drivers with the __usercopy annotation / Clang’s __attribute__((noderef)) to make repeated user-pointer access a build error.
  • Static analyzers (CodeQL cpp/kernel/double-fetch, SDV, KMDF analysis) catch many of these.
  • KASAN / Driver Verifier with concurrent IRP testing can sometimes expose races at test time.

References

See also: type-confusion-kernel, use-after-free-kernel, kernel-objects-and-irps.