How CombiSan Makes Different Failures Share One Run
CombiSan's distinctive move is to make addressability and initialization part of one memory state instead of asking incompatible sanitizers to maintain separate views. This chapter reconstructs that move from allocation to access, then follows an uninitialized-load signal through aggregation and confirmation. The point is to see exactly what the design gains—and where it deliberately accepts a boundary—before judging its accuracy or speed.
One byte, two independent questions
The first design problem is representational. ASan needs to know whether an address belongs to a live, accessible region. MSan needs to know whether the value obtained from that region has been initialized and whether that state can reach a meaningful use. Those questions can both apply to the same byte, but they do not change in the same way.
CombiSan gives each program byte two shadow bits. One bit records addressability: clear means the byte can be accessed, while set means it belongs to a redzone or a quarantined region. The other records initialization: clear means the byte is initialized, while set means it is not. Four program bytes can therefore share one shadow byte. The paper describes this as a 4-to-1 program-to-shadow mapping, which gives a 25 percent shadow-memory component before the other costs of the runtime are counted.
The two bits are necessary because the transitions are independent. A newly allocated, visible object is addressable but uninitialized. A valid store changes its bytes to addressable and initialized; it must not make a redzone accessible. A deallocation makes the object unaddressable and places it in quarantine; it must not rewrite the historical meaning of the data as if it had been initialized. If one bit represented both conditions, the runtime would have no way to distinguish “valid location, unknown value” from “invalid location,” or to clear one property while retaining the other.
That compact representation also makes the common check simple. A load can inspect the joint shadow value: a zero value means that the target bytes are both addressable and initialized, while any set bit signals a condition that needs handling. A store first masks the initialization portion away and asks whether the destination is addressable. If the address check succeeds, it clears the initialization bits for the bytes written. This order matters. A write into an inaccessible region is an addressability failure; it must not be allowed to “initialize” the region merely because data was supplied.
The allocator establishes the initial state. CombiSan pads objects with redzones, marks the usable portion uninitialized when the allocation is visible to the instrumented target, and updates the shadow state as the program writes. When the object is freed, the runtime marks the entire chunk inaccessible and quarantines it so that a later dangling access can still be recognized. In a simplified example, a buffer can contain initialized bytes after a partial memset, untouched but addressable bytes, and inaccessible padding around the allocation at the same time. A read from each region asks a different question: valid, uninitialized candidate, or addressability failure.
The design is close enough to ASan's access model to keep the compiler work focused.