Many critical C/C++ deserializers (such as dedicated game-server tick processors, replay container parsers, and embedded telemetry routers) are deeply coupled to global engine singletons, thread-local allocators, and socket state machines. Spawning a full process per testcase yields < 5 execs/sec because 99% of CPU cycles are spent initializing subsystems unrelated to the target parser function.
Deterministic Snapshot Fuzzing (pioneered by frameworks such as 0vercl0k/wtf and hardware-assisted/page-table Copy-on-Write engines) solves this by capturing a complete architectural state snapshot (CPU registers + physical memory pages) immediately at the entry point of the target parser function (e.g., CNetChan::ProcessMessages or a container chunk decoder).
Instead of restoring gigabytes of RAM after each fuzzing iteration, a snapshot harness marks all writable guest/process pages as read-only in the shadow page table and records page-faulting writes in a bounded dirty-page bitmap:
Snapshot Harness Lifecycle (10,000+ execs/sec per core):
+-----------------------------------------------------------------------+
| 1. Capture Baseline Snapshot at Parser Entry (RIP = ParsePacketEntry) |
| - RCX = this (NetChannel / ParserContext*) |
| - RDX = const uint8_t* input_buf |
| - R8 = size_t input_len |
+-----------------------------------------------------------------------+
|
v
+-----------------------------------------------------------------------+
| 2. Per-Iteration In-Memory Mutation Injection |
| - Write mutated testcase into pre-mapped page at RDX |
| - Set R8 = testcase_size |
| - Execute until Parser Return Address or Guard Page Fault |
+-----------------------------------------------------------------------+
|
v
+-----------------------------------------------------------------------+
| 3. O(DirtyPages) Deterministic State Rollback |
| - Restore only the 4-15 dirty 4 KiB pages touched during the call |
| - Restore GPRs/XMM state & advance coverage bitmap |
+-----------------------------------------------------------------------+
Without source-level AddressSanitizer (ASAN) instrumentation, small heap out-of-bounds reads or writes (CWE-125 / CWE-787) inside a custom slab allocator may not trigger an immediate hardware page fault. To achieve ASAN-equivalent sensitivity in binary-only snapshot harnesses:
// Hook custom allocator wrappers (malloc / realloc / CUtlMemory::Grow) inside the snapshot
// to place each allocation at the end of a 4 KiB page followed by an unmapped guard page:
void* GuardedPageAlloc(size_t requested_size) {
size_t aligned_pages = (requested_size + 0xFFF) & ~0xFFFULL;
uint8_t* base = AllocateVirtualPages(aligned_pages + 0x1000);
MarkPageUnmapped(base + aligned_pages); // Immediate #PF on 1-byte OOB read/write
return base + aligned_pages - requested_size;
}
By pairing static structure recovery in IDA Pro (identifying exact field offsets, uint32_t count * sizeof(Elem) multiplication sites, and signedness transitions) with guarded page allocations inside a deterministic snapshot loop, security auditors can systematically verify parser bounds invariants and author precise binary or source-level remediation patches.
When binary RPC handlers deserialize multi-dimensional descriptors alongside raw payload buffers, pointer arithmetic checks of the form base + offset + size <= end are vulnerable to 64-bit unsigned wraparound. Defensive deserializers must enforce subtraction-based bounds invariants:
// Enforce overflow-safe containment before indexing or copying tensor/segment buffers:
bool VerifySliceBounds(uintptr_t data_ptr, uintptr_t region_start, uintptr_t region_end,
size_t byte_offset, size_t byte_size) {
if (data_ptr < region_start || data_ptr > region_end) return false;
size_t avail = region_end - data_ptr;
if (byte_offset > avail || byte_size > avail - byte_offset) return false;
return true;
}