Compile with Fil-C and run it a few times. Most of the time, it will trap. However, every few times, it will print "secret password".
This covers the scenario where a malicious user causes a program to leak information using an overflowing offset. Typically Fil-C will mitigate this kinds of thing with a bounds check. However, a data race can cause the bounds on a pointer to be incorrect.
This isn’t just contrived:
Racy communication through shared globals is everywhere.
Think:
- Lock-free queues, ring buffers, flags.
- “Benign” data races that people rely on in C/C++.
- Faster-than-necessary shortcuts where people skip atomics/locks.
Separating “check” and “use” is standard.
The pattern:
if (ptr == expected) {
use(ptr + user_offset);
}shows up in filtering APIs, RPC handlers, security policies, etc. This could be inside a library where the safe pointer (expected) is considered trusted and the offset comes from unvalidated external data.
Unchecked offsets and index bugs are the norm.
Out-of-bounds indices, integer overflow, off-by-one errors—these are routine in real systems. If your “safe C” system doesn’t protect against “valid pointer + bad offset,” then it isn’t actually enforcing memory safety.
Compiler & hardware reordering make races worse, not better.
The asm volatile("" : : : "memory") is just to keep the compiler from optimizing away the race. In real hardware, CPU reordering, cache coherence effects, and weak memory models make it even harder to reason about which pointer value is observed.
So the punchline:
- The example leaks
secret passwordeven though there’s a seemingly valid check (“only read ifp == allowed_pointer”). - This shows that a memory-safety scheme which only reasons about pointer provenance—without addressing offsets, races, and TOCTOU—is insufficient.
- And the problematic patterns (integer offsets + racy communication + check-then-use) are common, so any claim of “completely memory safe C” while preserving compatibility must confront this directly.
Having adjusted the test program to use stdatomics for the pointer swap operations, so far it appears to be trapping every time.
It has the same (always trap) behaviour with the asm statements commented out.
Also simply taking the original code example, and changing the definition of
message_pointeras follows seems to be sufficient, although I am not sure if that is guaranteed.