notes-computer-programming-programmingLanguageDesign-cheri thoughts

see Self:proj-plbook-plChSecurityLangs for my notes on CHERI.


my questions on the above:

1) Let's say there is a capability sitting in main memory (so, its tag is on). If a non-capability register loads this (via repeated CLD instructions; page 131) and then stores it (via repeated CSD instructions), then does the stored value retain the tag?

i don't think so.

Some evidence that it would retain the tag is:

But some evidence that it wouldn't retain the tag is:

2) I see how Permit_Store_Capability gives you the option of giving code a capability without permitting it to share that capability (to 'write it down'). But what is the purpose of the Permit_Load_Capability capability? That is, in what circumstances would you want for some code to posses Permit_Load but not Permit_Load_Capability?

they list as one of their 'deeper design decisions' "Permissions on capabilities include the ability to not just control loading and storing of data, but also loading and storing of capabilities" (section 4.6 page 68), but what is the purpose of this?

if the answer to (1) were that the tag was preserved, then i would say that the purpose of Permit_Load was to allow code to copy capabilities in memory without being able to actually use them itself.

maybe the answer is here: " The decision to strip tags on load, but throw an exception on store, reflects pragmatic software utilization goals: language runtimes and system libraries often need to implement capability-oblivious memory copying , as the programmer may not wish to specify whether a region of memory must (or must not) contain capabilities). By stripping tags rather than throw- ing an exception on load, a capability-oblivious memory copy is safe to use against arbitrary virtual addresses and source capabilities – without risk of throwing an exception. Software that wishes to copy only data from a source capability, excluding tag bits due to a non-propagation goal, can simply remove the load-capability permission from the source capability before be- ginning a memory copy " (section 3.2.7 page 44)

so the idea is that you can use the capability registers to copy data, while stripping the tag bits, if Permit_Load is unset? That's not very satisfying if you could have already done that using non-capability registers.

actually, i think the idea is that you can have one copy routine which is passed in a capability to use while copying and, depending on whether or not Permit_Load and Permit_Store are set, will either end up stripping the tag bits, or preserving them (but if Permit_Load were set but Permit_Store were not, an exception would be thrown).

i don't find that so useful because the copy routine could have just branched on Permit_Store to either use the capability register or the non-capability GPRs for the copying.

now, what if ordinary data copies DID preserve capabilities? At first glance, this would seem to provide a way for the lack of Permit_Load_Capability to indicate that capabilities CAN be copied but CANNOT be used directly. That is, without Permit_Load_Capability or Permit_Store_Capability , there would still be some way to copy capabilities in main memory, preserving their tag bits; but these capabilities would not be able to be loaded into capability registers, noting that "Where general-purpose registers describe the computation state of a software thread, capability registers describe its instantaneous rights within an address space".

An critical flaw with this is that it prevents there from being any way to give a process read access to a chunk of main memory containing capabilities while preventing it from copying/sharing those capabilities with others. If the others have Permit_Load_Capability on ANY chunk of memory they can receive the capability using ordinary data copy from the sharing process, and then copy it into the chunk of memory in which they have Permit_Load_Capability, and then load it into a capability register from there.

In fact, i guess if a 'middleman' process wanted to transmit capabilities without using them itself, it could just pass on a pointer to them to a third process. If the third process then had Permit_Load_Capability on that area of memory, it could then use the contained capabilities. Note that the middleman can only pass by reference, not via copying; and note that the end recipient must already have a Permit_Load_Capability that spans the memory region that the capability is in. So the middleman can't do anything as useful as compacting garbage collection; it can only refer in conversation with the end recipient to memory locations that the end recipient can already see.

i suppose one way to have middleman copiers or compacting garbage collectors who cannot themselves use the capabilities in the contained memory would be to have a way to say "within the one memory region defined by this capability, you can copy capabilities, but you can't copy them outside of it". The "you can't copy them outside of it" is covered by Permit_Store_Local.

3) is there any use for a capability having Permit_Store_Local without also having Permit_Store?


