Hi Kevin, thank you for your insightful reply.
The idea of passing the cap to identify metadata in a server that does not implement the object the cap refers to is problematic. I believe the operation you want at the kernel-level is "what is the index in my cspace corresponding to the cap I was sent". Given cspaces are arbitrary directed graph, this operation is asking what is the path between two points in the graph (if it exists) and not something we wish to support.
From a user's point of view, this would indeed be perfect. On NOVA, this feature (in NOVA speak called "translate") is implemented in a way that the graph is traversed towards the root of the tree only. So there is no search involved. Hence, in the common case, the costs are reasonably small. Still, the time needed for the traversal depends on user-land behavior, which probably contradicts with seL4's guarantees about interrupt latencies.
One approach is to have the mediator mediate all operations, i.e. have M as a proxy for access to F - obviously this has a performance impact.
Unfortunately, this works not for scenarios where C passes the capability as argument to another service of F. E.g., Genode's core process provides services for allocating RAM dataspaces or obtaining MMIO dataspaces. Dataspaces are often obtained not from core directly but from mediating processes, for example a platform driver that hands out MMIO resources to clients according to a policy. Core also provides a region-manager (RM) service for paging processes. A process in its role of an RM client makes a dataspace visible in its address space by passing the dataspace capability as an argument to an 'attach' function on its RM session. If M implemented the dataspace interface, the dataspace capability handed out to C would be meaningless for core when C tries to attach the dataspace. In short, this approach would not be general enough for us.
Alternatively, C gets a tuple (Fcap, Mdesc) and you split the interface - which is what you seem to be doing anyway. Given C will have already have session cap to M (lets call it Mcap), destroy involves invoking Mcap with Mdesc. Note: I'm glossing over interface enforcement, i.e. M gets Fcap1 which can destroy, and C gets Fcap2 which cannot, allowing you to enforce the split interface. Note: You could have an (Fcap2,Mcap)-like tuple to the client to split interface as well.
That leads indeed to a direction I have been thinking about: We could represent a Genode capability by a triple of seL4 capabilities. (1) The "actual" seL4 cap that points to the real object. It is used for invocation. (2) A "local ID" that is created and badged by the local process at the time it obtained the capability. This "local ID" can be used as key to locally stored meta data associated with the Genode capability. (3) An "external ID" seL4 cap that corresponds to the "local ID" of the originator of the capability when it was delegated to the process. When delegating a Genode capability, the sender puts all three seL4 capabilities into the message. On the receiver side, (3) will be received as badge if the Genode capability originally came from the receiver (as (3) will correspond to a "local ID" at the receiver). In this case, the receiver can use the badge to re-identify the original Genode capability. This approach would allow us to re-identify capabilities along the branches of the delegation tree, which suffices for Genode.
In general, caps are more expensive and are only required when needing to pass an unforgeable authority between entities. Adding a descriptor to an existing session cap is generally cheaper. I know this statement is probably blasphemous, but performance implications of a design matter as long as the security model remains sound.
I see that carrying three seL4 capabilities for one Genode capability is more costly than a one-to-one relationship. This will certainly show in a microbenchmark. But the impact (if any) on application performance is completely uncertain. Most of Genode's interfaces are designed such that they avoid capabilities as arguments. For assessing the influence on application-level, we could conduct the experiment to represent a Genode capability as a tuple of (seL4 cap, globally unique ID) and compare the result to the three-caps variant.
Last design pattern is to implement object "death notification" from F -> M, which would require a registration protocol: M->F. We use this in RefOS for processes, which I assume are much more coarse-grained than your dataspaces.
This would work for the specific lifetime-management example but it does not solve the re-identification issue in other scenarios. Just to give an example, a parent process P may have both a server and a client as child processes. The client established a session to the server through the parent. So the parent is the mediator. Both the parent and the client refer to the session using the corresponding session capability created and badged by the server. Now, the client wants to instruct the parent to transfer memory quota from the client to the server. It does so by specifying the session capability and the amount to a 'transfer_quota' call at the parent. In this scenario, the operation cannot be triggered by a mere notification but requires the client and the parent to "talk about" the session (and naturally access meta data associated to it).
Note: We have mulled over the idea of an operation bool points_to_same_object(cap1,cap2), this would enable C passing a foreign cap (F) together with a handle to the metadata in M, and thus you could test if the passed cap corresponds with the cap associated with your metadata in M. I'd be interested if such an operation improve/eases the protocol design of interaction between your services.
We actually use this approach of passing a hint value along with each capability on Fiasco.OC. The hint is used by the receiver to lookup an already present capability. If such a capability exists, it compares the incoming capability with existing one using a kernel operation. But this approach is hardly satisfying. First, it is prone to leaking information across process boundaries because the hint values must be allocated somehow. To mitigate the leakage of information, hint values would need to be translated between incoming hints and outgoing hints, which requires complicated book keeping. For this reason, we use global hint values on Fiasco.OC as a compromise. Third, each incoming IPC that carries one or more capabilities requires at least one additional system call for comparing the incoming capabilities at the receiver. Even though the 'points_to_same_object' operation would enable me to implement the same approach as we use on Fiasco.OC, it would still be a compromise. Best regards Norman -- Dr.-Ing. Norman Feske Genode Labs http://www.genode-labs.com · http://genode.org Genode Labs GmbH · Amtsgericht Dresden · HRB 28424 · Sitz Dresden Geschäftsführer: Dr.-Ing. Norman Feske, Christian Helmuth