Temporal memory safety is the property that a memory object is accessed only during its valid lifetime: after allocation and before deallocation. A temporal memory safety violation happens when a program keeps using a reference after the referenced object is no longer alive.
The most representative example is [[Use-After-Free]], where a pointer remains reachable after the heap object it points to has been freed.
Memory safety has two commonly separated dimensions:
Temporal violations are about lifetime, not just address range. A stale pointer may still contain an address that looks valid, especially after the allocator reuses the same address for another object. That is what makes these bugs dangerous: the program can appear to access allocated memory while actually using an old authority to a different lifetime.
struct item *p = malloc(sizeof(*p));
free(p);
struct item *q = malloc(sizeof(*q));
p->field = attacker_controlled_value;
If q reuses the same address that p used to point to, the dangling pointer
p can corrupt the new object. Depending on what the new object represents,
this can lead to control-flow hijacking, privilege escalation, information
leakage, or logic corruption.
C and C++ make pointer lifetime difficult to enforce because pointers can be copied, stored, cast, passed across module boundaries, and placed in many memory regions. When an object is freed, the allocator usually knows that the object is dead, but it does not know whether every pointer to the old address has also disappeared.
The difficult question is:
Is there any remaining reachable pointer to this freed object?
Answering this question precisely is expensive because the pointer may be in the stack, heap, globals, registers, or memory acquired directly through system calls. In multithreaded programs, the answer can also change while a protection mechanism is checking memory.
A temporal memory safety violation becomes exploitable when an attacker can influence at least one of these steps:
The allocator’s reuse policy is therefore central. Reusing a freed address too early can turn a dangling pointer into a powerful primitive.
Temporal memory safety work can be organized along two axes.
The first axis is the defended event:
The second axis is the defended resource:
These axes explain why the same bug class has many families of defenses. Some systems try to make every stale access fail. Others try to ensure that stale pointers never point to newly allocated objects. Still others try to remove the stale pointers themselves.
Access-validation schemes check whether a pointer is still authorized when it is dereferenced. Lock-and-key approaches attach metadata to both objects and pointers, then compare them on access. This can catch stale pointers directly, but the cost is tied to pointer dereference frequency.
Representative works include CETS, PTAuth, PACMem, xTag, Fat Pointers for C, RTT-UAF, and ViK. These systems differ in how they encode and protect metadata: compiler-managed metadata, pointer authentication, software pointer tagging, object identifiers, fat pointers, or reuse-time tracking. The common shape is that a stale pointer should fail a metadata check before it can be used as ordinary authority to memory.
Escape-tracking and pointer-nullification approaches try to maintain the set of locations that point to each object. When the object is freed, the system invalidates or nullifies those locations. This attacks the root cause directly, but it requires tracking pointer copies and maintaining potentially large metadata.
DangNull, FreeSentry, DangSan, CRCount, HeapExpo, CAMP, and Fast Pointer Nullification belong near this family. They vary in how they track references: explicit points-to sets, shadow/log-structured metadata, reference counts, compiler assistance, allocator cooperation, or faster invalidation paths. Their central challenge is coverage: a pointer may live in stack slots, heap objects, globals, registers, optimized compiler temporaries, or concurrently modified locations.
Delayed-reuse approaches quarantine freed memory and allow reuse only after the system believes no dangling pointer still refers to it. Mark-sweep-style systems scan memory for references to quarantined objects. If no reference is found, the allocator can safely recycle the memory.
This strategy shifts the problem from “check every access” to “decide when reuse is safe.” It can be practical, but it introduces tradeoffs in scanning cost, stop-the-world latency, memory usage, and allocation locality.
MarkUs and MineSweeper are representative drop-in mark-sweep approaches. They delay reuse and scan memory to decide which freed objects can be recycled. SwiftSweeper follows the same high-level safety goal, but focuses on removing stop-the-world sweeping from the critical path by using concurrent, batched, and adaptive reclamation.
One-time allocation avoids reusing virtual addresses after free. A dangling pointer may still exist, but it does not become a pointer to a newly allocated object. The downside is virtual address pressure, which matters for long-running programs or programs with intense allocation behavior.
Oscar, FFmalloc, and BUDAlloc are examples of virtual-address-oriented designs. Their shared intuition is simple: if the allocator never gives a freed virtual address to another object, a stale pointer cannot silently become authority to a new object. The hard part is making this practical under finite virtual address space, kernel metadata costs, and long-running workloads.
Page-level delayed reuse treats virtual pages as the unit of safe reuse. A runtime can detach physical pages from freed virtual pages, keep the virtual addresses quarantined, and later reuse only pages that are no longer referenced by reachable pointers.
This connects temporal memory safety with [[Page Table]] behavior: the system can separate virtual address reuse from physical memory reuse. It also motivates sweeping mechanisms such as [[Memory Sweeper]], where the runtime searches for remaining references before recycling memory.
Temporal safety defenses usually choose a point in the execution where they pay overhead:
No single strategy dominates every workload. Allocation-intensive programs are especially sensitive because delaying reuse can reduce locality and increase fragmentation. A useful defense has to preserve the safety property while keeping allocator behavior close enough to what real programs expect.
| Category | Representative Works | Main Idea | Main Pressure Point |
|---|---|---|---|
| Access validation | CETS, PTAuth, PACMem, xTag, Fat Pointers for C, RTT-UAF, ViK | Check metadata when a pointer is used | Per-dereference or metadata-propagation overhead |
| Pointer tracking / nullification | DangNull, FreeSentry, DangSan, CRCount, HeapExpo, CAMP, Fast Pointer Nullification | Track aliases and invalidate stale references | Complete and scalable pointer coverage |
| Mark-sweep delayed reuse | MarkUs, MineSweeper, SwiftSweeper, CHERIvoke | Reuse freed memory only after reachability/revocation checks | Scan cost, concurrency, stop-the-world latency |
| Virtual address management | Oscar, FFmalloc, BUDAlloc | Avoid or decouple virtual address reuse | Address-space pressure and kernel metadata costs |
| Allocator hardening | DieHard, DieHarder, FreeGuard, Guarder | Reduce exploitability through randomized or hardened allocation | Usually probabilistic mitigation, not full temporal safety |
#memory #security