Capability-based security
This section introduces the nomenclature and the general model of Genode's capability-based security concept. The Genode OS framework is not tied to one kernel but supports a variety of kernels as base platforms. On each of those base platforms, Genode uses different kernel mechanisms to implement the general model as closely as possible. Note however that not all kernels satisfy the requirements that are needed to implement the model securely. For assessing the security of a Genode-based system, the respective platform-specific implementation must be considered. Sections Execution on bare hardware (base-hw) and Execution on the NOVA microhypervisor (base-nova) provide details for selected kernels.
Capability spaces, object identities, and RPC objects
Each component lives inside a protection domain that provides an isolated execution environment.
Genode provides an object-oriented way of letting components interact with each other. Analogously to object-oriented programming languages, which have the notion of objects and pointers to objects, Genode introduces the notion of RPC objects and capabilities to RPC objects.
An RPC object provides a remote-procedure call (RPC) interface. Similar to a regular object, an RPC object can be constructed and accessed from within the same program. But in contrast to a regular object, it can also be called from the outside of the component. What a pointer is to a regular object, a capability is to an RPC object. It is a token that unambiguously refers to an RPC object. In the following, we represent an RPC object as follows.
The circle represents the capability associated with the RPC object. Like a pointer to an object, that can be used to call a function of the pointed-to object, a capability can be used to call functions of its corresponding RPC object. However, there are two important differences between a capability and a pointer. First, in contrast to a pointer that can be created out of thin air (e.g., by casting an arbitrary number to a pointer), a capability cannot be created without an RPC object. At the creation time of an RPC object, Genode creates a so-called object identity that represents the RPC object in the kernel. Figure 3 illustrates the relationship of an RPC object and its object identity.
For each protection domain, the kernel maintains a so-called capability space, which is a name space that is local to the protection domain. At the creation time of an RPC object, the kernel creates a corresponding object identity and lets a slot in the protection domain's capability space refer to the RPC object's identity. From the component's point of view, the RPC object A has the name 3. When interacting with the kernel, the component can use this number to refer to the RPC object A.
Delegation of authority and ownership
The second difference between a pointer and a capability is that a capability can be passed to different components without losing its meaning. The transfer of a capability from one protection domain to another delegates the authority to use the capability to the receiving protection domain. This operation is called delegation and can be performed only by the kernel. Note that the originator of the delegation does not diminish its authority by delegating a capability. It merely shares its authority with the receiving protection domain. There is no superficial notion of access rights associated with a capability. The possession of a capability ultimately enables a protection domain to use it and to delegate it further. A capability should hence be understood as an access right. Figure 4 shows the delegation of the RPC object's capability to a second protection domain and a further delegation of the capability from the second to a third protection domain. Whenever the kernel delegates a capability from one to another protection domain, it inserts a reference to the RPC object's identity into a free slot of the target's capability space. Within protection domain 2 shown in Figure 4, the RPC object can be referred to by the number 5. Within protection domain 3, the same RPC object is known as 2. Note that the capability delegation does not hand over the ownership of the object identity to the target protection domain. The ownership is always retained by the protection domain that created the RPC object.
Only the owner of an RPC object is able to destroy it along with the corresponding object identity. Upon destruction of an object identity, the kernel removes all references to the vanishing object identity from all capability spaces. This effectively renders the RPC object inaccessible for all protection domains. Once the object identity for an RPC object is gone, the owner can destruct the actual RPC object.
Capability invocation
Capabilities enable components to call methods of RPC objects provided by different protection domains. A component that uses an RPC object plays the role of a client whereas a component that owns the RPC object acts in the role of a server. The interplay between client and server is very similar to a situation where a program calls a local function. The caller deposits the function arguments at a place where the callee will be able to pick them up and then passes control to the callee. When the callee takes over control, it obtains the function arguments, executes the function, copies the results to a place where the caller can pick them up, and finally hands back the control to the caller. In contrast to a program-local function call, however, client and server are different threads in their respective protection domains. The thread at the server side is called entrypoint denoting the fact that it becomes active only when a call from a client enters the protection domain or when an asynchronous notification comes in. Each component has at least one initial entrypoint, which is created as part of the component's execution environment.
The wiggly arrow denotes that the entrypoint is a thread. Besides being a thread that waits for incoming requests, the entrypoint is responsible for maintaining the association between RPC objects and their corresponding capabilities. The previous figures illustrated this association with the link between the RPC object and its capability. In order to become callable from the outside, an RPC object must be associated with a concrete entrypoint. This operation results in the creation of the object's identity and the corresponding capability. During the lifetime of the object identity, the entrypoint maintains the association between the RPC object and its capability in a data structure called object pool, which allows for looking up the matching RPC object for a given capability. Figure 6 shows a scenario where two RPC objects are associated with one entrypoint in the protection domain of a server. The capability for the RPC object A has been delegated to a client.
If a protection domain is in possession of a capability, each thread executed within this protection domain can issue a call to a member function of the RPC object that is referred to by the capability. Because this is not a normal function call but the invocation of an object located in a different protection domain, this operation has to be provided by the kernel. Figure 7 illustrates the interaction of the client, the kernel, and the server. The kernel operation takes the client-local name of the invoked capability, the opcode of the called function, and the function arguments as parameters. Upon entering the kernel, the client's thread is blocked until it receives a response. The operation of the kernel is represented by the dotted line. The kernel uses the supplied local name as an index into the client's capability space to look up the object identity, to which the capability refers. Given the object identity, the kernel is able to determine the protection domain and the corresponding entrypoint that is associated with the object identity and wakes up the entrypoint's thread with information about the incoming request. Among this information is the server-local name of the capability that was invoked. Note that the kernel has translated the client-local name to the corresponding server-local name. The capability name spaces of client and server are entirely different. The entrypoint uses this number as a key into its object pool to find the locally implemented RPC object A that belongs to the invoked capability. It then performs a method call of the so-called dispatch function on the RPC object. The dispatch function maps the supplied function opcode to the matching member function and calls this function with the request arguments.
The member function may produce function results. Once the RPC object's member function returns, the entrypoint thread passes the function results to the kernel by performing the kernel's reply operation. At this point, the server's entrypoint becomes ready for the next request. The kernel, in turn, passes the function results as return values of the original call operation to the client and wakes up the client thread.
Capability delegation through capability invocation
Section Delegation of authority and ownership explained that capabilities can be delegated from one protection domain to another via a kernel operation. But it left open the question of how this procedure works. The answer is the use of capabilities as RPC message payload. Similar to how a caller of a regular function can pass a pointer as an argument, a client can pass a capability as an argument to an RPC call. In fact, passing capabilities as RPC arguments or results is synonymous to delegating authority between components. If the kernel encounters a capability as an argument of a call operation, it performs the steps illustrated in Figure 8.
The local names are denoted as $cap$, e.g., $cap_{arg}$ is the local name of the object identity at the client side, and $cap_{translated}$ is the local name of the same object identity at the server side.-
The kernel looks up the object identity in the capability space of the client. This lookup may fail if the client specified a number of an empty slot of its capability space. Only if the lookup succeeds is the kernel able to obtain the object identity referred to by the argument. Note that under no circumstances can the client refer to object identities, for which it has no authority because it can merely specify the object identities reachable through its capability space. For all non-empty slots of its capability space, the protection domain was authorized to use their referenced object identities by the means of prior delegations. If the lookup fails, the translation results in an invalid capability passed to the server.
-
Given the object identity of the argument, the kernel searches the server's capability space for a slot that refers to the object identity. Note that the term "search" does not necessarily refer to an expensive linear search. The efficiency of the operation largely depends on the kernel implementation.
-
If the server already possesses a capability to the object identity, the kernel translates the argument to the server-local name when passing it as part of the request to the server. If the server does not yet possess a capability to the argument, the kernel installs a new entry into the server's capability space. The new entry refers to the object identity of the argument. At this point, the authority over the object identity has been delegated from the client to the server.
-
The kernel passes the translated or just-created local name of the argument as part of the request to the server.
Even though the above description covered the delegation of a single capability specified as argument, it is possible to delegate more than one capability with a single RPC call. Analogously to how capabilities can be delegated from a client to a server as arguments of an RPC call, capabilities can be delegated in the other direction as part of the reply of an RPC call. The procedure in the kernel is the same in both cases.