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+memcpyto a local, or uses__try/__exceptonly 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.
- Thread A:
DeviceIoControl(..., &req, ...)in a tight loop. - Thread B: pinned to a different core, flips
req->sizebetween a small (passes check) and large (overflowslocal) value as fast as possible. - Eventually fetch1 reads small and fetch2 reads large → controlled OOB write.
Reliability tricks.
- Pin threads with
SetThreadAffinityMaskso 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_si32to 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 uselocal_sizeexclusively. - Compile drivers with the
__usercopyannotation / 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
- Jurczyk & Coldwind — Bochspwn — original double-fetch survey
- Microsoft: probing pointers — safe user-buffer handling
- HackTricks — Windows binary exploitation — adjacent techniques
See also: type-confusion-kernel, use-after-free-kernel, kernel-objects-and-irps.