What CULPA Proves—and What It Cannot

CULPA gives a concrete test of the claim that unsafe Rust is a proof obligation rather than a blind spot. The question is not whether the tool can find suspicious code, but how far its chain of contracts, memory regions, safeguards, and public reachability supports a trustworthy judgment. This final synthesis connects that model to the study’s results and limitations so a reader can decide what CULPA contributes, what remains unproved, and how unsafe abstractions should be handled when no single assurance method covers the whole problem.

Unsafe Rust is a responsibility boundary, not a missing safety model

The most defensible answer to the series’ central question begins with a distinction. Rust’s compiler still provides substantial guarantees in safe code. Ownership, borrowing, and lifetime rules constrain how references are created and used. unsafe does not erase those rules or declare the surrounding program meaningless. It permits operations whose extra conditions cannot be established from the ordinary type and control-flow information alone, then places responsibility for those conditions on an author, caller, implementer, or maintainer.

That responsibility becomes dangerous only when it remains implicit or is attached to the wrong state. An unsafe function can document that an index must be in bounds, a pointer must be aligned and non-null, or a region must contain initialized values. An unsafe block can assert that those conditions have been discharged. A safe wrapper can hide the low-level operation if it establishes the conditions for every input it accepts. An unsafe trait can expose a semantic invariant that every external implementation must preserve. In each case, the language identifies the boundary, but the marker does not prove that the obligation was met.

CULPA’s contribution is to give that responsibility a connected analysis model. The Specification Pool turns selected contract language into predicates and emissions. The predicates say what must be true before an operation; the emissions record what can be relied on after the operation has been checked. The Memory Segment Constructor identifies which allocated object, range, or field the predicate concerns. The Safeguard Analyzer then follows explicit checks, safe-type invariants, constructors, callers, and invalidating operations to build a Requirement Graph for the relevant sink.

Those parts matter because each one corrects a different kind of lost context. Without predicates, a phrase such as “valid at its given type” remains too compound to check consistently. Without memory segments, a vector’s initialized prefix can be confused with its spare capacity, or a safeguard for one field can be credited to another. Without safeguard provenance, a safe-looking invariant can be trusted after an unsafe cast, mutation, reallocation, or deallocation has already compromised it. Without caller traversal, an analysis can miss the public input that makes a private unsafe operation security-relevant.

The model is therefore more than a component diagram. It describes a causal chain:

  1. Documentation assigns a condition to an unsafe operation.
  2. An executable predicate binds that condition to arguments, types, memory, and state.
  3. Segment construction identifies the object and sub-region the condition protects.
  4. Safeguard analysis records evidence and its invalidation history.
  5. Reachability analysis asks whether an external caller can drive a path to the sink without the required evidence.

The five stages are not interchangeable. A predicate can be correct but applied to the wrong range. A segment can be identified correctly while the safeguard has been revoked. A missing safeguard can be a local coding error without a public path that exposes it. CULPA’s central insight is that a memory-safety finding becomes more credible when these relationships are preserved together, not when any one component produces a confident label.