You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
We discussed this on Zulip multiple times and maybe even on IRLO but I could not find a concise summary. This assume that on CHERI, usize is 64bit -- discussion of other ways choices should be delegated to other threads.
So what operations work different on CHERI than they would on all other Rust targets?
with_exposed_provenance/int as ptr simply does not work. We should probably reject it at compile time; if we don't then the operation will create a no-capability pointer and the program will (safely) abort at runtime when that pointer is used to access memory.
wrapping_offset may sometimes "lose" the capability. Due to CHERI compressing capabilities into 64bit, they cannot precisely represent capabilities that are too far away from the address the pointer is pointing to, so something like ptr.wrapping_add(BIG).wrapping_sub(BIG) might break the pointer. The program will safely abort at runtime when such a pointer is used to access memory. This can also affect operations based on wrapping_offset, such as with_addr.
Copying a pointer byte-for-byte with MaybeUninit<u8> (or similar) no longer works: the copy may lose its permissions, using it to access memory will (safely) abort the process.
CHERIoT has some more concerning limitations: pointers to stack variables cannot be stored in global variables or the heap.
I think that's it? Basically we get some safe aborts in cases where the program should not abort, but that should hopefully be rare and if it happens, it shouldn't be too hard to debug.
CHERI capabilities also contain a set of permissions, so you can make capabilities you can't write through or can't read through (while still allowing to write through them). And you can have sealed capabilities that you can't do anything with other than pass around and unseal it using another capability. The exact set of permissions depends on which CHERI variant you use.
For example on CHERIoT1 the permissions are global (can be stored in arbitrary memory, not just on the stack and in registers), load, store, execute, seal, unseal, load/store capability (you can load/store capabilities at the target address of the capability with this permission), load global (load capabilities with the global bit set), load mutable (load capabilities with the store bit set, if not set on the capability passed to the load instruction, the loaded capability would forcefully lose the store bit), store local (store capabilities without the global bit set), and access system registers (if the instruction pointer capability has this bit set).
While RISC-V CHERI2 (the stable, but not yet ratified Y extension) only has read, write, execute, load/store capability, load mutable, and access system registers..
Do those new permissions matter for this thread? My understanding of the OP question is: given a program today (for which we can compute its semantics today, ie. for non-CHERI targets), in which way will its semantics differ for CHERI targets tomorrow? Here is the OP question for reference:
So what operations work different on CHERI than they would on all other Rust targets?
It's true CHERI will come with additional intrinsics (to read/modify capabilities beyond just their address, including sealing, etc) and maybe additional provenance components (we're kind of generic over provenance today, in a "row polymorphism" sense, but that could be an issue when/if we stop), but those things don't exist for today's programs. Of course adding them should not change existing programs, which is the question.
(Also I believe this thread deliberately ignores the fact that programs may randomly abort due to stack overflow, since that would make the question uninteresting. In some sense this thread answers positively that supporting CHERI won't significantly change the language. We already randomly abort because of stack overflow, and we may additionally randomly abort because of mitigations. In both cases the compiler does its best to only abort when relevant.)
On CHERIoT you can't store pointers to stack variables in heap allocations I believe. Other than that, I don't think those pernissions matter unless you explicitly make use of them.
My understanding of the OP question is: given a program today (for which we can compute its semantics today, ie. for non-CHERI targets), in which way will its semantics differ for CHERI targets tomorrow? Here is the OP question for reference:
Correct.
Also I believe this thread deliberately ignores the fact that programs may randomly abort due to stack overflow,
Correct.
On CHERIoT you can't store pointers to stack variables in heap allocations I believe.
Interesting. Yeah such a system can only run no_std Rust. This will have to be a debate whether we are okay with having a CHERIoT dialect of Rust that has a non-standard heap.
Indeed, you can't store a capability without G (global) through a capability without SL (store-local), because it will trap. But that's just another case of "randomly abort". See minirust/minirust#32 but we essentially have to bite this bullet (like we did for stack overflow).
For clarification, this is not specific to heap allocations. This also affects global variables.
While the bootloader makes sure globals don't have the SL permission, I'm not sure under which circumstances the G permission is removed from arguments on function call. If both are out of the program control (i.e. they happen on existing language operation and don't need an attribute or intrinsic), then I agree we should add this situation to the OP list. This is not just a "bounds precision loss", it's a permission loss on function call for specific pointer arguments (but I don't know how that's exactly decided), which means those pointers can't be stored to global variables (heap, statics, etc) which are identified by missing the SL permission.
I added one more that I had entirely forgotten about:
Copying a pointer byte-for-byte with MaybeUninit<u8> (or similar) no longer works: the copy has lost its permissions, using it to access memory will (safely) abort the process.
I suspect that it may crash, not is guaranteed to crash. If the optimizer happens to merge the loads/stores for all 16 bytes of the capability into a single load/store instruction with a capability register as temporary location, I think it will work.
Activity
bjorn3 commentedon Sep 24, 2026
CHERI capabilities also contain a set of permissions, so you can make capabilities you can't write through or can't read through (while still allowing to write through them). And you can have sealed capabilities that you can't do anything with other than pass around and unseal it using another capability. The exact set of permissions depends on which CHERI variant you use.
For example on CHERIoT1 the permissions are global (can be stored in arbitrary memory, not just on the stack and in registers), load, store, execute, seal, unseal, load/store capability (you can load/store capabilities at the target address of the capability with this permission), load global (load capabilities with the global bit set), load mutable (load capabilities with the store bit set, if not set on the capability passed to the load instruction, the loaded capability would forcefully lose the store bit), store local (store capabilities without the global bit set), and access system registers (if the instruction pointer capability has this bit set).
While RISC-V CHERI2 (the stable, but not yet ratified Y extension) only has read, write, execute, load/store capability, load mutable, and access system registers..
Footnotes
https://cheriot.org/book/concepts.html#permissions ↩
https://riscv.github.io/riscv-cheri/#AP-field ↩
ia0 commentedon Sep 24, 2026
Do those new permissions matter for this thread? My understanding of the OP question is: given a program today (for which we can compute its semantics today, ie. for non-CHERI targets), in which way will its semantics differ for CHERI targets tomorrow? Here is the OP question for reference:
It's true CHERI will come with additional intrinsics (to read/modify capabilities beyond just their address, including sealing, etc) and maybe additional provenance components (we're kind of generic over provenance today, in a "row polymorphism" sense, but that could be an issue when/if we stop), but those things don't exist for today's programs. Of course adding them should not change existing programs, which is the question.
(Also I believe this thread deliberately ignores the fact that programs may randomly abort due to stack overflow, since that would make the question uninteresting. In some sense this thread answers positively that supporting CHERI won't significantly change the language. We already randomly abort because of stack overflow, and we may additionally randomly abort because of mitigations. In both cases the compiler does its best to only abort when relevant.)
bjorn3 commentedon Sep 24, 2026
On CHERIoT you can't store pointers to stack variables in heap allocations I believe. Other than that, I don't think those pernissions matter unless you explicitly make use of them.
RalfJung commentedon Sep 24, 2026
Correct.
Correct.
Interesting. Yeah such a system can only run
no_stdRust. This will have to be a debate whether we are okay with having a CHERIoT dialect of Rust that has a non-standard heap.ia0 commentedon Sep 24, 2026
Indeed, you can't store a capability without G (global) through a capability without SL (store-local), because it will trap. But that's just another case of "randomly abort". See minirust/minirust#32 but we essentially have to bite this bullet (like we did for stack overflow).
ia0 commentedon Sep 24, 2026
For clarification, this is not specific to heap allocations. This also affects global variables.
While the bootloader makes sure globals don't have the SL permission, I'm not sure under which circumstances the G permission is removed from arguments on function call. If both are out of the program control (i.e. they happen on existing language operation and don't need an attribute or intrinsic), then I agree we should add this situation to the OP list. This is not just a "bounds precision loss", it's a permission loss on function call for specific pointer arguments (but I don't know how that's exactly decided), which means those pointers can't be stored to global variables (heap, statics, etc) which are identified by missing the SL permission.
RalfJung commentedon Sep 26, 2026
I added one more that I had entirely forgotten about:
bjorn3 commentedon Sep 26, 2026
I suspect that it may crash, not is guaranteed to crash. If the optimizer happens to merge the loads/stores for all 16 bytes of the capability into a single load/store instruction with a capability register as temporary location, I think it will work.