so i think there should be a 'CMemCpy?' instruction which can copy capabilities only within the region of memory defined by the capability (provided that either Global is set on the capability being copied, or Permit_Store_Local is set on the enabling capability; otoh maybe we dont want to use Permit_Store_Local for this, as we want to preserve Permit_Store without Permit_Store_Local to mean that you can only store global capabilities into here, but what if you had Permit_Store only, would that mean you can copy? this is getting hairy. Maybe introduce a new capability just for CMemCpy?).

This allows:

now, how to provide a way to say 'you can use this capability but you can't share it'? The obvious thing to do would be to introduce a dual of Permit_Store_Local; Permit_Share_Local; and an additional 'Writable' tag bit. If unset, you can load capabilities from the affected region, but in doing so, the 'writable' tag bit is cleared. You can't store any capability with its writable tag bit cleared to main memory. If set, you can load capabilities from the affected region without clearing their 'writable' tag bit.

I'm not sure how useful this is because if a routine really wants to share a capability that it holds but it isn't allowed to, it can just emulate other routines that it wants to share it with (assuming the other routines trust the emulator enough to share all of their capabilities with it). (But by the same token, how useful is it to not set Permit_Store_Capability? That just prevents the routine from storing capabilities INTO THIS REGION, so if there is ANY region it can store them into, it can pass any capability it owns to anyone else with Permit_Load_Capability on that other region. Well, i guess that restriction has some teeth, actually).

otoh the 'global' bit is already somewhat like this; unless you have Permit_Store_Local somewhere, you can't store any capabilities that you possess that are non-global.

Here's what they have to say about that:

" The Permit Store Local Capability permission bit is used to limit capability propagation via software-defined policies: local capabilities (i.e., those without the Global permission set) can be stored only via capabilities that have Permit Store Local Capability set. Normally, this permission will be set only on capabilities that, themselves, have the Global bit cleared. This allows higher-level, software-defined policies, such as “Disallow storing stack references to heap memory” or “Disallow passing local capabilities via cross-domain procedure calls,” to be implemented. We anticipate both generalizing and extending this model in the future in order to support more complex policies – e.g., relating to the propagation of garbage-collected pointers, or pointers to volatile vs. non-volatile memory "

---

yknow, actually i think the whole concept of Permit_Store_Capability (and similarly Permit_Store_Local) is flawed.

Permit_Store makes sense because it's just like whether you have write access to a file; sometimes you want ppl to be able to receive information without being able to alter the information being sent by others. Except for storage costs, people probably wouldn't have a problem granting write access all the time if the 'write' was just like submitting an alternate version of the file that no one else had to see unless they wanted to. So really, the reason we have write access is that it is also 'destroy' access (because you can mutate existing information), although if the file is the way of transmitting information from an authority, then giving you write access to that file also gives you 'impersonate authority' access.

But neither of those is an issue with Permit_Store_Capability. Permit_Store_Capability presumes write access (although ironically i don't think it actually checks for Permit_Store; are there any use cases when you are allowed to overwrite someone else's memory, but only if you overwrite it with capabilities? no); if the file owner already wants to give you write access then why not also give you Permit_Store_Capability? You can already 'destroy' the existing information and you can already 'impersonate' the owner of the file w/r/t others whose only way of receiving information from em is that memory area. Instead, the reason for Permit_Store_Capability seems to be to limit the propagation of capabilities.

That is, you trust a delegate to use a capability emself, but not to keep it a secret from others, either through incompentence or gullibility. The problem with this is that to do it this way, you have to have a complete/comprehensive "God's eye" view of which other capabilities the delegate has. If the delegate has Permit_Store to some other memory region that you don't know about, they can share your capability through that channel instead. Imagine you share your bank password with an assistant. You don't trust your assistant not to share the password with others. Not giving Permit_Store_Capability is like equiping your assistant's telephone with a device that monitors everything e says and cuts the connection if they are saying any password. But if, unbeknowst to you, your assistant has papers and pencils, they can still write down the password and leave it where the janitor might see it later.

