Catching Temporal Corruption Across Reuse

CuSafe's temporal-safety problem begins where the spatial check ends. A stale pointer may still contain a plausible address, and that address may even fall inside a currently allocated range. This chapter follows what happens when a global buffer is freed or a local function returns, then explains how CuSafe uses shared buffer invalidation, stack epochs, and randomized virtual-address identity to make reuse visible. The mechanisms are different, but the judgment is the same: is this pointer still naming the live object that made it valid?

That question matters because use-after-free is not necessarily an out-of-bounds access. A pointer can be in range for the wrong object. Temporal safety therefore requires state that changes when an allocation ends and identity information that makes a later object distinguishable from an earlier one. CuSafe supplies both, while acknowledging bounded counters and probabilistic collision limits.

Invalidation is necessary but not enough

For a global allocation, CuSafe has an explicit lifecycle event. The program allocates the buffer through a runtime API and later releases it through cudaFree. CuSafe instruments that release path so the exact bound stored at the beginning of the buffer is cleared. The same metadata that once proved the buffer had a valid size now records that it no longer has a live extent. A later access through an old pointer encounters an invalid bound rather than receiving permission merely because the address still exists in the device address space.

Local memory reaches the same end state through a different trigger. A local buffer is tied to a function's execution and has no ordinary explicit deallocation call. CuSafe invalidates its metadata when the function exits. The transition is therefore scope-based rather than API-based: the object becomes unavailable when its allocation function returns, even if a pointer to it has escaped into a caller or another part of the computation.

The distinction between these triggers is operationally important. A global free is an event the runtime hook can intercept at a named boundary. A local return is a control-flow transition observed through the instrumented execution of the thread. Treating both as “free” would hide the reason the metadata must change and could leave a local object apparently live simply because no explicit release function was called.

The buffer is the right place to record this transition because pointers can be copied. Suppose a program makes two pointer values refer to one global allocation and then frees the allocation. Clearing only one pointer would leave the other copy unchanged. A shared buffer-side bound gives both pointers the same liveness fact to encounter. Invalidation is consequently visible to all uses that still lead back to that buffer metadata, not only to the pointer instance that happened to be present at the free site.

This is a meaningful difference from a pointer-only invalidation scheme. If validity exists solely in the pointer, updating one representation cannot automatically update copies made earlier. CuSafe does not need to find and rewrite every pointer because it leaves the allocation's shared description in the buffer. The copied pointer still carries a route to that description; the description now says that the object has no usable extent.

But clearing the bound does not solve every temporal problem. Memory is reused. A stack location that held one local buffer can later hold a variable from another function. A virtual address released by one global allocation can later be assigned to a new global buffer.