How hard is it to adapt a memory allocator to CHERI?
We're working with the Morello processor and have had to wrestle a bit with allocation from a block based allocator: https://medium.com/thg-tech-blog/morello-and-memory-pools-91...
There's an interesting exchange on Lobsters between the author of this post and a person working on CHERI:
Good lord, what an absolute turn-off. I'll take memory unsafety over working with someone who continually instigates arguments like that.
Yeah the original discussion is reasonable (disclaimer: I brought up the exact same points independently) but it very quickly veers into irrelevant drama.
Getting CHERI protection for security violations seems a bit like hitting the jackpot on a slot machine.
You need a CHERI OS on a CHERI CPU running software compiled with a CHERI aware compiler.
Doesn’t Rust also provide buffer overrun protection, without needing a special OS or CPU instruction set? Isn’t it a better ROI to rewrite the software on Rust, and not have to change anything else?
Can the same benefit be achieved by replacing CHERI CPU with a virtual machine runtime, or building support in LLVM IR?
VMware virtualized x86 without hardware support, so it probably can be done. That doesn't mean it would be easy or it would be as performant as with hardware support.
> Doesn’t Rust also provide buffer overrun protection, without needing a special OS or CPU instruction set?
No, because there’s still quite a lot of (inevitable) Unsafe Rust code floating around, and the compiler can’t protect these from memory-related errors. In fact there are some ongoing discussions and progress to support CHERI in Rust:
It would be nice to have a “part 2” of this where a more complex allocator was discussed that does more interesting things like have intrusive metadata or have heap block headers. For example, you probably do not want to hand out a pointer with a capability to alter its own heap header, but on free you’ll get that pointer back and it won’t have the capability: how do you safely use that context to modify your metadata again? How do you protect freelists? There’s lots to explore.
Perhaps we'll see allocators hold a heap/bucket pointer + capability; you would use the "free" pointer to find the metadata, and the heap pointer to interact with it
Some more security conscious allocators have already moved away from adjacent metadata storage to mitigate heap overflows. Instead, the metadata is stored in separate ASLR'd allocations and indexed by the pointer value. Some metadata might be implicitly encoded in the pointer value (e.g. by alignment or position above/below some virtual memory demarcation) to optimize lookup of the metadata.
But CHERI would stop pointer owners from exploring nearby memory space, so the metadata would only be accessible from within the allocator itself regardless. AFAICT there should be no need for ASLR with CHERI. (I mean defense in depth is ok…)
My point was just that current security best practice already requires indexing a separate data structure for bookkeeping. With CHERI, even if your metadata is stored in an adjacent header, to read or write that memory you have to lookup its pool to derive a wider pointer encompassing that header. (If not, as the article describes your allocator is misusing CHERI.)
But certainly with CHERI you could recover some optimization opportunities. For example, the block length can be derived directly from the pointer, which could make it easier to lookup the parent pool. And CHERI pointers preserve some bits for application pointer tagging, making it easier to implement typed allocations.
However, you still have to be careful. CHERI provides spatial safety, not temporal safety--double frees, dangling pointers, etc--so you may not want to reuse the application-visible portion of a block for bookkeeping purposes. Similarly, ASLR still retains much of its benefit, as does Rust--static enforcement of temporal safety, not spatial safety, is the principal value-add of Rust (spatial safety in Rust primarily comes from the library, not the language per se).
IIRC, the allocator would possess a capability with authority over the whole heap, and it can derive a new pointer using that capability and the address of the block that's being freed.
Obviously, it should make sure that the capability passed to free has full authority over the block first, or else it may end up vulnerable to confused deputy attacks.