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