What you would prefer would be for the privacy settings to be linked to the information itself, like the gevulot system of the book Quantum Thief, or to the combination of information and person (so that a middleman could pass along private information without using it emself). The 'global' flag is more like this. They probably just thought of Permit_Store_Capability because there is a use for Permit_Load_Capability, and because you have Permit_Load and Permit_Store.

---

So let's scrap Permit_Store_Capability and elaborate on 'global'.

i think the first pass can be pretty simple. So you don't trust someone to share a capability? Don't let them share it. Abolish Permit_Store_Capability and Permit_Store_Local (note: just abolishing Permit_Store_Local would be almost the same as what i am about to say). Rename 'global' to 'writable'. A capability can't be stored (as a (tagged) capability) unless its 'writable' bit is set. Add an instruction to store a capability while clearing its writable bit. Add an instruction to memcopy a capability while clearing its writable bit.

for the second pass, we want to achieve the goals of Permit_Store_Local, eg "This allows higher-level, software-defined policies, such as “Disallow storing stack references to heap memory” or “Disallow passing local capabilities via cross-domain procedure calls,” to be implemented. We anticipate both generalizing and extending this model in the future in order to support more complex policies – e.g., relating to the propagation of garbage-collected pointers, or pointers to volatile vs. non-volatile memory.".

in general, we might want to restrict writability by the product of (capability being written, process doing the writing, place being written to). Of course the whole point of capabilities is to replace identifying a specific 'process' with 'any process holding this writability capability'. And the idea of capabilities over memory regions is to replace 'capability being written' with 'memory region in which capability was found'.

So it seems like what we want is to parameterize 'writability' by locations to which the capability might be written. And since our capabilities now contain pointer bounds, we can refer to memory regions by refering to capabilities. So how about we add a new pointer field to a capability, which points to another capability (or to itself), and which means, "i can be written to this place". For reduction of redundancy, we can use sentinels here to replace the 'writable' bit: if this pointer is 0, that means 'non-writable', and if this pointer is the highest possible value (or mb just -1?), that means 'writable anywhere'. Otherwise it means, 'i can be written only to the memory region specified by the capability found at this address'.

This still only covers one contiguous memory region; what if you have a capability that can be written to either of two memory regions M1 and M2? You could try to represent this as two different capabilities C1 and C2 respectively, but then if there is a processes P1 that can only access M1, and both C1 and C2 are given to P1, then P1 can only save C1. Then if there is a process P2 which can write to M2 and read from M1, it can only get P1, so it can't write anything to M2.

So i guess for more generality, the capability points to a list of capabilities, and it can be written to every memory region contained in the capabilities of that list.

Just to generalize this some more in a pleasingly symmetric way, we could say that the previous holds for those capabilities in the list which give Permit_Store, but for those that give Permit_Load, that defines from which memory regions this capability can be loaded (and what about memcopying without loading?).

---

wait a minute, wouldn't it be simpler just to use sealing for passing around 'opaque' pointers among copying middlemen?

and if that's good enough, then again, why have Permit_Load_Capability at all?

nah, for the reasons given above (no comprehensive view of which regions another process has access to), we want to restrict the movement of even sealed capabilities

i guess another use for Permit_Load_Capability is eg:

---

also i though of another reason for Permit_Store_Capability:

Let's say there is an untrusted entity that you are the only one directly communicating with. You create a region that they can read. For some reason, you also have to give the untrustworthy entity Permit_Load_Capability on that region. You then ask a non-malicious but confusable deputy to write some stuff into that region. You want to ensure that the deputy can't be tricked into writing capabilities into there. You don't need a God's-eye view because you know that this region is the only one in the system that the untrusted entity can read.

---

also, do we want a Permit_Local_Copy flag?

at first blush, we always want to permit local copying of capabilities, at least when we have rw permissions (Permit_Load + Permit_Store). But i suppose there might be some times when we don't, for example, if we are giving some subroutine access to a large segment of memory that contains multiple subsegments which are destined to be used to share with different end consumers. It seems odd, however, that we would give the subroutine rw access to the chunk, that is, trusting it to read and write everywhere in it, without also trusting it not to improperly move the contained capabilities. All in all, i think this is probably a niche use and i'd probably just say local copies with CMemCpy? are always permitted.

---