Formal Mechanised Semantics of CHERI C: Capabilities, Undefined Behaviour
dl.acm.org
dl.acm.org
Note there's also a RISC-V effort[0][1] to write an extension specification proper.
0. https://jira.riscv.org/browse/RVS-2141?src=confmacro
1. https://wiki.riscv.org/display/HOME/RISC-V+Specification+Sta...
Unfortunately low level languages are kind of addicted to optimizations that only work for languages with UB. There's too much performance being left at the table if you create a programming language entirely without UB.
There's no law of nature, however, that says this must be the case forever. You could have a theorem prover prove useful properties about programs (and them feed them to the optimizer) and refrain from "optimizing" if you can't 100% prove that this will not break.
Another example on tweetnacl: https://blog.adacore.com/sparknacl-two-years-of-optimizing-c...
Memory safety is not a binary choice. Consider this typical structure:
struct buffer {
char *data;
size_t capacity;
size_t length;
}
In fact it doesn't even have to be C, you could write it in Java, Python, etc. Lets say you decide to reuse such a buffer multiple times. From the C semantics point of view, there is nothing wrong with accessing elements past length. But it can easily lead to a heartbleed style vulnerability. There is probably some escape hatch for CHERI to mark memory as uninitialized, but I'm not very familiar with it. In Rust you can probably give out slices which will ensure bounds checking (or eliminate it if possible). The only correct way towards safety is languages which enable the programmer to add additional restrictions, like Rust or Frama-C.Anyway, this is mostly just theoretical musing, in practice CHERI will catch lots of issues. But I don't think it is correct to call it "memory safety".
struct buffer {
char *data;
size_t capacity;
size_t length;
}
the pointer 'data' will in CHERI have to be a valid capability, not just a virtual address, to permit access. It should normally have been instantiated with the correct bounds from the appropriate allocation, separately from the 'length' field, so the hardware will do the right check.In C and C++, if you write x[4] for an array with only 2 members, you get undefined behaviour. In Rust this causes a 'panic', but that isn't a 'borrow checker' thing, that's because in Rust all array accesses are bounds checked by default.
You can do un-bounds checked array accesses in Rust, you can do bounds-checked accesses in C++ (for std::vector at least), it's just that the languages chose different defaults.
One thing the borrow checker does is help write parallel code by stopping multiple threads writing to the same memory at the same time. CHERI doesn't stop that at all.
For embedded systems, the CHERIoT [2] platform implements hardware-assisted temporal memory safety without MMU.
[0] https://ctsrd-cheri.github.io/cheribsd-getting-started/featu...
[1] https://www.asplos-conference.org/asplos2024/main-program/ab...