Recursive system structure
The previous section introduced capability delegation as the fundamental mechanism to share authority over RPC objects between protection domains. But in the given examples, the client was already in possession of a capability to the server's RPC object. This raises the question of how do clients get acquainted to servers?
Component ownership
In a Genode system, each component (except for the very first component called core) has a parent, which owns the component. The ownership relation between a parent and a child is two-fold.
On the one hand, ownership stands for responsibility. Each component requires physical resources such as the memory or in-kernel data structures that represent the component in the kernel. The parent is responsible for providing a budget of those physical resources to the child at the child's creation time but also during the child's entire lifetime. As the parent has to assign a fraction of its own physical resources to its children, it is the parent's natural interest to maintain the balance of the physical resources split between itself and each of its children. Besides being the provider of resources, the parent defines all aspects of the child's execution and serves as the child's primary point of contact for seeking acquaintances with other components.
On the other hand, ownership stands for control. Because the parent has created its children out of its own resources, it is in the position to exercise ultimate power over its children. This includes the decision to destruct a child at any time in order to regain the resources that were assigned to the child. But it is also in control over the relationships of the child with other components known to the parent.
Each new component is created as an empty protection domain. It is up to the parent to populate the protection domain with code and data, and to create a thread that executes the code within the protection domain. At creation time, the parent installs a single capability called parent capability into the new protection domain. The parent capability enables the child to perform RPC calls to the parent. The child is unaware of anything else that exists in the Genode system. It does not even know its own identity nor the identity of its parent. All it can do is issue calls to its parent using the parent capability. Figure 2 depicts the situation right after the creation of a child component. A thread in the parent component created a new protection domain and a thread residing in the protection domain. It also installed the parent capability referring to an RPC object provided by the parent. To provide the RPC object, the parent has to maintain an entrypoint. For brevity, entrypoints are not depicted in this and the following figures. Section Component creation covers the procedure of creating a component in detail.
The ownership relation between parent and child implies that each component has to inherently trust its parent. From a child's perspective, its parent is as powerful as the kernel. Whereas the child has to trust its parent, a parent does not necessarily need to trust its children.
Tree of components
The parent-child relationship is not limited to a single level. Child components are free to use their resources to create further children, thereby forming a tree of components. Figure 3 shows an example scenario. The init component creates subsystems according to its configuration. In the example, it created two children, namely a GUI and a launcher. The latter allows the user to interactively create further subsystems. In the example, launcher was used to start an application.
At each position in the tree, the parent-child interface is the same. The position of a component within the tree is just a matter of composition. For example, by a mere configuration change of init, the application could be started directly by the init component and would thereby not be subjected to the launcher.
Services and sessions
The primary purpose of the parent interface is the establishment of communication channels between components. Any component can inform its parent about a service that it provides. In order to provide a service, a component needs to create an RPC object implementing the so-called root interface. The root interface offers functions for creating and destroying sessions of the service. Figure 4 shows a scenario where the GUI component announces its service to the init component. The announce function takes the service name and the capability for the service's root interface as arguments. Thereby, the root capability is delegated from the GUI to init.
It is up to the parent to decide what to do with the announced information. The parent may ignore the announcement or remember that the child "GUI" provides a service "GUI". A component can announce any number of services via subsequent announce calls.
The counterpart of the service announcement is the creation of a session by a client by issuing a session request to its parent. Figure 5 shows the scenario where the application requests a "GUI" session. Along with the session call, the client specifies the type of the service and a number of session arguments. The session arguments enable the client to inform the server about various properties of the desired session. In the example, the client informs the server that the client's window should be labeled with the name "browser". As a result of the session request, the client expects to obtain a capability to an RPC object that implements the session interface of the requested service. Such a capability is called session capability.
When the parent receives a session request from a child, it is free to take a policy decision on how to respond to the request. This decision is closely related to the management of resources described in Section Trading memory between clients and servers. There are the following options.
- Parent denies the service
-
The parent may deny the request and thereby prevent the child from using a particular service.
- Parent provides the service
-
The parent could decide to implement the requested service by itself by handing out a session capability for a locally implemented RPC object to the child.
- Server is another child
-
If the parent has received an announcement of the service from another child, it may decide to direct the session request to the other child.
- Forward to grandparent
-
The parent may decide to request a session in the name of its child from its own parent.
Figure 5 illustrates the latter option where the launcher responds to the application's session request by issuing a session request to its parent, the init component. Note that by requesting a session in the name of its child, the launcher is able to modify the session arguments according to its policy. In the example, the launcher imposes the use of a different label to the session. When init receives the session request from the launcher, it is up to init to take a policy decision with the same principle options. In fact, each component that sits in between the client and the server along the branches of the ownership tree can impose its policy onto sessions. The routing of the session request and the final session arguments as received by the server are the result of the successive application of all policies along the route.
Because the GUI announced its "GUI" service beforehand, init is in possession of the root capability, which enables it to create and destroy GUI sessions. It decides to respond to the launcher's session request by triggering the GUI-session creation at the GUI component's root interface. The GUI component responds to this request with the creation of a new GUI session and attaches the received session arguments to the new session. The accumulated session policy is thereby tied to the session's RPC object. The RPC object is accompanied with its corresponding session capability, which is delegated along the entire call chain up to the originator of the session request (Section Delegation of authority and ownership). Once the application's session request returns, the application can interact directly with the GUI session using the session capability.
The differentiation between session creation and session use aligns two seemingly conflicting goals with each other, namely efficiency and the application of the security policies by potentially many components. All components on the route between client and server are involved in the creation of the session and can thereby impose their policies on the session. Once established, the direct communication channel between client and server via the session capability allows for the efficient interaction between the two components. For the actual use of the session, the intermediate components are not on the performance-critical path.
Client-server relationship
Whereas the role of a component as a child is dictated by the strict ownership relation that implies that the child has to ultimately trust its parent, the role of a component as client or server is more diverse.
In its role of a client that obtained a session capability as result of a session request from its parent, a component is unaware of the real identity of the server. It is unable to judge the trustworthiness of the server. However, it obtained the session from its parent, which the client ultimately trusts. Whichever session capability was handed out by the parent, the client is not in the position to question the parent's decision.
Even though the integrity of the session capability can be taken for granted, the client does not need to trust the server in the same way as it trusts its parent. By invoking the capability, the client is in full control over the information it reveals to the server in the form of RPC arguments. The confidentiality and integrity of its internal state is protected. Furthermore, the invocation of a capability cannot have side effects on the client's protection domain other than the retrieval of RPC results. So the integrity of the client's internal state is protected. However, when invoking a capability, the client hands over the flow of execution to the server. The client is blocked until the server responds to the request. A misbehaving server may never respond and thereby block the client infinitely. Therefore, with respect to the liveliness of the client, the client has to trust the server. To empathize with the role of a component as a client, a capability invocation can be compared to the call of a function of an opaque 3rd-party library. When calling such a library function, the caller can never be certain to regain control. It just expects that a function returns at some point. However, in contrast to a call of a library function, a capability invocation does not put the integrity and confidentiality of the client's internal state at risk.
Servers do not trust their clients
When exercising the role of a server, a component should generally not trust its clients. On the contrary, from the server's perspective, clients should be expected to misbehave. This has two practical implications. First, a server is responsible for validating the arguments of incoming RPC requests. Second, a server should never make itself dependent on the good will of its clients. For example, a server should generally not invoke a capability obtained from one of its clients. A malicious client could have delegated a capability to a non-responding RPC object, which may block the server forever when invoked and thereby make the server unavailable for all clients. As another example, the server must always be in control over the physical memory resources used for a shared-memory interface between itself and its clients. Otherwise, if a client was in control over the used memory, it could revoke the memory from the server at any time, possibly triggering a fault at the server. The establishment of shared memory is described in detail in Section Shared memory. Similarly to the role as client, the internal state of a server is protected from its clients with respect to integrity and confidentiality. In contrast to a client, however, the liveliness of a server is protected as well. A server never needs to wait for any response from a client. By responding to an RPC request, the server does immediately become ready to accept the next RPC request without any prior handshake with the client of the first request.
Ownership and lifetime of a session
The object identity of a session RPC object and additional RPC objects that may have been created via the session is owned by the server. So the server is in control over the lifetime of those RPC objects. The client is not in the immediate position to dictate the server when to close a session because it has no power over the server. Instead, the procedure of closing a session follows the same chain of commands as involved in the session creation. The common parent of client and server plays the role of a broker, which is trusted by both parties. From the client's perspective, closing a session is a request to its parent. The client has to accept that the response to such a request is up to the policy of the parent. The closing of a session can alternatively be initiated by all nodes of the component tree that were involved in the session creation.
From the perspective of a server that is implemented by a child, the request to close a session originates from its parent, which, as the owner of the server, represents an authority that must be ultimately obeyed. If the server complies, the object identity of the session's RPC object vanishes. Since the kernel invalidates capabilities once their associated RPC object is destroyed, all capabilities referring to the RPC object - however delegated - are implicitly revoked as a side effect. Still, a server may ignore the session-close request. In this case, the parent of a server might take steps to enforce its will by destructing the server altogether.
Trustworthiness of servers
Servers that are shared by clients of different security levels must be designed and implemented with special care. Besides the correct response to session-close requests, another consideration is the adherence to the security policy as configured by the parent. The mere fact that a server is a child of its parent does not imply that the parent won't need to trust it in some respects.
In cases where is not viable to trust the server (e.g., because the server is based on ported software that is too complex for thorough evaluation), certain security properties such as the effectiveness of closing sessions could be enforced by a small (and thereby trustworthy) intermediate server that sits in-between the real server and the client. This intermediate server would then effectively wrap the server's session interface.