1 |
\documentclass[9pt,a4paper]{extarticle} |
\documentclass{book} |
2 |
%\usepackage{german} |
%\usepackage{german} |
3 |
%\usepackage[margin=2.5cm]{geometry} |
%\usepackage[margin=2.5cm]{geometry} |
4 |
|
|
9 |
\date{August 2003} |
\date{August 2003} |
10 |
|
|
11 |
\begin{document} |
\begin{document} |
12 |
|
|
13 |
\maketitle |
\maketitle |
|
\newpage |
|
14 |
\tableofcontents |
\tableofcontents |
|
\newpage |
|
15 |
\setlength{\parindent}{0pt} |
\setlength{\parindent}{0pt} |
16 |
\setlength{\parskip}{1ex plus 0.5ex minus 0.2ex} |
\setlength{\parskip}{1ex plus 0.5ex minus 0.2ex} |
17 |
|
|
18 |
\section{Introduction} |
\include{introduction} |
19 |
|
\include{booting} |
20 |
The GNU Hurd is a multi-server operating system running on top of a |
\include{ipc} |
21 |
microkernel (currently Mach variants). The core motivation of the |
\include{threads-tasks} |
22 |
Hurd is the following: |
\include{vmm} |
23 |
|
\include{authentication} |
24 |
\begin{quote} |
\include{posix} |
25 |
\emph{The operating system should enable its users to share the |
\include{debugging} |
26 |
resources of the system without harming each other.} |
\include{device-drivers} |
|
\end{quote} |
|
|
|
|
|
The focus is on the user, the system should try to allow the user to |
|
|
do anything that is not harmful for other users. Many operating |
|
|
systems either restrict what the user can do to be more secure, while |
|
|
others allow the user to do everything, but fail on protecting the |
|
|
users from each other effectively. |
|
|
|
|
|
The Hurd is designed to minimize the system code that the user is |
|
|
required to use, while allowing the user to use, ignore or replace the |
|
|
remaining system code, and this without harming other users. |
|
|
|
|
|
So while the L4 microkernel tries to minimize the policy that the |
|
|
kernel enforces on the software running on it, the Hurd tries to |
|
|
minimize the policy that the operating system enforces on its users. |
|
|
Furthermore, the Hurd also aims to provide a POSIX compatible general |
|
|
purpose operating system. However, this POSIX personality of the Hurd |
|
|
is provided for convenience only, and to make the Hurd useful. Other |
|
|
personalities can be implemented and used by the users of the system |
|
|
along with the POSIX personality. This default personality of the |
|
|
Hurd also provides some convenient features that allow the user to |
|
|
extend the system so that all POSIX compatible programs can take |
|
|
advantage of it. |
|
|
|
|
|
These notes are a moving target in the effort to find the best |
|
|
strategy to port the Hurd to the L4 microkernel. |
|
|
|
|
|
\begin{comment} |
|
|
Remarks about the history of a certain feature and implementation |
|
|
details are set in a smaller font and separated from the main text, |
|
|
just like this paragraph. Because this is work in progress, there |
|
|
are naturally a lot of such comments. |
|
|
\end{comment} |
|
|
|
|
|
|
|
|
\section{Booting} |
|
|
|
|
|
A multiboot-compliant bootloader, for example GNU GRUB, loads the |
|
|
loader program \texttt{laden}, the kernel, $\sigma_0$, the rootserver |
|
|
and further modules. The loader is started, patches the kernel |
|
|
interface page, and starts the kernel. The kernel starts $\sigma_0$ |
|
|
and the rootserver. The rootserver has to deal with the other |
|
|
modules. |
|
|
|
|
|
|
|
|
\subsection{System bootstrap} |
|
|
|
|
|
The initial part of the boot procedure is system specific. |
|
|
|
|
|
|
|
|
\subsubsection{Booting the ia32} |
|
|
|
|
|
On the ia32, the BIOS will be one of the first things to run. |
|
|
Eventually, the BIOS will start the bootloader. The Hurd requires a |
|
|
multiboot-compliant bootloader, such as GNU GRUB. A typical |
|
|
configuration file entry in the \verb/menu.list/ file of GNU GRUB will |
|
|
look like this: |
|
|
|
|
|
\begin{verbatim} |
|
|
title = The GNU Hurd on L4 |
|
|
root = (hd0,0) |
|
|
kernel = /boot/laden |
|
|
module = /boot/ia32-kernel |
|
|
module = /boot/sigma0 |
|
|
module = /boot/rootserver |
|
|
module = ...more servers... |
|
|
\end{verbatim} |
|
|
|
|
|
\begin{comment} |
|
|
The name of the rootserver and the further modules are not specified |
|
|
yet. |
|
|
\end{comment} |
|
|
|
|
|
GNU GRUB loads the binary image files into memory and jumps to the |
|
|
entry point of \texttt{laden}. |
|
|
|
|
|
|
|
|
\subsection{The loader \texttt{laden}} |
|
|
|
|
|
\texttt{laden} is a multiboot compliant kernel from the perspective of |
|
|
GNU GRUB. It expects at least three modules. The first module is the |
|
|
L4 kernel image, the second module is the $\sigma_0$ server image, and |
|
|
the third module is the rootserver image. |
|
|
|
|
|
\begin{comment} |
|
|
Later, the L4 kernel will support the optional UTCB paging server |
|
|
$\sigma_1$, which has to be treated like the other initial servers |
|
|
by \texttt{laden}. A command line option to \texttt{laden} will |
|
|
allow the user to specify if the third module is the rootserver or |
|
|
$\sigma_1$. If $\sigma_1$ is used, the rootserver is the fourth |
|
|
module in the list. |
|
|
\end{comment} |
|
|
|
|
|
\texttt{laden} copies (or moves) the three executable images to the |
|
|
right location in memory, according to their respective ELF headers. |
|
|
It also initializes the BSS section to zero. |
|
|
|
|
|
\begin{comment} |
|
|
Laden has to deal with overlapping source and destination memory |
|
|
areas in an intelligent way. It currently will detect such |
|
|
situations, but is not always able to find a solution, even if one |
|
|
exists. |
|
|
|
|
|
If a memory area stretches out to the very last page addressible in |
|
|
32 bit, the high address of the memory descriptor will overflow. |
|
|
This is in fact the behaviour of \texttt{kickstart}. \texttt{laden} |
|
|
currently truncates such an area by one page. This needs |
|
|
clarification in the L4 standard. |
|
|
\end{comment} |
|
|
|
|
|
Then it searches for the kernel interface page (KIP) in the L4 kernel |
|
|
image and modifies it in the following way: |
|
|
|
|
|
\begin{itemize} |
|
|
\item The memory descriptors are filled in according to the memory |
|
|
layout of the system. On ia32, this information is -- at least |
|
|
partially -- provided by GNU GRUB. |
|
|
|
|
|
\begin{comment} |
|
|
GNU GRUB seems to omit information about the memory that is shared |
|
|
with the VGA card. \texttt{laden} creates a special entry for |
|
|
that region, overriding any previous memory descriptor. |
|
|
\end{comment} |
|
|
|
|
|
\item The start and end addresses and the entry point of the initial |
|
|
servers are filled in. |
|
|
|
|
|
\begin{comment} |
|
|
A future version of L4 should support adding information about the |
|
|
UTCB area of the initial rootserver as well. Until then, the |
|
|
rootserver has no clean way to create a new thread (a hack is used |
|
|
by the rootserver to calculate the UTCB addresses for other |
|
|
threads). |
|
|
\end{comment} |
|
|
|
|
|
\item The \verb/boot_info/ field is initialized. |
|
|
|
|
|
\begin{comment} |
|
|
The \verb/boot_info/ field is currently set to the GNU GRUB |
|
|
\verb/multiboot_info/ structure. This only works for the ia32 |
|
|
architecture of course. We might want to have a more architecture |
|
|
independent way to pass the information about further modules to |
|
|
the rootserver. We also might want to gather the information |
|
|
provided by GNU GRUB in a single page (if it is not). |
|
|
\end{comment} |
|
|
\end{itemize} |
|
|
|
|
|
|
|
|
\subsection{The L4 kernel} |
|
|
|
|
|
The L4 kernel initializes itself and then creates the address spaces |
|
|
and threads for the initial servers $\sigma_0$ and the rootserver. It |
|
|
maps all physical memory idempotently into $\sigma_0$, and sets the |
|
|
pager of the rootserver thread to $\sigma_0$. Then it starts the |
|
|
initial servers. |
|
|
|
|
|
|
|
|
\subsection{The initial server $\sigma_0$} |
|
|
|
|
|
$\sigma_0$ acts as the pager for the rootserver, answering page fault |
|
|
messages by mapping the page at the fault address idempotently in the |
|
|
rootserver. |
|
|
|
|
|
\begin{comment} |
|
|
$\sigma_0$ can also be used directly by sending messages to it, |
|
|
according to the $\sigma_0$ RPC protocol. This is used by the kernel |
|
|
to allocate reserved memory, but can also be used by the user to |
|
|
explicitely allocate more memory than single pages indirectly via |
|
|
page faults. |
|
|
\end{comment} |
|
|
|
|
|
The thread ID of $\sigma_0$ is (\verb/UserBase, 1)/. |
|
|
|
|
|
\begin{comment} |
|
|
We will write all thread IDs in the form (\verb/thread nr/, |
|
|
\verb/version/). |
|
|
\end{comment} |
|
|
|
|
|
Any fpage will only be provided to one thread. $\sigma_0$ will return |
|
|
an error if another thread attempts to map or manipulate an fpage that |
|
|
has already been given to some other thread, even if both threads |
|
|
reside in the same address space. |
|
|
|
|
|
|
|
|
\subsection{The initial server $\sigma_1$} |
|
|
|
|
|
$\sigma_1$ is intended to provide a paging service for UTCB memory. |
|
|
This will allow orthogonal persistence to be implemented. It is not |
|
|
yet supported. |
|
|
|
|
|
The thread ID of $\sigma_1$ is (\verb/UserBase + 1, 1)/. |
|
|
|
|
|
|
|
|
\subsection{The rootserver} |
|
|
\label{rootserver} |
|
|
|
|
|
The rootserver is the only task in the system which threads can |
|
|
perform privileged system calls. So the rootserver must provide |
|
|
wrappers for the system calls to other unprivileged system tasks. |
|
|
|
|
|
\begin{comment} |
|
|
For this, a simple authentication scheme is required. The |
|
|
rootserver can keep a small, statically allocated table of threads |
|
|
which are granted access to the system call wrappers. The caller |
|
|
could provide the index in the table for fast O(1) lookup instead |
|
|
linear search. Threads with access could be allowed to add other |
|
|
threads or change existing table entries. The same scheme can be |
|
|
used in the device driver framework. |
|
|
|
|
|
The rootserver should have one thread per CPU, and run at a high |
|
|
priority. |
|
|
\end{comment} |
|
|
|
|
|
The rootserver has the following initial state: |
|
|
|
|
|
\begin{itemize} |
|
|
\item Its thread ID is (\verb/UserBase + 2/, 1). |
|
|
|
|
|
\item The priority is set to the 255, the maximum value. |
|
|
|
|
|
\begin{comment} |
|
|
The rootserver, or at least the system call wrapper, should run at |
|
|
a very high priority. |
|
|
\end{comment} |
|
|
|
|
|
\item The instruction pointer \verb/%eip/ is set to the entry point, |
|
|
all other registers are undefined (including the stack pointer). |
|
|
|
|
|
\item The pager is set to $\sigma_0$. |
|
|
|
|
|
\item The exception handler set to \verb/nilthread/. |
|
|
|
|
|
\item The scheduler is set to the rootserver thread itself. |
|
|
\end{itemize} |
|
|
|
|
|
So the first thing the rootserver has to do is to set up a simple |
|
|
stack. |
|
|
|
|
|
Then the rootserver should evaluate the \verb/boot_info/ field in the |
|
|
KIP to find the information about the other modules. It should parse |
|
|
the information and create the desired initial tasks of the operating |
|
|
system. The Hurd uses a boot script syntax to allow to pass |
|
|
information about other initial tasks and the root tasks to each |
|
|
initial task in a generalized manner. |
|
|
|
|
|
\begin{comment} |
|
|
The exact number and type of initial tasks necessary to boot the |
|
|
Hurd are not yet known. Chances are that this list includes the |
|
|
\texttt{task} server, the physical memory server, the device |
|
|
servers, and the boot filesystem. The boot filesystem might be a |
|
|
small simple filesystem, which also includes the device drivers |
|
|
needed to access the real root filesystem. |
|
|
\end{comment} |
|
|
|
|
|
|
|
|
\subsection{The physical memory server} |
|
|
|
|
|
To be written. |
|
|
|
|
|
\begin{comment} |
|
|
In fact, I already have some ideas. Here they are: |
|
|
|
|
|
The rootserver copies (or moves) the physical memory server |
|
|
executable image to the right location in memory, according to its |
|
|
respective ELF header. It also initializes the BSS section to zero. |
|
|
|
|
|
Then it follows the \texttt{exec()} protocol to startup the new |
|
|
task. This should be done as transparently as possible. All pages |
|
|
the rootserver provides because of page faults should be granted. |
|
|
The rootserver waits for the physical memory server to contact the |
|
|
rootserver thread. Then the following startup protocol is walked |
|
|
through: |
|
|
|
|
|
\begin{enumerate} |
|
|
\item The physical memory server requests all system memory from the |
|
|
rootserver. The rootserver maps the memory from $\sigma_0$ and |
|
|
grants it to the physical memory server. Alternatively, the |
|
|
physical memory server might get the memory directly from |
|
|
$\sigma_0$, but it should ask the rootserver for the amount and |
|
|
location of memory to get. |
|
|
|
|
|
\item For each module that has not been used yet, the rootserver |
|
|
requests a capability in the physical memory server that can be |
|
|
used to map in pages from the range of memory that the module |
|
|
occupies. These capabilities should implement the same pager |
|
|
interface that mappable files implement. |
|
|
|
|
|
The idea is that these capabilities can be used in the |
|
|
\texttt{exec()} protocol to start up the tasks for these modules. |
|
|
If a module is not a task, the capability can be used to access |
|
|
the module data by mapping it into the address space like a file. |
|
|
The physical memory server can even swap out pages that back these |
|
|
objects on memory pressure. |
|
|
|
|
|
So, the physical memory server is in fact a simple filesystem for |
|
|
these initial tasks, usable only for mapping operations. |
|
|
|
|
|
\item The rootserver can then start up the other tasks in the module |
|
|
list using the normal \texttt{exec()} protocol. |
|
|
\end{enumerate} |
|
|
|
|
|
The result is that all tasks except for the rootserver can be |
|
|
started like normal Hurd tasks, and can also be swapped out. |
|
|
\end{comment} |
|
|
|
|
|
|
|
|
\section{Inter-process communication (IPC)} |
|
|
\label{ipc} |
|
|
|
|
|
The Hurd requires a capability system. Capabilities are used to proof |
|
|
your identity to other servers (authentication), and access |
|
|
server-side implemented objects like devices, files, directories, |
|
|
terminals, and other things. The server can use a capability for |
|
|
whatever it wants. Capabilities provide interfaces. Interfaces can |
|
|
be invoked by sending messages to the capability. In L4, this means |
|
|
that a message is sent to a thread in the server providing the |
|
|
capability, with the identifier for the capability in the message. |
|
|
|
|
|
Capabilities are protected objects. Access to a capability needs to |
|
|
be granted by the server. Once you have a capability, you can copy it |
|
|
to other tasks (if the server permits it, which is usually the case). |
|
|
In the Hurd, access to capabilities is always granted to a whole task, |
|
|
not to individual threads. |
|
|
|
|
|
\begin{comment} |
|
|
There is no reason for the server not to permit it, because the |
|
|
holder of the capability could also just act as a proxy for the |
|
|
intended receiver instead copying the capability to it. The |
|
|
operation might fail anyway, for example because of resource |
|
|
shortage, in particular if the server puts a quota on the number of |
|
|
capabilities a user can hold. |
|
|
\end{comment} |
|
|
|
|
|
Capabilities provide two essential services to the Hurd. They are |
|
|
used to restrict access to a server function, and they are the |
|
|
standard interface the components in the Hurd use to communicate with |
|
|
each others. Thus, it is important that their implementation is fast |
|
|
and secure. |
|
|
|
|
|
\begin{comment} |
|
|
There are several ways to implement such a capability system. A |
|
|
more traditional design would be a global, trusted capability server |
|
|
that provides capabilities to all its users. The L4 redirector |
|
|
could be used to reroute all client traffic automatically through |
|
|
this server. This approach has several disadvantages: |
|
|
|
|
|
\begin{itemize} |
|
|
\item It adds a lot of overhead to every single RPC, because all |
|
|
traffic has to be routed through the capability server, which must |
|
|
then perform the authentication on the server's behalf. |
|
|
\item It would be difficult to copy a capability to another task. |
|
|
Either the cap server would have to provide interfaces for clients |
|
|
to do it, or it would be have to know the message format for every |
|
|
interface and do it automatically. |
|
|
\item It would be a single point of failure. If it had a bug and |
|
|
crashed, the whole system would be affected. |
|
|
\item Users could not avoid it, it would be enforced system code. |
|
|
\item It is inflexible. It would be hard to replace or extend at |
|
|
run-time. |
|
|
\end{itemize} |
|
|
|
|
|
Another approach is taken by CORBA with IORs. IORs contain long |
|
|
random numbers which allow the server to identify a user of an |
|
|
object. This approach is not feasible for the following reasons: |
|
|
|
|
|
\begin{itemize} |
|
|
\item Even good random numbers can be guessed. Long enough random |
|
|
numbers can reduce the likelihood to arbitrary small numbers, |
|
|
though (below the probability of a hardware failure). |
|
|
\item Good random numbers are in short supply, and is slow to |
|
|
generate. Good pseudo random is faster, but it is still difficult |
|
|
to generate. The random number generator would become a critical |
|
|
part of the operating system. |
|
|
\item The random number had to be transfered in every single |
|
|
message. Because it would have to be long, it would have a |
|
|
significant negative impact on IPC performance. |
|
|
\end{itemize} |
|
|
\end{comment} |
|
|
|
|
|
The Hurd implements the capability system locally in each task. A |
|
|
common default implementation will be shared by all programs. |
|
|
However, a malicious untrusted program can do nothing to disturb the |
|
|
communication of other tasks. A capability is identified in the |
|
|
client task by the server thread and a local identifier (which can be |
|
|
different from client to client). The server thread will receive |
|
|
messages for the capabilities. The first argument in the message is |
|
|
the capability identifier. Although every task can get different IDs |
|
|
for the same capability, a well-behaving server will give the same ID |
|
|
to a client which already has a capability and gets the same |
|
|
capability from another client. So clients can compare capability IDs |
|
|
from the server numerically to check if two capabilities are the same, |
|
|
but only if one of the two IDs is received while the client already |
|
|
had the other one. |
|
|
|
|
|
Because access to a capability must be restricted, the server needs to |
|
|
be careful in only allowing registered and known users to access the |
|
|
capability. For this, the server must be sure that it can determine |
|
|
the sender of a message. In L4, this is easy on the surface: The |
|
|
kernel provides the receiving thread with the sender's thread ID, |
|
|
which also contains the task ID in the version field. However, the |
|
|
server must also know for sure if this task is the same task that it |
|
|
gave access to the capability. Comparing the task IDs numerically is |
|
|
not good enough, the server must also somehow have knowledge or |
|
|
influence on how task IDs are reused when tasks die and are created. |
|
|
|
|
|
The same is true for the client, of course, which trusts the server |
|
|
and thus must be sure that it is not tricked into trusting on |
|
|
unreliable data from an imposter, or sends sensitive data to it. |
|
|
|
|
|
\begin{comment} |
|
|
The \texttt{task} server wants to reuse thread numbers because that |
|
|
makes best use of kernel memory. Reusing task IDs, the version |
|
|
field of a thread ID, is not so important, but there are only 14 |
|
|
bits for the version field (and the lower six bits must not be all |
|
|
zero). So a thread ID is bound to be reused eventually. |
|
|
|
|
|
Using the version field in a thread ID as a generation number is not |
|
|
good enough, because it is so small. Even on 64-bit architectures, |
|
|
where it is 32 bit long, it can eventually overflow. |
|
|
\end{comment} |
|
|
|
|
|
The best way to prevent that a task can be tricked into talking to an |
|
|
imposter is to have the \texttt{task} server notify the task if the |
|
|
communication partner dies. The \texttt{task} server must guarantee |
|
|
that the task ID is not reused until all tasks that got such a |
|
|
notification acknowledge that it is processed, and thus no danger of |
|
|
confusion exists anymore. |
|
|
|
|
|
The \texttt{task} server provides references to task IDs in form of |
|
|
\emph{task info capabilities}. If a task has a task info capability |
|
|
for another task, it prevents that this other task's task ID is reused |
|
|
even if that task dies, and it also makes sure that task death |
|
|
notifications are delivered in that case. |
|
|
|
|
|
\begin{comment} |
|
|
Because only the \texttt{task} server can create and destroy tasks, |
|
|
and assign task IDs, there is no need to hold such task info |
|
|
capabilities for the \texttt{task} server, nor does the |
|
|
\texttt{task} server need to hold task info capabilities for its |
|
|
clients. This avoids the obvious bootstrap problem in providing |
|
|
capabilities in the \texttt{task} server. This will even work if |
|
|
the \texttt{task} server is not the real \texttt{task} server, but a |
|
|
proxy task server (see section \ref{proxytaskserver} on page |
|
|
\pageref{proxytaskserver}). |
|
|
\end{comment} |
|
|
|
|
|
As task IDs are a global resource, care has to be taken that this |
|
|
approach does not allow for a DoS-attack by exhausting the task ID |
|
|
number space, see section \ref{taskinfocap} on page |
|
|
\pageref{taskinfocap} for more details. |
|
|
|
|
|
|
|
|
\subsection{Capabilities} |
|
|
|
|
|
This subsection contains implementation details about capabilities. |
|
|
|
|
|
A server will usually operate on objects, and not capabilities. In |
|
|
the case of a filesystem, this could be file objects, for example. |
|
|
|
|
|
\begin{comment} |
|
|
In the Hurd, filesystem servers have to keep different objects for |
|
|
each time a file is looked up (or ``opened''), because some state, |
|
|
for example authentication, open flags and record locks, are |
|
|
associated not with the file directly, but with this instance of |
|
|
opening the file. Such a state structure (``credential'') will also |
|
|
contain a pointer and reference to the actual file node. For |
|
|
simplicity, we will assume that the capability is associated with a |
|
|
file node directly. |
|
|
\end{comment} |
|
|
|
|
|
To provide access to the object to another task, the server creates a |
|
|
capability, and associates it with the object (by setting a hook |
|
|
variable in the capability). From this capability, the server can |
|
|
either create send references to itself, or to other tasks. If the |
|
|
server creates send references for itself, it can use the capability |
|
|
just as it can use capabilities implemented by other servers. This |
|
|
makes access to locally and remotely implemented capabilities |
|
|
identical. If you write code to work on capabilities, it can be used |
|
|
for remote objects as well as for local objects. |
|
|
|
|
|
If the server creates a send reference for another task (a client), a |
|
|
new capability ID will be created for this task. This ID will only be |
|
|
valid for this task, and should be returned to the client. |
|
|
|
|
|
The client itself will create a capability object from this capability |
|
|
ID. The capability will also contain information about the server, |
|
|
for example the server thread which should be used for sending |
|
|
messages to the capability. |
|
|
|
|
|
If the client wants to send a message, it will send it to the provided |
|
|
server thread, and use the capability ID it got from the server as the |
|
|
first argument in the RPC. The server receives the message, and now |
|
|
has to look up the capability ID in the list of capabilties for this |
|
|
task. |
|
|
|
|
|
\begin{comment} |
|
|
The server knows the task ID from the version field of the sender's |
|
|
thread ID. It can look up the list of capabilities for this task in |
|
|
a hash table. The capability ID can be an index into an array, so |
|
|
the server only needs to perform a range check. This allows to |
|
|
verify quickly that the user is allowed to access the object. |
|
|
|
|
|
This is not enough if several systems run in parallel on the same |
|
|
host. Then the version ID for the threads in the other systems will |
|
|
not be under the control of the Hurd's \texttt{task} server, and can |
|
|
thus not be trusted. The server can still use the version field to |
|
|
find out the task ID, which will be correct \emph{if the thread is |
|
|
part of the same subsystem}. It also has to verify that the |
|
|
thread belongs to this subsystem. Hopefully the subsystem will be |
|
|
encoded in the thread ID. Otherwise, the \texttt{task} server has |
|
|
to be consulted (and, assuming that thread numbers are not shared by |
|
|
the different systems, the result can be cached). |
|
|
\end{comment} |
|
|
|
|
|
The server reads out the capability associated with the capability ID, |
|
|
and invokes the server stub according to the message ID field in the |
|
|
message. |
|
|
|
|
|
After the message is processed, the server sends it reply to the |
|
|
sender thread with a zero timeout. |
|
|
|
|
|
\begin{comment} |
|
|
Servers must never block on sending messages to clients. Even a |
|
|
small timeout can be used for DoS-attacks. The client can always |
|
|
make sure that it receives the reply by using a combined send and |
|
|
receive operation together with an infinite timeout. |
|
|
\end{comment} |
|
|
|
|
|
The above scheme assumes that the server and the client already have |
|
|
task info caps for the respective other task. This is the normal |
|
|
case, because acquiring these task info caps is part of the protocol |
|
|
that is used when a capability is copied from one task to another. |
|
|
|
|
|
|
|
|
\subsubsection{Bootstrapping a client-server connection} |
|
|
\label{ipcbootstrap} |
|
|
|
|
|
If the client and the server do not know about each other yet, then |
|
|
they can bootstrap a connection without support from any other task |
|
|
except the \texttt{task} server. The purpose of the initial handshake |
|
|
is to give both participants a chance to acquire a task info cap for |
|
|
the other participants task ID, so they can be sure that from there on |
|
|
they will always talk to the same task as they talked to before. |
|
|
|
|
|
\paragraph{Preconditions} |
|
|
The client knows the thread ID of the server thread that receives and |
|
|
processes the bootstrap messages. Some other task might hold a task |
|
|
info capability to the server the client wants to connect to. |
|
|
|
|
|
\begin{comment} |
|
|
If no such other tasks exists, the protocol will still work. |
|
|
However, the client might not get a connection to the server that |
|
|
run at the time the client started the protocol, but rather to the |
|
|
server that run at the time the client acquired the task info cap |
|
|
for the server's task ID (after step 1 below). |
|
|
|
|
|
This is similar to how sending signals works in Unix: Technically, |
|
|
at the time you write \texttt{kill 203}, and press enter, you do not |
|
|
know if the process with the PID 203 you thought of will receive the |
|
|
signal, or some other process that got the PID in the time between |
|
|
you getting the information about the PID and writing the |
|
|
\texttt{kill}-command. |
|
|
\end{comment} |
|
|
|
|
|
FIXME: Here should be the pseudo code for the protocol. For now, you |
|
|
have to take it out of the long version. |
|
|
|
|
|
\begin{enumerate} |
|
|
|
|
|
\item The client acquires a task info capability for the server's task |
|
|
ID, either directly from the \texttt{task} server, or from another |
|
|
task in a capability copy. From that point on, the client can be |
|
|
sure to always talk to the same task when talking to the server. |
|
|
|
|
|
Of course, if the client already has a task info cap for the server |
|
|
it does not need to do anything in this step. |
|
|
|
|
|
\begin{comment} |
|
|
As explained above, if the client does not have any other task |
|
|
holding the task info cap already, it has no secure information |
|
|
about what this task is for which it got a task info cap. |
|
|
\end{comment} |
|
|
|
|
|
\item The client sends a message to the server, requesting the initial |
|
|
handshake. |
|
|
|
|
|
\item The server receives the message, and acquires a task info cap |
|
|
for the client task (directly from the \texttt{task} server). |
|
|
|
|
|
Of course, if the server already has a task info cap for the client |
|
|
it does not need to do anything in this step. |
|
|
|
|
|
\begin{comment} |
|
|
At this point, the server knows that future messages from this task |
|
|
will come from the same task as it got the task info cap for. |
|
|
However, it does not know that this is the same task that sent the |
|
|
initial handshake request in step 2 above. This shows that there is |
|
|
no sense in verifying the task ID or perform any other |
|
|
authentication before acquiring the task info cap. |
|
|
\end{comment} |
|
|
|
|
|
\item The server replies to the initial handshake request with an |
|
|
empty reply message. |
|
|
|
|
|
\begin{comment} |
|
|
Because the reply now can go to a different task than the request |
|
|
came from, sending the reply might fail. It might also succeed and |
|
|
be accepted by the task that replaced the requestor. Or it might |
|
|
succeed normally. The important thing is that it does not matter to |
|
|
the server at all. It would have provided the same ``service'' to |
|
|
the ``imposter'' of the client, if he had bothered to do the |
|
|
request. As no authentication is done yet, there is no point for |
|
|
the server to bother. |
|
|
|
|
|
This means however, that the server needs to be careful in not |
|
|
consuming too many resources for this service. However, this is |
|
|
easy to achieve. Only one task info cap per client task will ever |
|
|
be held in the server. The server can either keep it around until |
|
|
the task dies (and a task death notification is received), or it can |
|
|
clean it up after some timeout if the client does not follow up and |
|
|
do some real authentication. |
|
|
\end{comment} |
|
|
|
|
|
\item The client receives the reply message to its initial handshake |
|
|
request. |
|
|
|
|
|
\item The client sends a request to create its initial capability. |
|
|
How this request looks depends on the type of the server and the |
|
|
initial capabilities it provides. Here are some examples: |
|
|
|
|
|
\begin{itemize} |
|
|
\item A filesystem might provide an unauthenticated root directory |
|
|
object in return of the underlying node capability, which is |
|
|
provided by the parent filesystem and proves to the filesystem |
|
|
that the user was allowed to look up the root node of this |
|
|
filesystem (see section \ref{xfslookup} on page |
|
|
\pageref{xfslookup}). |
|
|
|
|
|
\begin{comment} |
|
|
In this example, the parent filesystem will either provide the |
|
|
task info cap for the child filesystem to the user, or it will |
|
|
hold the task info cap while the user is creating their own |
|
|
(which the user has to verify by repeating the lookup, though). |
|
|
Again, see section \ref{xfslookup} on page \pageref{xfslookup}. |
|
|
|
|
|
The unauthenticated root directory object will then have the be |
|
|
authenticated using the normal reauthentication mechanism (see |
|
|
section \ref{auth} on pageref{auth}). This can also be combined |
|
|
in a single RPC. |
|
|
\end{comment} |
|
|
|
|
|
\item Every process acts as a server that implements the signal |
|
|
capability for this process. Tasks who want to send a signal to |
|
|
another task can perform the above handshake, and then provide |
|
|
some type of authentication capability that indicates that they |
|
|
are allowed to send a signal. Different authentication |
|
|
capabilities can be accepted by the signalled task for different |
|
|
types of signals. |
|
|
|
|
|
\begin{comment} |
|
|
The Hurd used to store the signal capability in the proc server, |
|
|
where authorized tasks could look it up. This is no longer |
|
|
possible because a server can not accept capabilities |
|
|
implemented by untrusted tasks, see below. |
|
|
\end{comment} |
|
|
\end{itemize} |
|
|
|
|
|
\item The server replies with whatever capability the client |
|
|
requested, provided that the client could provide the necessary |
|
|
authentication capabilities, if any. |
|
|
|
|
|
\begin{comment} |
|
|
It is not required that the server performs any authentication at |
|
|
all, but it is recommended, and all Hurd servers will do so. |
|
|
|
|
|
In particular, the server should normally only allow access from |
|
|
tasks running in the same system, if running multiple systems on |
|
|
the same host is possible. |
|
|
\end{comment} |
|
|
\end{enumerate} |
|
|
|
|
|
\paragraph{Result} |
|
|
The client has a task info capability for the server and an |
|
|
authenticated capability. The server has a task info capability for |
|
|
the client and seen some sort of authentication for the capability it |
|
|
gave to the client. |
|
|
|
|
|
\begin{comment} |
|
|
If you think that the above protocol is complex, you have seen |
|
|
nothing yet! Read on. |
|
|
\end{comment} |
|
|
|
|
|
|
|
|
\subsubsection{Returning a capability from a server to a client} |
|
|
|
|
|
Before we go on to the more complex case of copying a capability from |
|
|
one client to another, let us point out that once a client has a |
|
|
capability from a server, it is easy for the server to return more |
|
|
capabilities it implements to the client. |
|
|
|
|
|
The server just needs to create the capability, acquire a capability |
|
|
ID in the client's cap ID space, and return the information in the |
|
|
reply RPC. |
|
|
|
|
|
FIXME: Here should be the pseudo code for the protocol. For now, you |
|
|
have to take it out of the long version. |
|
|
|
|
|
\begin{comment} |
|
|
The main point of this section is to point out that only one task |
|
|
info capability is required to protect all capabilities provided to |
|
|
a single task. The protocols described here always assume that no |
|
|
task info caps are held by anyone (except those mentioned in the |
|
|
preconditions). In reality, sometimes the required task info caps |
|
|
will already be held. |
|
|
\end{comment} |
|
|
|
|
|
|
|
|
\subsubsection{Copying a capability from one client to another task} |
|
|
|
|
|
The most complex operation in managing capabilities is to copy or move |
|
|
a capability from the client to another task, which subsequently |
|
|
becomes a client of the server providing the capability. The |
|
|
difficulty here lies in the fact that the protocol should be fast, but |
|
|
also robust and secure. If any of the participants dies unexpectedly, |
|
|
or any of the untrusted participants is malicious, the others should |
|
|
not be harmed. |
|
|
|
|
|
\paragraph{Preconditions} |
|
|
The client $C$ has a capability from server $S$ (this implies that $C$ |
|
|
has a task info cap for $S$ and $S$ has a task info cap for $C$). It |
|
|
wants to copy the capability to the destination task $D$. For this, |
|
|
it will have to make RPCs to $D$, so $C$ has also a capability from |
|
|
$D$ (this implies that $C$ has a task info cap for $D$ and $D$ has a |
|
|
task info cap for $C$). Of course, the client $C$ trusts its servers |
|
|
$S$ and $D$. $D$ might trust $S$ or not, and thus accept or reject |
|
|
the capability that $C$ wants to give to $D$. $S$ does not trust |
|
|
either $C$ or $D$. |
|
|
|
|
|
The \texttt{task} server is also involved, because it provides the |
|
|
task info capabilities. Everyone trusts the \texttt{task} server they |
|
|
use. This does not need to be the same one for every participant. |
|
|
|
|
|
FIXME: Here should be the pseudo code for the protocol. For now, you |
|
|
have to take it out of the long version. |
|
|
|
|
|
\begin{enumerate} |
|
|
\item The client invokes the \verb/cap_ref_cont_create/ RPC on the |
|
|
capability, providing the task ID of the intended receiver $D$ of |
|
|
the capability. |
|
|
|
|
|
\item The server receives the \verb/cap_ref_cont_create/ RPC from the |
|
|
client. It requests a task info cap for $D$ from its trusted task |
|
|
server, under the constraint that $C$ is still living. |
|
|
|
|
|
\begin{comment} |
|
|
A task can provide a constraint when creating a task info cap in |
|
|
the \texttt{task} server. The constraint is a task ID. The task |
|
|
server will only create the task info cap and return it if the |
|
|
task with the constraint task ID is not destroyed. This allows |
|
|
for a task requesting a task info capability to make sure that |
|
|
another task, which also holds this task info cap, is not |
|
|
destroyed. This is important, because if a task is destroyed, all |
|
|
the task info caps it held are released. |
|
|
|
|
|
In this case, the server relies on the client to hold a task info |
|
|
cap for $D$ until it established its own. See below for what can |
|
|
go wrong if the server would not provide a constraint and both, |
|
|
the client and the destination task would die unexpectedly. |
|
|
\end{comment} |
|
|
|
|
|
Now that the server established its own task info cap for $D$, it |
|
|
creates a reference container for $D$, that has the following |
|
|
properties: |
|
|
|
|
|
\begin{itemize} |
|
|
\item The reference container has a single new reference for the |
|
|
capability. |
|
|
|
|
|
\item The reference container has an ID that is unique among all |
|
|
reference container IDs for the client $C$. |
|
|
|
|
|
\item The reference container is associated with the client $C$. If |
|
|
$C$ dies, and the server processes the task death notification for |
|
|
it, the server will destroy the reference container and release |
|
|
the capability reference it has (if any). All resources |
|
|
associated with the reference container will be released. If this |
|
|
reference container was the only reason for $S$ to hold the task |
|
|
info cap for $D$, the server will also release the task info cap |
|
|
for $D$. |
|
|
|
|
|
\item The reference container is also associated with the |
|
|
destination task $D$. If $D$ dies, and the server processes the |
|
|
task death notification for it, the server will release the |
|
|
capability reference that is in the reference container (if any). |
|
|
It will not destroy the part of the container that is associated |
|
|
with $C$. |
|
|
\end{itemize} |
|
|
|
|
|
The server returns the reference container ID $R$ to the client. |
|
|
|
|
|
\item The client receives the reference container ID $R$. |
|
|
|
|
|
\begin{comment} |
|
|
If several capabilities have to be copied in one message, the |
|
|
above steps need to be repeated for each capability. With |
|
|
appropriate interfaces, capabilities could be collected so that |
|
|
only one call per server has to be made. We are assuming here |
|
|
that only one capability is copied. |
|
|
\end{comment} |
|
|
|
|
|
\item The client sends the server thread ID $T$ and the reference |
|
|
container ID $R$ to the destination task $D$. |
|
|
|
|
|
\item The destination task $D$ receives the server thread ID $T$ and |
|
|
the reference container ID $R$ from $C$. |
|
|
|
|
|
It now inspects the server thread ID $T$, and in particular the task |
|
|
ID component of it. $D$ has to make the decision if it trusts this |
|
|
task to be a server for it, or if it does not trust this task. |
|
|
|
|
|
If $D$ trusts $C$, it might decide to always trust $T$, too, |
|
|
irregardless of what task contains $T$. |
|
|
|
|
|
If $D$ does not trust $C$, it might be more picky about the task |
|
|
that contains $T$. This is because $D$ will have to become a client |
|
|
of $T$, so it will trust it. For example, it will block on messages |
|
|
it sends to $T$. |
|
|
|
|
|
\begin{comment} |
|
|
If $D$ is a server, it will usually only accept capabilities from |
|
|
its client that are provided by specific other servers it trusts. |
|
|
This can be the authentication server, for example (see section |
|
|
\ref{auth} on page \pageref{auth}). |
|
|
|
|
|
Usually, the type of capability that $D$ wants to accept from $C$ |
|
|
is then further restricted, and only one possible trusted server |
|
|
implements that type of capabilities. Thus, $D$ can simply |
|
|
compare the task ID of $T$ with the task ID of its trusted server |
|
|
(authentication server, ...) to make the decision if it wants to |
|
|
accept the capability or not. |
|
|
\end{comment} |
|
|
|
|
|
If $D$ does not trust $T$, it replies to $C$ (probably with an error |
|
|
value indicating why the capability was not accepted). In that |
|
|
case, jump to step \ref{copycapout}. |
|
|
|
|
|
Otherwise, it requests a task info cap for $S$ from its trusted task |
|
|
server, under the constraint that $C$ is still living. |
|
|
|
|
|
Then $D$ sends a \verb/cap_ref_cont_accept/ RPC to the server $S$, |
|
|
providing the task ID of the client $C$ and the reference container |
|
|
ID $R$. |
|
|
|
|
|
\begin{comment} |
|
|
\verb/cap_ref_cont_accept/ is one of the few interfaces that is not |
|
|
sent to a (real) capability, of course. Nevertheless, it is part of |
|
|
the capability object interface, hence the name. You can think of |
|
|
it as a static member in the capability class, that does not require |
|
|
an instance of the class. |
|
|
\end{comment} |
|
|
|
|
|
\item The server receives the \verb/cap_ref_cont_accept/ RPC from the |
|
|
destination task $D$. It verifies that a reference container exists |
|
|
with the ID $R$, that is associated with $D$ and $C$. |
|
|
|
|
|
\begin{comment} |
|
|
The server will store the reference container in data structures |
|
|
associated with $C$, under an ID that is unique but local to $C$. |
|
|
So $D$ needs to provide both information, the task ID and the |
|
|
reference container ID of $C$. |
|
|
\end{comment} |
|
|
|
|
|
If that is the case, it takes the reference from the reference |
|
|
container, and creates a capability ID for $D$ from it. The |
|
|
capability ID for $D$ is returned in the reply message. |
|
|
|
|
|
From that moment on, the reference container is deassociated from |
|
|
$D$. It is still associated with $C$, but it does not contain any |
|
|
reference for the capability. |
|
|
|
|
|
\begin{comment} |
|
|
It is not deassociated from $C$ and removed completely, so that |
|
|
its ID $R$ (or at least the part of it that is used for $C$) is |
|
|
not reused. $C$ must explicitely destroy the reference container |
|
|
anyway because $D$ might die unexpectedly or return an error that |
|
|
gives no indication if it accepted the reference or not. |
|
|
\end{comment} |
|
|
|
|
|
\item The destination task $D$ receives the capability ID and enters |
|
|
it into its capability system. It sends a reply message to $C$. |
|
|
|
|
|
\begin{comment} |
|
|
If the only purpose of the RPC was to copy the capability, the |
|
|
reply message can be empty. Usually, capabilities will be |
|
|
transfered as part of a larger operation, though, and more work |
|
|
will be done by $D$ before returning to $C$. |
|
|
\end{comment} |
|
|
|
|
|
\item \label{copycapout} The client $C$ receives the reply from $D$. |
|
|
Irregardless if it indicated failure or success, it will now send |
|
|
the \verb/cap_ref_cont_destroy/ message to the server $S$, providing |
|
|
the reference container $R$. |
|
|
|
|
|
\begin{comment} |
|
|
This message can be a simple message. It does not require a reply |
|
|
from the server. |
|
|
\end{comment} |
|
|
|
|
|
\item The server receives the \verb/cap_ref_cont_destroy/ message and |
|
|
removes the reference container $R$. The reference container is |
|
|
deassociated from $C$ and $D$. If this was the only reason that $S$ |
|
|
held a task info cap for $D$, this task info cap is also released. |
|
|
|
|
|
\begin{comment} |
|
|
Because the reference container can not be deassociated from $C$ |
|
|
by any other means than this interface, the client does not need |
|
|
to provide $D$. $R$ can not be reused without the client $C$ |
|
|
having it destroyed first. This is different from the |
|
|
\verb/cap_ref_cont_accept/ call made by $D$, see above. |
|
|
\end{comment} |
|
|
|
|
|
\end{enumerate} |
|
|
|
|
|
\paragraph{Result} |
|
|
For the client $C$, nothing has changed. The destination task $D$ |
|
|
either did not accept the capability, and nothing has changed for it, |
|
|
and also not for the server $S$. Or $D$ accepted the capability, and |
|
|
it now has a task info cap for $S$ and a reference to the capability |
|
|
provided by $S$. In this case, the server $S$ has a task info cap for |
|
|
$D$ and provides a capability ID for this task. |
|
|
|
|
|
The above protocol is for copying a capability from $C$ to $D$. If |
|
|
the goal was to move the capability, then $C$ can now release its |
|
|
reference to it. |
|
|
|
|
|
\begin{comment} |
|
|
Originally we considered to move capabilities by default, and |
|
|
require the client to acquire an additional reference if it wanted |
|
|
to copy it instead. However, it turned out that for the |
|
|
implementation, copying is easier to handle. One reason is that the |
|
|
client usually will use local reference counting for the |
|
|
capabilities it holds, and with local reference counting, one |
|
|
server-side reference is shared by many local references. In that |
|
|
case, you would need to acquire a new server-side reference even if |
|
|
you want to move the capability. The other reason is cancellation. |
|
|
If an RPC is cancelled, and you want to back out of it, you need to |
|
|
restore the original situation. And that is easier if you do not |
|
|
change the original situation in the first place until the natural |
|
|
``point of no return''. |
|
|
\end{comment} |
|
|
|
|
|
The above protocol quite obviously achieves the result as described in |
|
|
the above concluding paragraph. However, many other, and often |
|
|
simpler, protocols would also do that. The other protocols we looked |
|
|
at are not secure or robust though, or require more operations. To |
|
|
date we think that the above is the shortest (in particular in number |
|
|
of IPC operations) protocol that is also secure and robust (and if it |
|
|
is not we think it can be fixed to be secure and robust with minimal |
|
|
changes). We have no proof for its correctness. Our confidence comes |
|
|
from the scrutiny we applied to it. If you find a problem with the |
|
|
above protocol, or if you can prove various aspects of it, we would |
|
|
like to hear about it. |
|
|
|
|
|
To understand why the protocol is laid out as it is, and why it is a |
|
|
secure and robust protocol, one has to understand what could possibly |
|
|
go wrong and why it does not cause any problems for any participant if |
|
|
it follows its part of the protocol (independent on what the other |
|
|
participants do). In the following paragraphs, various scenarios are |
|
|
suggested where things do not go as expected in the above protocol. |
|
|
This is probably not a complete list, but it should come close to it. |
|
|
If you find any other problematic scenario, again, let us know. |
|
|
|
|
|
\begin{comment} |
|
|
Although some comments like this appear in the protocol description |
|
|
above, many comments have been spared for the following analysis of |
|
|
potential problems. Read the analysis carefully, as it provides |
|
|
important information about how, and more importantly, why it works. |
|
|
\end{comment} |
|
|
|
|
|
\paragraph{The server $S$ dies} |
|
|
What happens if the server $S$ dies unexpectedly sometime throughout |
|
|
the protocol? |
|
|
|
|
|
\begin{comment} |
|
|
At any time a task dies, the task info caps it held are released. |
|
|
Also, task death notifications are sent to any task that holds task |
|
|
info caps to the now dead task. The task death notifications will |
|
|
be processed asynchrnouly, so they might be processed immediately, |
|
|
or at any later time, even much later after the task died! So one |
|
|
important thing to keep in mind is that the release of task info |
|
|
caps a task held, and other tasks noticing the task death, are |
|
|
always some time apart. |
|
|
\end{comment} |
|
|
|
|
|
Because the client $C$ holds a task info cap for $S$ no imposter can |
|
|
get the task ID of $S$. $C$ and $D$ will get errors when trying to |
|
|
send messages to $S$. |
|
|
|
|
|
\begin{comment} |
|
|
You might now wonder what happens if $C$ also dies, or if $C$ is |
|
|
malicious and does not hold the task info cap. You can use this as |
|
|
an exercise, and try to find the answer on your own. The answers |
|
|
are below. |
|
|
\end{comment} |
|
|
|
|
|
Eventually, $C$ (and $D$ if it already got the task info cap for $S$) |
|
|
will process the task death notification and clean up their state. |
|
|
|
|
|
\paragraph{The client $C$ dies} |
|
|
The server $S$ and the destination task $D$ hold a task info cap for |
|
|
$C$, so no imposter can get its task ID. $S$ and $D$ will get errors |
|
|
when trying to send messages to $C$. Depending on when $C$ dies, the |
|
|
capability might be copied successfully or not at all. |
|
|
|
|
|
Eventually, $S$ and $D$ will process the task death notification and |
|
|
release all resources associated with $C$. If the reference was not |
|
|
yet copied, this will include the reference container associated with |
|
|
$C$, if any. If the reference was already copied, this will only |
|
|
include the empty reference container, if any. |
|
|
|
|
|
\begin{comment} |
|
|
Of course, the participants need to use internal locking to protect |
|
|
the integrity of their internal data structures. The above protocol |
|
|
does not show where locks are required. In the few cases where some |
|
|
actions must be performed atomically, a wording is used that |
|
|
suggests that. |
|
|
\end{comment} |
|
|
|
|
|
\paragraph{The destination task $D$ dies} |
|
|
|
|
|
The client $C$ holds a task info cap for $D$ over the whole operation, |
|
|
so no imposter can get its task ID. Depending on when $D$ dies, it |
|
|
has either not yet accepted the capability, then $C$ will clean up by |
|
|
destroying the reference container, or it has, and then $S$ will clean |
|
|
up its state when it processes the task death notification for $D$. |
|
|
|
|
|
\paragraph{The client $C$ and the destination task $D$ die} |
|
|
|
|
|
This scenario is the reason why the server acquires its own task info |
|
|
cap for $D$ so early, and why it must do that under the constraint |
|
|
that $C$ still lives. If $C$ and $D$ die before the server created |
|
|
the reference container, then either no request was made, or creating |
|
|
the task info cap for $D$ fails because of the constraint. If $C$ and |
|
|
$D$ die afterwards, then no imposter can get the task ID of $D$ and |
|
|
try to get at the reference in the container, because the server has |
|
|
its own task info cap for $D$. |
|
|
|
|
|
\begin{comment} |
|
|
This problem was identified very late in the development of this |
|
|
protocol. We just did not think of both clients dieing at the same |
|
|
time! In an earlier version of the protocol, the server would |
|
|
acquire its task info cap when $D$ accepts its reference. This is |
|
|
too late: If $C$ and $D$ die just before that, an imposter with |
|
|
$D$'s task ID can try to get the reference in the container before |
|
|
the server processes the task death notification for $C$ and |
|
|
destroys it. |
|
|
\end{comment} |
|
|
|
|
|
Eventually, the server will receive and process the task death |
|
|
notifications. If it processes the task death notification for $C$ |
|
|
first, it will destroy the whole container immediately, including the |
|
|
reference, if any. If it processes the task death notification for |
|
|
$D$ first, it will destroy the reference, and leave behind the empty |
|
|
container associated with $C$, until the other task death notification |
|
|
is processed. Either way no imposter can get at the capability. |
|
|
|
|
|
Of course, if the capability was already copied at the time $C$ and |
|
|
$D$ die, the server will just do the normal cleanup. |
|
|
|
|
|
\paragraph{The client $C$ and the server $S$ die} |
|
|
|
|
|
This scenario does not cause any problems, because on the one hand, |
|
|
the destination task $D$ holds a task info cap for $C$, and it |
|
|
acquires its own task info cap for $S$. Although it does this quite |
|
|
late in the protocol, it does so under the constraint that $C$ still |
|
|
lives, which has a task info cap for $S$ for the whole time (until it |
|
|
dies). It also gets the task info cap for $S$ before sending any |
|
|
message to it. An imposter with the task ID of $S$, which it was |
|
|
possible to get because $C$ died early, would not receive any message |
|
|
from $D$ because $D$ uses $C$ as its constraint in acquireing the task |
|
|
info cap for $S$. |
|
|
|
|
|
\paragraph{The destination task $D$ and the server $S$ die} |
|
|
|
|
|
As $C$ holds task info caps for $S$ and $D$, there is nothing that can |
|
|
go wrong here. Eventually, the task death notifications are |
|
|
processed, but the task info caps are not released until the protocol |
|
|
is completed or aborted because of errors. |
|
|
|
|
|
\paragraph{The client $C$, the destination task $D$ and the server $S$ die} |
|
|
|
|
|
Before the last one of these dies, you are in one of the scenarios |
|
|
which already have been covered. After the last one dies, there is |
|
|
nothing to take care of anymore. |
|
|
|
|
|
\begin{comment} |
|
|
In this case your problem is probably not the capability copy |
|
|
protocol, but the stability of your software! Go fix some bugs. |
|
|
\end{comment} |
|
|
|
|
|
So far the scenarios where one or more of the participating tasks die |
|
|
unexpectedly. They could also die purposefully. Other things that |
|
|
tasks can try to do purposefully to break the protocol are presented |
|
|
in the following paragraphs. |
|
|
|
|
|
\begin{comment} |
|
|
A task that tries to harm other tasks by not following a protocol |
|
|
and behaving as other tasks might expect it is malicious. Beside |
|
|
security concerns, this is also an issue of robustness, because |
|
|
malicious behaviour can also be triggered by bugs rather than bad |
|
|
intentions. |
|
|
|
|
|
It is difficult to protect against malicious behaviour by trusted |
|
|
components, like the server $S$, which is trusted by both $C$ and |
|
|
$D$. If a trusted component is compromised or buggy, ill |
|
|
consequences for software that trusts it must be expected. Thus, no |
|
|
analysis is provided for scenarious involving a malicious or buggy |
|
|
server $S$. |
|
|
\end{comment} |
|
|
|
|
|
\paragraph{The client $C$ is malicious} |
|
|
|
|
|
If the client $C$ wants to break the protocol, it has numerous |
|
|
possibilities to do so. The first thing it can do is to provide a |
|
|
wrong destination task ID when creating the container. But in this |
|
|
case, the server will return an error to $D$ when it tries to accept |
|
|
it, and this will give $D$ a chance to notice the problem and clean |
|
|
up. This also would allow for some other task to receive the |
|
|
container, but the client can give the capability to any other task it |
|
|
wants to anyway, so this is not a problem. |
|
|
|
|
|
\begin{comment} |
|
|
If a malicious behaviour results in an outcome that can also be |
|
|
achieved following the normal protocol with different parameters, |
|
|
then this not a problem at all. |
|
|
\end{comment} |
|
|
|
|
|
The client could also try to create a reference container for $D$ and |
|
|
then not tell $D$ about it. However, a reference container should not |
|
|
consume a lot of resources in the server, and all such resources |
|
|
should be attributed to $C$. When $C$ dies eventually, the server |
|
|
will clean up any such pending containers when the task death |
|
|
notification is processed. |
|
|
|
|
|
The same argument holds when $C$ leaves out the call to |
|
|
\verb/cap_ref_cont_destroy/. |
|
|
|
|
|
The client $C$ could also provide wrong information to $D$. It could |
|
|
supply a wrong server thread ID $T$. It could supply a wrong |
|
|
reference container ID $R$. If $D$ does not trust $C$ and expects a |
|
|
capability implemented by some specific trusted server, it will verify |
|
|
the thread ID numerically and reject it if it does not match. The |
|
|
reference container ID will be verified by the server, and it will |
|
|
only be accepted if the reference container was created by the client |
|
|
task $C$. Thus, the only wrong reference container IDs that the |
|
|
client $C$ could use to not provoke an error message from the server |
|
|
(which then lead $D$ to abort the operation) would be a reference |
|
|
container that it created itself in the first place. However, $C$ |
|
|
already is frree to send $D$ any reference container it created. |
|
|
|
|
|
\begin{comment} |
|
|
Again $C$ can not achieve anything it could not achieve by just |
|
|
following the protocol as well. If $C$ tries to use the same |
|
|
reference container with several RPCs in $D$, one of them would |
|
|
succeed and the others would fail, hurting only $C$. |
|
|
|
|
|
If $D$ does trust $C$, then it can not protect against malicious |
|
|
behaviour by $C$. |
|
|
\end{comment} |
|
|
|
|
|
To summarize the result so far: $C$ can provide wrong data in the |
|
|
operations it does, but it can not achieve anything this way that it |
|
|
could not achieve by just following the protocol. In most cases the |
|
|
operation would just fail. If it leaves out some operations, trying |
|
|
to provoke resource leaks in the server, it will only hurt itself (as |
|
|
the reference container is strictly associated with $C$ until the |
|
|
reference is accepted by $D$). |
|
|
|
|
|
\begin{comment} |
|
|
For optimum performance, the server should be able to keep the |
|
|
information about the capabilities and reference containers a client |
|
|
holds on memory that is allocated on the clients behalf. |
|
|
|
|
|
It might also use some type of quota system. |
|
|
\end{comment} |
|
|
|
|
|
Another attack that $C$ can attempt is to deny a service that $S$ and |
|
|
$D$ are expecting of it. Beside not doing one or more of the RPCs, |
|
|
this is in particular holding the task info caps for the time span as |
|
|
described in the protocol. Of course, this can only be potentially |
|
|
dangerous in combination with a task death. If $C$ does not hold the |
|
|
server task info capability, then an imposter of $S$ could trick $D$ |
|
|
into using the imposter as the server. However, this is only possible |
|
|
if $D$ already trusts $C$. Otherwise it would only allow servers that |
|
|
it already trusts, and it would always hold task info caps to such |
|
|
trusted servers when making the decision that it trusts them. |
|
|
However, if $D$ trusts $C$, it can not protect against $C$ being |
|
|
malicious. |
|
|
|
|
|
\begin{comment} |
|
|
If $D$ does not trust $C$, it should only ever compare the task ID |
|
|
of the server thread against trusted servers it has a task info cap |
|
|
for. It must not rely on $C$ doing that for $D$. |
|
|
|
|
|
However, if $D$ does trust $C$, it can rely on $C$ holding the |
|
|
server task info cap until it got its own. Thus, the task ID of $C$ |
|
|
can be used as the constraint when acquiring the task info cap in |
|
|
the protocol. |
|
|
\end{comment} |
|
|
|
|
|
If $C$ does not hold the task info cap of $D$, and $D$ dies before the |
|
|
server acquires its task info cap for $D$, it might get a task info |
|
|
cap for an imposter of $D$. But if the client wants to achieve that, |
|
|
it could just follow the protocol with the imposter as the destination |
|
|
task. |
|
|
|
|
|
\paragraph{The destination task $D$ is malicious} |
|
|
|
|
|
The destination task has not as many possibilities as $C$ to attack |
|
|
the protocol. This is because it is trusted by $C$. So the only |
|
|
participant that $D$ can try to attack is the server $S$. But the |
|
|
server $S$ does not rely on any action by $D$. $D$ does not hold any |
|
|
task info caps for $S$. The only operation it does is an RPC to $S$ |
|
|
accepting the capability, and if it omits that it will just not get |
|
|
the capability (the reference will be cleaned up by $C$ or by the |
|
|
server when $C$ dies). |
|
|
|
|
|
The only thing that $D$ could try is to provide false information in |
|
|
the \verb/cap_ref_cont_accept/ RPC. The information in that RPC is |
|
|
the task ID of the client $C$ and the reference container ID $R$. The |
|
|
server will verify that the client $C$ has previously created a |
|
|
reference container with the ID $R$ that is destined for $D$. So $D$ |
|
|
will only be able to accept references that it is granted access to. |
|
|
So it can not achieve anything that it could not achieve by following |
|
|
the protocol (possibly the protocol with another client). If $D$ |
|
|
accepts capabilities from other transactions outside of the protocol, |
|
|
it can only cause other transactions in its own task to fail. |
|
|
|
|
|
\begin{comment} |
|
|
If you can do something wrong and harm yourself that way, then this |
|
|
is called ``shooting yourself in your foot''. |
|
|
|
|
|
The destination task $D$ is welcome to shoot itself in its foot. |
|
|
\end{comment} |
|
|
|
|
|
\paragraph{The client $C$ and the destination task $D$ are malicious} |
|
|
|
|
|
The final question we want to raise is what can happen if the client |
|
|
$C$ and the destination task $D$ are malicious. Can $C$ and $D$ |
|
|
cooperate and attacking $S$ in a way that $C$ or $D$ alone could not? |
|
|
|
|
|
In the above analysis, there is no place where we assume any specific |
|
|
behaviour of $D$ to help $S$ in preventing an attack on $S$. There is |
|
|
only one place where we make an assumption for $C$ in the analysis of |
|
|
a malicious $D$. If $D$ does not accept a reference container, we |
|
|
said that $C$ would clean it up by calling |
|
|
\verb/cap_ref_cont_destroy/. So we have to look at what would happen |
|
|
if $C$ were not to do that. |
|
|
|
|
|
Luckily, we covered this case already. It is identical to the case |
|
|
where $C$ does not even tell $D$ about the reference container and |
|
|
just do nothing. In this case, as said before, the server will |
|
|
eventually release the reference container when $C$ dies. Before |
|
|
that, it only occupies resources in the server that are associated |
|
|
with $C$. |
|
|
|
|
|
This analysis is sketchy in parts, but it covers a broad range of |
|
|
possible attacks. For example, all possible and relevant combinations |
|
|
of task deaths and malicious tasks are covered. Although by no means |
|
|
complete, it can give us some confidence about the rightness of the |
|
|
protocol. It also provides a good set of test cases that you can test |
|
|
your own protocols, and improvements to the above protocol against. |
|
|
|
|
|
|
|
|
\subsubsection{The trust rule} |
|
|
|
|
|
The protocol to copy a capability from one client to another task has |
|
|
a dramatic consequence on the design of the Hurd interfaces. |
|
|
|
|
|
Because the receiver of the capability must make blocking calls to the |
|
|
server providing the capability, the receiver of the capability |
|
|
\emph{must} trust the server providing the capability. |
|
|
|
|
|
This means also: If the receiver of a capability does not trust the |
|
|
server providing the capability, it \emph{must not} accept it. |
|
|
|
|
|
The consequence is that normally, servers can not accept capabilities |
|
|
from clients, unless they are provided by a specific trusted server. |
|
|
This can be the \texttt{task} or \texttt{auth} server for example. |
|
|
|
|
|
This rule is even true if the receiver does not actually want to use |
|
|
the capability for anything. Just accepting the capability requires |
|
|
trusting the server providing it already. |
|
|
|
|
|
In the Hurd on Mach, ports (which are analogous to capabilities in |
|
|
this context) can be passed around freely. There is no security risk |
|
|
in accepting a port from any source, because the kernel implements |
|
|
them as protected objects. Using a port by sending blocking messages |
|
|
to it requires trust, but simply storing the port on the server side |
|
|
does not. |
|
|
|
|
|
This is different in the Hurd on L4: A server must not accept |
|
|
capabilities unless it trusts the server providing them. Because |
|
|
capabilities are used for many different purposes (remote objects, |
|
|
authentication, identification), one has to be very careful in |
|
|
designing the interfaces. The Hurd interfaces on Mach use ports in a |
|
|
way that is not possible on L4. Such interfaces need to be |
|
|
redesigned. |
|
|
|
|
|
Often, redesigning such an interface also fixes some other security |
|
|
problems that exists with in the Hurd on L4, in particular DoS |
|
|
attacks. A good part of this paper is about redesigning the Hurd to |
|
|
avoid storing untrusted capabilities on the server side. |
|
|
|
|
|
\begin{comment} |
|
|
Examples are: |
|
|
|
|
|
\begin{itemize} |
|
|
\item The new authentication protocol, which eliminates the need for |
|
|
a rendezvous port and is not only faster, but also does not |
|
|
require the server to block on the client anymore (see section |
|
|
\ref{auth} on page \pageref{auth}). |
|
|
|
|
|
\item The signal handling, which does not require the \texttt{proc} |
|
|
server to hold the signal port for every task anymore (see section |
|
|
\ref{signals} on page \pageref{signals}). |
|
|
|
|
|
\item The new exec protocol, which eliminates the need to pass all |
|
|
capabilities that need to be transfered to the new executable from |
|
|
the old program to the filesystem server, and then to the |
|
|
\texttt{exec} server (see section \ref{exec} on page |
|
|
\pageref{exec}). |
|
|
|
|
|
\item The new way to implement Unix Domain Sockets, which don't |
|
|
require a trusted system server, so that descriptor passing (which |
|
|
is really capability passing) can work (see section |
|
|
\ref{unixdomainsockets} on page \pageref{unixdomainsockets}. |
|
|
|
|
|
\item The way parent and child filesystem are linked to each other, |
|
|
in other words: how mounting a filesystem works (see section |
|
|
\ref{xfslookup} on page \pageref{xfslookup}). |
|
|
|
|
|
\item The replacement for the \verb/file_reparent()/ RPC (see |
|
|
section \ref{reparenting} on page \pageref{reparenting}). |
|
|
\end{itemize} |
|
|
\end{comment} |
|
|
|
|
|
\subsection{Synchronous IPC} |
|
|
|
|
|
The Hurd only needs synchronous IPC. Asynchronous IPC is usually not |
|
|
required. An exception are notifications (see below). |
|
|
|
|
|
There are possibly some places in the Hurd source code where |
|
|
asynchronous IPC is assumed. These must be replaced with different |
|
|
strategies. One example is the implementation of select() in the GNU |
|
|
C library. |
|
|
|
|
|
\begin{comment} |
|
|
A naive implementation would use one thread per capability to select |
|
|
on. A better one would combine all capabilities implemented by the |
|
|
same server in one array and use one thread per server. |
|
|
|
|
|
A more complex scheme might let the server process select() calls |
|
|
asynchronously and report the result back via notifications. |
|
|
\end{comment} |
|
|
|
|
|
In other cases the Hurd receives the reply asynchronously from sending |
|
|
the message. This works fine in Mach, because send-once rights are |
|
|
used as reply ports and Mach guarantees to deliver the reply message, |
|
|
ignoring the kernel queue limit. In L4, no messages are queued and |
|
|
such places need to be rewritten in a different way (for example using |
|
|
extra threads). |
|
|
|
|
|
\begin{comment} |
|
|
What happens if a client does not go into the receive phase after a |
|
|
send, but instead does another send, and another one, quickly many |
|
|
sends, as fast as possible? A carelessly written server might |
|
|
create worker threads for each request. Instead, the server should |
|
|
probably reject to accept a request from a client thread that |
|
|
already has a pending request, so the number of worker threads is |
|
|
limited to the number of client threads. |
|
|
|
|
|
This also makes interrupting an RPC operation easier (the client |
|
|
thread ID can be used to identify the request to interrupt). |
|
|
\end{comment} |
|
|
|
|
|
|
|
|
\subsection{Notifications} |
|
|
|
|
|
Notifications to untrusted tasks happen frequently. One case is |
|
|
object death notifications, in particular task death notifications. |
|
|
Other cases might be select() or notifications of changes to the |
|
|
filesystem. |
|
|
|
|
|
The console uses notifications to broadcast change events to the |
|
|
console content, but it also uses shared memory to broadcast the |
|
|
actual data, so not all notifications need to be received for |
|
|
functional operation. Still, at least one notification is queued by |
|
|
Mach, and this is sufficient for the console to wakeup whenever |
|
|
changes happened, even if the changes can not be processed |
|
|
immediately. |
|
|
|
|
|
From the servers point of view, notifications are simply messages with |
|
|
a send and xfer timeout of 0 and without a receive phase. |
|
|
|
|
|
For the client, however, there is only one way to ensure that it will |
|
|
receive the notification: It must have the receiving thread in the |
|
|
receive phase of an IPC. While this thread is processing the |
|
|
notification (even if it is only delegating it), it might be preempted |
|
|
and another (or the same) server might try to send a second |
|
|
notification. |
|
|
|
|
|
\begin{comment} |
|
|
It is an open challenge how the client can ensure that it either |
|
|
receives the notification or at least knows that it missed it, while |
|
|
the server remains save from potential DoS attacks. The usual |
|
|
strategy, to give receivers of notifications a higher scheduling |
|
|
priority than the sender, is not usable in a system with untrusted |
|
|
receivers (like the Hurd). The best strategy determined so far is |
|
|
to have the servers retry to send the notification several times |
|
|
with small delays inbetween. This can increase the chance that a |
|
|
client is able to receive the notification. However, there is still |
|
|
the question what a server can do if the client is not ready. |
|
|
|
|
|
An alternative might be a global trusted notification server that |
|
|
runs at a higher scheduling priority and records which servers have |
|
|
notifications for which clients, and that can be used by clients to |
|
|
be notified of pending notifications. Then the clients can poll the |
|
|
notifications from the servers. |
|
|
\end{comment} |
|
|
|
|
|
|
|
|
\section{Threads and Tasks} |
|
|
|
|
|
The \texttt{task} server will provide the ability to create tasks and |
|
|
threads, and to destroy them. |
|
|
|
|
|
\begin{comment} |
|
|
In L4, only threads in the privileged address space (the rootserver) |
|
|
are allowed to manipulate threads and address spaces (using the |
|
|
\textsc{ThreadControl} and \textsc{SpaceControl} system calls). The |
|
|
\texttt{task} server will use the system call wrappers provided by |
|
|
the rootserver, see section \ref{rootserver} on page |
|
|
\pageref{rootserver}. |
|
|
\end{comment} |
|
|
|
|
|
The \texttt{task} server provides three different capability types. |
|
|
|
|
|
\paragraph{Task control capabilities} |
|
|
If a new task is created, it is always associated with a task control |
|
|
capability. The task control capability can be used to create and |
|
|
destroy threads in the task, and destroy the task itself. So the task |
|
|
control capability gives the owner of a task control over it. Task |
|
|
control capabilities have the side effect that the task ID of this |
|
|
task is not reused, as long as the task control capability is not |
|
|
released. Thus, having a task control capability affects the global |
|
|
namespace of task IDs. If a task is destroyed, task death |
|
|
notifications are sent to holders of task control capabilities for |
|
|
that task. |
|
|
|
|
|
\begin{comment} |
|
|
A task is also implicitely destroyed when the last task control |
|
|
capability reference is released. |
|
|
\end{comment} |
|
|
|
|
|
\paragraph{Task info capabilities} |
|
|
\label{taskinfocap} |
|
|
Any task can create task info capabilities for other tasks. Such task |
|
|
info capabilities are used mainly in the IPC system (see section |
|
|
\ref{ipc} on page \pageref{ipc}). Task info capabilities have the |
|
|
side effect that the task ID of this task is not reused, as long as |
|
|
the task info capability is not released. Thus, having a task info |
|
|
capability affects the global namespace of task IDs. If a task is |
|
|
destroyed, task death notifications are sent to holders of task info |
|
|
capabilities for that task. |
|
|
|
|
|
\begin{comment} |
|
|
Because of that, holding task info capabilities must be restricted |
|
|
somehow. Several strategies can be taken: |
|
|
|
|
|
\begin{itemize} |
|
|
\item Task death notifications can be monitored. If there is no |
|
|
acknowdgement within a certain time period, the \texttt{task} |
|
|
server could be allowed to reuse the task ID anyway. This is not |
|
|
a good strategy because it can considerably weaken the security of |
|
|
the system (capabilities might be leaked to tasks which reuse such |
|
|
a task ID reclaimed by force). |
|
|
\item The proc server can show dead task IDs which are not released |
|
|
yet, in analogy to the zombie processes in Unix. It can also make |
|
|
available the list of tasks which prevent reusing the task ID, to |
|
|
allow users or the system administrator to clean up manually. |
|
|
\item Quotas can be used to punish users which do not acknowledge |
|
|
task death timely. For example, if the number of tasks the user |
|
|
is allowed to create is restricted, the task info caps that the |
|
|
user holds for dead tasks could be counted toward that limit. |
|
|
\item Any task could be restricted to as many task ID references as |
|
|
there are live tasks in the system, plus some slack. That would |
|
|
prevent the task from creating new task info caps if it does not |
|
|
release old ones from death tasks. The slack would be provided to |
|
|
not unnecessarily slow down a task that processes task death |
|
|
notifications asynchronously to making connections with new tasks. |
|
|
\end{itemize} |
|
|
|
|
|
In particular the last two approaches should proof to be effective |
|
|
in providing an incentive for tasks to release task info caps they |
|
|
do not need anymore. |
|
|
\end{comment} |
|
|
|
|
|
\paragraph{Task manager capability} |
|
|
A task is a relatively simple object, compared to a full blown POSIX |
|
|
process, for example. As the \texttt{task} server is enforced system |
|
|
code, the Hurd does not impose POSIX process semantics in the task |
|
|
server. Instead, POSIX process semantics are implemented in a |
|
|
different server, the proc server (see also section \ref{proc} on page |
|
|
\pageref{proc}). To allow the \texttt{proc} server to do its work, it |
|
|
needs to be able to get the task control capability for any task, and |
|
|
gather other statistics about them. Furthermore, there must be the |
|
|
possibility to install quota mechanisms and other monitoring systems. |
|
|
The \texttt{task} server provides a task manager capability, that |
|
|
allows the holder of that capability to control the behaviour of the |
|
|
\texttt{task} server and get access to the information and objects it |
|
|
provides. |
|
|
|
|
|
\begin{comment} |
|
|
For example, the task manager capability could be used to install a |
|
|
policy capability that is used by the \texttt{task} server to make |
|
|
upcalls to a policy server whenever a new task or thread is created. |
|
|
The policy server could then indicate if the creation of the task or |
|
|
thread is allowed by that user. For this to work, the \texttt{task} |
|
|
server itself does not need to know about the concept of a user, or |
|
|
the policies that the policy server implements. |
|
|
|
|
|
Now that I am writing this, I realize that without any further |
|
|
support by the \texttt{task} server, the policy server would be |
|
|
restricted to the task and thread ID of the caller (or rather the |
|
|
task control capability used) to make its decision. A more |
|
|
capability oriented approach would then not be possible. This |
|
|
requires more thought. |
|
|
|
|
|
The whole task manager interface is not written yet. |
|
|
\end{comment} |
|
|
|
|
|
When creating a new task, the \texttt{task} server allocates a new |
|
|
task ID for it. The task ID will be used as the version field of the |
|
|
thread ID of all threads created in the task. This allows the |
|
|
recipient of a message to verify the sender's task ID efficiently and |
|
|
easily. |
|
|
|
|
|
\begin{comment} |
|
|
The version field is 14 bit on 32-bit architectures, and 32 bit on |
|
|
64 bit architectures. Because the lower six bits must not be all |
|
|
zero (to make global thread IDs different from local thread IDs), |
|
|
the number of available task IDs is $2^{14} - 2^6$ resp. $2^{32} - |
|
|
2^6$. |
|
|
|
|
|
If several systems are running in parallel on the same host, they |
|
|
might share thread IDs by encoding the system ID in the upper bits |
|
|
of the thread number. |
|
|
\end{comment} |
|
|
|
|
|
Task IDs will be reused only if there are no task control or info |
|
|
capabilities for that task ID held by any task in the system. To |
|
|
support bootstrapping an IPC connection (see section |
|
|
\ref{ipcbootstrap} on page \pageref{ipcbootstrap}), the \texttt{task} |
|
|
server will delay reusing a task ID as long as possible. |
|
|
|
|
|
\begin{comment} |
|
|
This is similar to how PIDs are generated in Unix. Although it is |
|
|
attempted to keep PIDs small for ease of use, PIDs are not reused |
|
|
immediately. Instead, the PID is incremented up to a certain |
|
|
maximum number, and only then smaller PID values are reused again. |
|
|
|
|
|
As task IDs are not a user interface, there is no need to keep them |
|
|
small. The whole available range can be used to delay reusing a |
|
|
task ID as long as possible. |
|
|
\end{comment} |
|
|
|
|
|
When creating a new task, the \texttt{task} server also has to create |
|
|
the initial thread. This thread will be inactive. Once the creation |
|
|
and activation of the initial thread has been requested by the user, |
|
|
it will be activated. When the user requests to destroy the last |
|
|
thread in a task, the \texttt{task} server makes that thread inactive |
|
|
again. |
|
|
|
|
|
\begin{comment} |
|
|
In L4, an address space can only be implicitely created (resp. |
|
|
destroyed) with the first (resp. last) thread in that address space. |
|
|
\end{comment} |
|
|
|
|
|
Some operations, like starting and stopping threads in a task, can not |
|
|
be supported by the task server, but have to be implemented locally in |
|
|
each task because of the minimality of L4. If external control over |
|
|
the threads in a task at this level is required, the debugger |
|
|
interface might be used (see section \ref{debug} on page |
|
|
\pageref{debug}). |
|
|
|
|
|
|
|
|
\subsection{Accounting} |
|
|
|
|
|
We want to allow the users of the system to use the \texttt{task} |
|
|
server directly, and ignore other task management facilities like the |
|
|
\texttt{proc} server. However, the system administrator still needs |
|
|
to be able to identify the user who created such anonymous tasks. |
|
|
|
|
|
For this, a simple accounting mechanism is provided by the task |
|
|
server. An identifier can be set for a task by the task manager |
|
|
capability, which is inherited at task creation time from the parent |
|
|
task. This accounting ID can not be changed without the task manager |
|
|
capability. |
|
|
|
|
|
The \texttt{proc} server sets the accounting ID to the process ID |
|
|
(PID) of the task whenever a task registers itself with the |
|
|
\texttt{proc} server. This means that all tasks which do not register |
|
|
themself with the \texttt{proc} server will be grouped together with |
|
|
the first parent task that did. This allows to easily kill all |
|
|
unregistered tasks together with its registered parent. |
|
|
|
|
|
The \texttt{task} server does not interpret or use the accounting ID |
|
|
in any way. |
|
|
|
|
|
|
|
|
\subsection{Proxy Task Server} |
|
|
\label{proxytaskserver} |
|
|
|
|
|
The \texttt{task} server can be safely proxied, and the users of such |
|
|
a proxy task server can use it like the real \texttt{task} server, |
|
|
even though capabilities work a bit differently for the \texttt{task} |
|
|
server than for other servers. |
|
|
|
|
|
The problem exists because the proxy task server would hold the real |
|
|
task info capabilities for the task info capabilities that it provides |
|
|
to the proxied task. So if the proxy task server dies, all such task |
|
|
info capabilities would be released, and the tasks using the proxy |
|
|
task server would become insecure and open to attacks by imposters. |
|
|
|
|
|
However, this is not really a problem, because the proxy task server |
|
|
will also provide proxy objects for all task control capabilities. So |
|
|
it will be the only task which holds task control capabilities for the |
|
|
tasks that use it. When the proxy task server dies, all tasks that |
|
|
were created with it will be destroyed when these tak control |
|
|
capabilities are released. The proxy task server is a vital system |
|
|
component for the tasks that use it, just as the real \texttt{task} |
|
|
server is a vital system component for the whole system. |
|
|
|
|
|
|
|
|
\subsection{Scheduling} |
|
|
|
|
|
The task server is the natural place to implement a simple, initial |
|
|
scheduler for the Hurd. A first version can at least collect some |
|
|
information about the cpu time of a task and its threads. Later a |
|
|
proper scheduler has to be written that also has SMP support. |
|
|
|
|
|
The scheduler should run at a higher priority than normal threads. |
|
|
|
|
|
\begin{comment} |
|
|
This might require that the whole task server must run at a higher |
|
|
priority, which makes sense anyway. |
|
|
|
|
|
Not much thought has been given to the scheduler so far. This is |
|
|
work that still needs to be done. |
|
|
\end{comment} |
|
|
|
|
|
There is no way to get at the ``system time'' in L4, it is assumed |
|
|
that no time is spent in the kernel (which is mostly true). So system |
|
|
time will always be reported as $0.00$, or $0.01$. |
|
|
|
|
|
|
|
|
\section{Virtual Memory Management} |
|
|
|
|
|
Traditionally, monolithical kernels, but even kernels like Mach, |
|
|
provide a virtual memory management system in the kernel. All paging |
|
|
decisions are made by the kernel itself. This requires good |
|
|
heuristics. Smart paging decisions are often not possible because the |
|
|
kernel lacks the information about how the data is used. |
|
|
|
|
|
In the Hurd, paging will be done locally in each task. A physical |
|
|
memory server provides a number of guaranteed physical pages to tasks. |
|
|
It will also provide a number of excess pages (over-commit). The task |
|
|
might have to return any number of excess pages on short notice. If |
|
|
the task does not comply, all mappings are revoked (essentially |
|
|
killing the task). |
|
|
|
|
|
A problem arises when data has to be exchanged between a client and a |
|
|
server, and the server wants to have control over the content of the |
|
|
pages (for example, pass it on to other servers, like device drivers). |
|
|
The client can not map the pages directly into the servers address |
|
|
space, as it is not trusted. Container objects created in the |
|
|
physical memory server and mapped into the client and/or the servers |
|
|
address space will provide the necessary security features to allow |
|
|
this. This can be used for DMA and zero-copying in the data exchange |
|
|
between device drivers and (untrusted) user tasks. |
|
|
|
|
|
|
|
|
\section{Authentication} |
|
|
\label{auth} |
|
|
|
|
|
Capabilities are a good way to give access to protected objects and |
|
|
services. They are flexible, lightweight and generic. However, Unix |
|
|
traditionally uses access control lists (ACL) to restrict access to |
|
|
objects like files. Any task running with a certain user ID can |
|
|
access all files that are readable for the user with that user ID. |
|
|
Although all objects are implemented as capabilities in the Hurd, the |
|
|
Hurd also supports the use of user IDs for access control. |
|
|
|
|
|
The system authentication server \texttt{auth} implements the Unix |
|
|
authentication scheme using capabilities. It provides auth |
|
|
capabilities, which are associated with a list of effective and |
|
|
available user and group IDs. The holder of such a capability can use |
|
|
it to authenticate itself to other servers, using the protocol below. |
|
|
|
|
|
Of course, these other servers must use (and trust) the same |
|
|
\texttt{auth} server as the user. Otherwise, the authentication will |
|
|
fail. Once a capability is authenticated in the server, the server |
|
|
will know the user IDs of the client, and can use them to validate |
|
|
further operations. |
|
|
|
|
|
The \texttt{auth} server provides two types of capabilities: |
|
|
|
|
|
\paragraph{Auth capabilities} |
|
|
An auth capability is associated with four vectors of IDs: The |
|
|
effective user and group IDs, which should be used by other servers to |
|
|
authenticate operations that require certain user or group IDs, and |
|
|
the available user and group IDs. Available IDs should not be used |
|
|
for authentication purposes, but can be turned into effective IDs by |
|
|
the holder of an auth capability at any time. |
|
|
|
|
|
New auth capabilities can be created from existing auth capabilities, |
|
|
but only if the requested IDs are a subsets from the union of the |
|
|
(effective and available) IDs in the provided auth capabilities. If |
|
|
an auth capability has an effective or available user ID 0, then |
|
|
arbitrary new auth objects can be created from that. |
|
|
|
|
|
\paragraph{Passport capabilities} |
|
|
A passport capability can be created from an auth capability and is |
|
|
only valid for the task that created it. It can be provided to a |
|
|
server in an authentication process (see below). For the client, the |
|
|
passport capability does not directly implement any useful operation. |
|
|
For the server, it can be used to verify the identity of a user and |
|
|
read out the effective user and group IDs. |
|
|
|
|
|
The auth server should always create new passport objects for |
|
|
different tasks, even if the underlying auth object is the same, so |
|
|
that a task having the passport capability can not spy on other tasks |
|
|
unless they were given the passport capability by that task. |
|
|
|
|
|
\subsection{Authenticating a client to a server} |
|
|
|
|
|
A client can authenticate itself to a server with the following |
|
|
protocol: |
|
|
|
|
|
\paragraph{Preconditions} |
|
|
The client $C$ has an auth capability implemented by the \texttt{auth} |
|
|
server $A$. It also has a capability implemented by the server $S$. |
|
|
It wants to reauthenticate this capability with the auth capability, |
|
|
so the server associates the new user and group IDs with it. |
|
|
|
|
|
The server also has an auth capability implemented by its trusted |
|
|
\texttt{auth} server. For the reauthentication to succeed, the |
|
|
\texttt{auth} server of the client and the server must be identical. |
|
|
If this is the case, the participating tasks hold task info caps for |
|
|
all other participating tasks (because of the capabilities they hold). |
|
|
|
|
|
\begin{enumerate} |
|
|
\item The client $C$ requests the passport capability for itself from |
|
|
the auth capability from $A$. |
|
|
|
|
|
\begin{comment} |
|
|
Normally, the client will request the passport capability only |
|
|
once and store it together with the auth capability. |
|
|
\end{comment} |
|
|
|
|
|
\item The \texttt{auth} server receives the request and creates a new |
|
|
passport capability for this auth capability and this client. The |
|
|
passport capability is returned to the user. |
|
|
|
|
|
\item The user receives the reply from the \texttt{auth} server. |
|
|
|
|
|
It then sends the reauthentication request to the server $S$, which |
|
|
is invoked on the capability the client wants to reauthenticate. It |
|
|
provides the passport capability as an argument. |
|
|
|
|
|
\item The server $S$ can accept the passport capability, if it |
|
|
verifies that it is really implemented by the \texttt{auth} server |
|
|
it trusts. If the client does not provide a passport capability to |
|
|
the trusted \texttt{auth} server, the authentication process is |
|
|
aborted with an error. |
|
|
|
|
|
Now the server can send a request to the \texttt{auth} server to |
|
|
validate the passport capability. The RPC is invoked on the |
|
|
passport capability. |
|
|
|
|
|
\item The \texttt{auth} server receives the validation request on the |
|
|
passport capability and returns the task ID of the client $C$ that |
|
|
this passport belongs to, and the effective user and group IDs for |
|
|
the auth cap to which this passport cap belongs. |
|
|
|
|
|
\begin{comment} |
|
|
The Hurd on Mach returned the available IDs as well. This feature |
|
|
is not used anywhere in the Hurd, and as the available IDs should |
|
|
not be used for authentication anyway, this does not seem to be |
|
|
useful. If it is needed, it can be added in an extended version |
|
|
of the validation RPC. |
|
|
\end{comment} |
|
|
|
|
|
\item The server receives the task ID and the effective user and group |
|
|
IDs. The server now verifies that the task ID is the same as the |
|
|
task ID of the sender of the reauthentication request. Only then |
|
|
was the reauthentication request made by the owner of the auth cap. |
|
|
It can then return a new capability authenticated with the new user |
|
|
and group IDs. |
|
|
|
|
|
\begin{comment} |
|
|
The verification of the client's task ID is necessary. As the |
|
|
passport cap is copied to other tasks, it can not serve as a proof |
|
|
of identity alone. It is of course absolutely crucial that the |
|
|
server holds the task info cap for the client task $C$ for the |
|
|
whole time of the protocol. But the same is actually true for any |
|
|
RPC, as the server needs to be sure that the reply message is sent |
|
|
to the sender thread (and not any imposter). |
|
|
\end{comment} |
|
|
|
|
|
\item The client receives the reply with the new, reauthenticated |
|
|
capability. Usually this capability is associated in the server |
|
|
with the same abstract object, but different user credentials. |
|
|
|
|
|
\begin{comment} |
|
|
Of course a new capability must be created. Otherwise, all other |
|
|
users holding the same capability would be affected as well. |
|
|
\end{comment} |
|
|
|
|
|
The client can now deallocate the passport cap. |
|
|
|
|
|
\begin{comment} |
|
|
As said before, normally the passport cap is cached by the client |
|
|
for other reauthentications. |
|
|
\end{comment} |
|
|
\end{enumerate} |
|
|
|
|
|
\paragraph{Result} |
|
|
The client $C$ has a new capability that is authenticated with the new |
|
|
effective user and group IDs. The server has obtained the effective |
|
|
user and group IDs from the \texttt{auth} server it trusts. |
|
|
|
|
|
\begin{comment} |
|
|
The Hurd on Mach uses a different protocol, which is more complex |
|
|
and is vulnerable to DoS attacks. The above protocol can not |
|
|
readily be used on Mach, because the sender task of a message can |
|
|
not be easily identified. |
|
|
\end{comment} |
|
|
|
|
|
|
|
|
\section{The POSIX personality} |
|
|
|
|
|
The Hurd offers a POSIX API to the user by default. This is |
|
|
implemented in the GNU C library which uses the services provided by |
|
|
the Hurd servers. Several system servers support the C library. |
|
|
|
|
|
|
|
|
\subsection{Process Management} |
|
|
\label{proc} |
|
|
|
|
|
The \texttt{proc} server implements Unix process semantics in the Hurd |
|
|
system. It will also assign a PID to each task that was created with |
|
|
the \texttt{task} server, so that the owner of these tasks, and the |
|
|
system administrator, can at least send the \verb/SIGKILL/ signal to |
|
|
them. |
|
|
|
|
|
The \texttt{proc} server uses the task manager capability from the |
|
|
\texttt{task} server to get hold of the information about all tasks |
|
|
and the task control caps. |
|
|
|
|
|
\begin{comment} |
|
|
The \texttt{proc} server might also be the natural place to |
|
|
implement a first policy server for the \texttt{task} server. |
|
|
\end{comment} |
|
|
|
|
|
|
|
|
\subsubsection{Signals} |
|
|
\label{signals} |
|
|
|
|
|
Each process can register the thread ID of a signal thread with the |
|
|
\texttt{proc} server. The proc server will give the signal thread ID |
|
|
to any other task which asks for it. |
|
|
|
|
|
\begin{comment} |
|
|
The thread ID can be guessed, so there is no point in protecting it. |
|
|
\end{comment} |
|
|
|
|
|
The signal thread ID can then be used by a task to contact the task to |
|
|
which it wants to send a signal. The task must bootstrap its |
|
|
connection with the intended receiver of the signal, according to the |
|
|
protocol described in section \ref{ipcbootstrap} on page |
|
|
\pageref{ipcbootstrap}. As a result, it will receive the signal |
|
|
capability of the receiving task. |
|
|
|
|
|
The sender of a signal must then provide some capability that proves |
|
|
that the sender is allowed to send the signal when a signal is posted |
|
|
to the signal capability. For example, the owner of the task control |
|
|
cap is usually allowed to send any signal to it. Other capabilities |
|
|
might only give permission to send some types of signals. |
|
|
|
|
|
\begin{comment} |
|
|
The receiver of the signal decides itself which signals to accept |
|
|
from which other tasks. The default implementation in the C library |
|
|
provides POSIX semantics, plus some extensions. |
|
|
\end{comment} |
|
|
|
|
|
Signal handling is thus completely implemented locally in each task. |
|
|
The \texttt{proc} server only serves as a name-server for the thread |
|
|
IDs of the signal threads. |
|
|
|
|
|
\begin{comment} |
|
|
The \texttt{proc} server can not hold the signal capability itself, |
|
|
as it used to do in the implementation on Mach, as it does not trust |
|
|
the tasks implementing the capability. But this is not a problem, |
|
|
as the sender and receiver of a signal can negotiate and bootstrap |
|
|
the connection without any further support by the \texttt{proc} |
|
|
server. |
|
|
|
|
|
Also, the \texttt{proc} server can not even hold task info caps to |
|
|
support the sender of a signal in bootstrapping the connection. |
|
|
This means that there is a race between looking up the signal thread |
|
|
ID from the PID in the \texttt{proc} server and acquiring a task |
|
|
info cap for the task ID of the signal receiver in the sender. |
|
|
However, in Unix, there is always a race when sending a signal using |
|
|
\verb/kill/. The task server helps the users a bit here by not |
|
|
reusing task IDs as long as possible. |
|
|
\end{comment} |
|
|
|
|
|
Some signals are not implemented by sending a message to the task. |
|
|
\verb/SIGKILL/ for example destroys the tasks without contacting it at |
|
|
all. This feature is implemented in the \texttt{proc} server. |
|
|
|
|
|
The signal capability is also used for other things, like the message |
|
|
interface (which allows you to manipulate the environment variables |
|
|
and \texttt{auth} capability of a running task, etc). |
|
|
|
|
|
|
|
|
\subsubsection{The \texttt{fork()} function} |
|
|
|
|
|
To be written. |
|
|
|
|
|
|
|
|
\subsubsection{The \texttt{exec()} function} |
|
|
\label{exec} |
|
|
|
|
|
The \texttt{exec()} operation will be done locally in a task. |
|
|
Traditionally, \texttt{exec()} overlays the same task with a new |
|
|
process image, because creating a new task and transferring the |
|
|
associated state is expensive. In L4, only the threads and virtual |
|
|
memory mappings are actually kernel state associated with a task, and |
|
|
exactly those have to be destroyed by \texttt{exec()} anyway. There |
|
|
is a lot of Hurd specific state associated with a task (capabilities, |
|
|
for example), but it is difficult to preserve that. There are |
|
|
security concerns, because POSIX programs do not know about Hurd |
|
|
features like capabilities, so inheriting all capabilities across |
|
|
\texttt{exec()} unconditionally seems dangerous. |
|
|
|
|
|
\begin{comment} |
|
|
One could think that if a program is not Hurd-aware, then it will |
|
|
not make any use of capabilities except through the normal POSIX |
|
|
API, and thus there are no capabilities except those that the GNU C |
|
|
library uses itself, which \texttt{exec()} can take care of. |
|
|
However, this is only true if code that is not Hurd-aware is never |
|
|
mixed with Hurd specific code, even libraries (unless the library |
|
|
intimately cooperates with the GNU C library). This would be a high |
|
|
barrier to enable Hurd features in otherwise portable programs and |
|
|
libraries. |
|
|
|
|
|
It is better to make all POSIX functions safe by default and allow |
|
|
for extensions to let the user specify which capabilities besides |
|
|
those used for file descriptors etc to be inherited by the new |
|
|
executable. |
|
|
|
|
|
For \verb/posix_spawn()/, this is straight-forward. For |
|
|
\texttt{exec()}, it is not. either specific capabilities could be |
|
|
markes as ``do not close on \texttt{exec()}'', or variants of the |
|
|
\texttt{exec()} function could be provided which take further |
|
|
arguments. |
|
|
\end{comment} |
|
|
|
|
|
There are also implementation obstacles hindering the reuse of the |
|
|
existing task. Only local threads can manipulate the virtual memory |
|
|
mappings, and there is a lot of local state that has to be kept |
|
|
somewhere between the time the old program becomes defunct and the new |
|
|
binary image is installed and used (not to speak of the actual program |
|
|
snippet that runs during the transition). |
|
|
|
|
|
So the decision was made to always create a new task with |
|
|
\texttt{exec()}, and copy the desired state from the current task to |
|
|
the new task. This is a clean solution, because a new task will |
|
|
always start out without any capabilities in servers, etc, and thus |
|
|
there is no need for the old task to try to destroy all unneeded |
|
|
capabilities and other local state before \texttt{exec()}. Also, in |
|
|
case the exec fails, the old program can continue to run, even if the |
|
|
exec fails at a very late point (there is no ``point of no return'' |
|
|
until the new task is actually up and running). |
|
|
|
|
|
For suid and sgid applications, the actual \texttt{exec()} has to be |
|
|
done by the filesystem. However, the filesystem can not be bothered |
|
|
to also transfer all the user state into the new task. It can not |
|
|
even do that, because it can not accept capabilities implemented by |
|
|
untrusted servers from the user. Also, the filesystem does not want |
|
|
to rely on the new task to be cooperative, because it does not |
|
|
necessarily trust the code, if is is owned by an untrusted user. |
|
|
|
|
|
\begin{enumerate} |
|
|
\item The user creates a new task and a container with a single |
|
|
physical page, and makes the \texttt{exec()} call to the file |
|
|
capability, providing the task control capability. Before that, it |
|
|
creates a task info capability from it for its own use. |
|
|
\item The filesystem checks permission and then revokes all other |
|
|
users on the task control capability. This will revoke the users |
|
|
access to the task, and will fail if the user did not provide a |
|
|
pristine task object. (It is assumed that the filesystem should not |
|
|
create the task itself so the user can not use suid/sgid |
|
|
applications to escape from their quota restriction). |
|
|
\item Then it revokes access to the provided physical page and writes |
|
|
a trusted startup code to it. |
|
|
\item The filesystem will also prepare all capability transactions and |
|
|
write the required information (together with other useful |
|
|
information) in a stack on the physical page. |
|
|
\item Then it creates a thread in the task, and starts it. At |
|
|
pagefault, it will provide the physical page. |
|
|
\item The startup code on the physical page completes the capability |
|
|
transfer. It will also install a small pager that can install file |
|
|
mappings for this binary image. Then it jumps to the entry point. |
|
|
\item The filesystem in the meanwhile has done all it can do to help |
|
|
the task startup. It will provide the content of the binary or |
|
|
script via paging or file reads, but that happens asynchronously, |
|
|
and as for any other task. So the filesystem returns to the client. |
|
|
\item The client can then send its untrusted information to the new |
|
|
task. The new task got the client's thread ID from the filesystem |
|
|
(possibly provided by the client), and thus knows to which thread it |
|
|
should listen. The new task will not trust this information |
|
|
ultimatively (ie, the new task will use the authentication, root |
|
|
directory and other capabilities it got from the filesystem), but it |
|
|
will accept all capabilities and make proper use of them. |
|
|
\item Then the new task will send a message to proc to take over the |
|
|
old PID and other process state. How this can be done best is still |
|
|
to be determined (likely the old task will provide a process control |
|
|
capability to the new task). At that moment, the old task is |
|
|
desrtoyed by the proc server. |
|
|
\end{enumerate} |
|
|
|
|
|
This is a coarse and incomplete description, but it shows the general |
|
|
idea. The details will depend a lot on the actual implementation. |
|
|
|
|
|
|
|
|
\subsection{Unix Domain Sockets} |
|
|
\label{unixdomainsockets} |
|
|
|
|
|
In the Hurd on Mach, there was a global pflocal server that provided |
|
|
unix domain sockets and pipes to all users. This will not work very |
|
|
well in the Hurd on L4, because for descriptor passing, read: |
|
|
capability passing, the unix domain socket server needs to accept |
|
|
capabilities in transit. User capabilities are often implemented by |
|
|
untrusted servers, though, and thus a global pflocal server running as |
|
|
root can not accept them. |
|
|
|
|
|
However, unix domain sockets and pipes can not be implemented locally |
|
|
in the task. An external task is needed to hold buffered data |
|
|
capabilities in transit. in theory, a new task could be used for |
|
|
every pipe or unix domain socketpair. However, in practice, one |
|
|
server for each user would suffice and perform better. |
|
|
|
|
|
This works, because access to Unix Domain Sockets is controlled via |
|
|
the filesystem, and access to pipes is controlled via file |
|
|
descriptors, usually by inheritance. For example, if a fifo is |
|
|
installed as a passive translator in the filesystem, the first user |
|
|
accessing it will create a pipe in his pflocal server. From then on, |
|
|
an active translator must be installed in the node that redirects any |
|
|
other users to the right pflocal server implementing this fifo. This |
|
|
is asymmetrical in that the first user to access a fifo will implement |
|
|
it, and thus pay the costs for it. But it does not seem to cause any |
|
|
particular problems in implementing the POSIX semantics. |
|
|
|
|
|
The GNU C library can contact ~/servers/socket/pflocal to implement |
|
|
socketpair, or start a pflocal server for this task's exclusive use if |
|
|
that node does not exist. |
|
|
|
|
|
All this are optimizations: It should work to have one pflocal process |
|
|
for each socketpair. However, performance should be better with a |
|
|
shared pflocal server, one per user. |
|
|
|
|
|
|
|
|
\subsection{Pipes} |
|
|
|
|
|
Pipes are implemented using \texttt{socketpair()}, that means as |
|
|
unnamed pair of Unix Domain Sockets. The \texttt{pflocal} server will |
|
|
support this by implementing pipe semantics on the socketpair if |
|
|
requested. |
|
|
|
|
|
\begin{comment} |
|
|
It was considered to use shared memory for the pipe implementation. |
|
|
But we are not aware of a lock-free protocol using shared memory |
|
|
with multiple readers and multiple writers. It might be possible, |
|
|
but it is not obvious if that would be faster: Pipes are normally |
|
|
used with \texttt{read()} and \texttt{write()}, so the data has to |
|
|
be copied from and to the supplied buffer. This can be done |
|
|
efficiently in L4 even across address spaces using string items. In |
|
|
the implementation using sockets, the \texttt{pflocal} server |
|
|
handles concurrent read and write accesses with mutual exclusion. |
|
|
\end{comment} |
|
|
|
|
|
|
|
|
\subsection{Filesystems} |
|
|
|
|
|
\subsubsection{Directory lookup across filesystems} |
|
|
\label{xfslookup} |
|
|
|
|
|
The Hurd has the ability to let users mount filesystems and other |
|
|
servers providing a filesystem-like interface. Such filesystem |
|
|
servers are called translators. In the Hurd on GNU Mach, the parent |
|
|
filesystem would automatically start up such translators from passive |
|
|
translator settings in the inode. It would then block until the child |
|
|
filesystem sends a message to its bootstrap port (provided by the |
|
|
parent fs) with its root directory port. This root directory port can |
|
|
then be given to any client looking up the translated node. |
|
|
|
|
|
There are several things wrong with this scheme, which becomes |
|
|
apparent in the Hurd on L4. The parent filesystem must be careful to |
|
|
not block on creating the child filesystem task. It must also be |
|
|
careful to not block on receiving any acknowledgement or startup |
|
|
message from it. Furthermore, it can not accept the root directory |
|
|
capability from the child filesystem and forward it to clients, as |
|
|
they are potentially not trusted. |
|
|
|
|
|
The latter problem can be solved the following way: The filesystem |
|
|
knows about the server thread in the child filesystem. It also |
|
|
implements an authentication capability that represents the ability to |
|
|
access the child filesystem. This capability is also given to the |
|
|
child filesystem at startup (or when it attaches itself to the parent |
|
|
filesystem). On client dir\_lookup, the parent filesystem can return |
|
|
the server\_thread and the authentication capability to the client. |
|
|
The client can use that to initiate a connection with the child |
|
|
filesystem (by first building up a connection, then sending the |
|
|
authentication capability from the parent filesystem, and receiving a |
|
|
root directory capability in exchange). |
|
|
|
|
|
\begin{comment} |
|
|
There is a race here. If the child filesystem dies and the parent |
|
|
filesystem processes the task death notification and releases the |
|
|
task info cap for the child before the user acquires its own task |
|
|
info cap for the child, then an imposter might be able to pretend to |
|
|
be the child filesystem for the client. |
|
|
|
|
|
This race can only be avoided by a more complex protocol: |
|
|
|
|
|
Variant 1: The user has to acquire the task info cap for the child |
|
|
fs, and then it has to perform the lookup again. If then the thread |
|
|
ID is for the task it got the task ID for in advance, it can go on. |
|
|
If not, it has to retry. This is not so good because a directory |
|
|
lookup is usually an expensive operation. However, it has the |
|
|
advantage of only slowing down the rare case. |
|
|
|
|
|
Variant 2: The client creates an empty reference container in the |
|
|
task server, which can then be used by the server to fill in a |
|
|
reference to the child's task ID. However, the client has to create |
|
|
and destroy such a container for every filesystem where it excepts |
|
|
it could be redirected to another (that means: for all filesystems |
|
|
for which it does not use \verb/O_NOTRANS/). This is quite an |
|
|
overhead to the common case. |
|
|
|
|
|
\begin{verbatim} |
|
|
<marcus> I have another idea |
|
|
<marcus> the client does not give a container |
|
|
<marcus> server sees child fs, no container -> returns O_NOTRANS node |
|
|
<marcus> then client sees error, uses O_NOTRANS node, "" and container |
|
|
<marcus> problem solved |
|
|
<marcus> this seems to be the optimum |
|
|
<neal> hmm. |
|
|
<neal> So lazily supply a container. |
|
|
<marcus> yeah |
|
|
<neal> Hoping you won't need one. |
|
|
<marcus> and the server helps you by doing as much as it can usefully |
|
|
<neal> And that is the normal case. |
|
|
<neal> Yeah, that seems reasonable. |
|
|
<marcus> the trick is that the server won't fail completely |
|
|
<marcus> it will give you at least the underlying node |
|
|
\end{verbatim} |
|
|
\end{comment} |
|
|
|
|
|
The actual creation of the child filesystem can be performed much like |
|
|
a suid exec, just without any client to follow up with further |
|
|
capabilities and startup info. The only problem that remains is how |
|
|
the parent filesystem can know which thread in the child filesystem |
|
|
implements the initial handshake protocol for the clients to use. The |
|
|
only safe way here seems to be that the parent filesystem requires the |
|
|
child to use the main thread for that, or that the parent filesystem |
|
|
creates a second thread in the child at startup (passing its thread ID |
|
|
in the startup data), requiring that this second thread is used. In |
|
|
either case the parent filesystem will know the thread ID in advance |
|
|
because it created the thread in the first place. This looks a bit |
|
|
ugly, and violates good taste, so we might try to look for alternative |
|
|
solutions. |
|
|
|
|
|
|
|
|
\subsubsection{Reparenting} |
|
|
\label{reparenting} |
|
|
|
|
|
The Hurd on Mach contains a curious RPC, \verb/file_reparent/, which |
|
|
allows you to create a new capability for the same node, with the |
|
|
difference that the new node will have a supplied capability as its |
|
|
parent node. A directory lookup of \texttt{..} on this new capability |
|
|
would return the provided parent capability. |
|
|
|
|
|
This function is used by the \texttt{chroot()} function, which sets |
|
|
the parent node to the null capability to prevent escape from a |
|
|
\texttt{chroot()} environment. It is also used by the |
|
|
\texttt{firmlink} translator, which is a cross over of a symbolic and |
|
|
a hard link: It works like a hard link, but can be used across |
|
|
filesystems. |
|
|
|
|
|
A firmlink is a dangerous thing. Because the filesystem will give no |
|
|
indication if the parent node it returns is provided by itself or some |
|
|
other, possibly untrusted filesystem, the user might follow the parent |
|
|
node to untrusted filesystems without being aware of it. |
|
|
|
|
|
In the Hurd port to L4, the filesystem can not accept untrusted parent |
|
|
capabilities on behalf of the user anymore. The \texttt{chroot()} |
|
|
function is not difficult to implement anyway, as no real capability |
|
|
is required. The server can just be instructed to create a node with |
|
|
no parent node, and it can do that without problems. Nevertheless, we |
|
|
also want a secure version of the \texttt{firmlink} translator. This |
|
|
is possible if the same strategy is used as in cross filesystem |
|
|
lookups. The client registers a server thread as the handler for the |
|
|
parent node, and the filesystem returns a capability that can be used |
|
|
for authentication purposes. Now, the client still needs to connect |
|
|
this to the new parent node. Normally, the filesystem providing the |
|
|
new parent node will also not trust the other filesystem, and thus can |
|
|
not accept the capability that should be used for authentication |
|
|
purposes. So instead creating a direct link from the one filesystem |
|
|
to the other, the firmlink translator must act as a middle man, and |
|
|
redirect all accesses to the parent node first to itself, and then to |
|
|
the filesystem providing the parent node. For this, it must request a |
|
|
capability from that filesystem that can be used for authentication |
|
|
purposes when bootstrapping a connection, that allows such a |
|
|
bootstrapping client to access the parent node directly. |
|
|
|
|
|
This also fixes the security issues, because now any move away from |
|
|
the filesystem providing the reparented node will explicitely go first |
|
|
to the \texttt{firmlink} translator, and then to the filesystem |
|
|
providing the parent node. The user can thus make an informed |
|
|
decision if it trusts the \texttt{firmlink} translator and the |
|
|
filesystem providing the parent node. |
|
|
|
|
|
\begin{comment} |
|
|
This is a good example where the redesign of the IPC system forces |
|
|
us to fix a security issue and provides a deeper insight into the |
|
|
trust issues and how to solve them. |
|
|
\end{comment} |
|
|
|
|
|
|
|
|
\section{Debugging} |
|
|
\label{debug} |
|
|
|
|
|
L4 does not support debugging. So every task has to implement a debug |
|
|
interface and implement debugging locally. gdb needs to be changed to |
|
|
make use of this interface. How to perform the required |
|
|
authentication, and how the debug thread is advertised to gdb, and how |
|
|
the debug interface should look like, are all open questions. |
|
|
|
|
|
|
|
|
\section{Device Drivers} |
|
|
|
|
|
This section written by Peter De Schrijver and Daniel Wagner. |
|
|
|
|
|
\subsection{Requirements} |
|
|
|
|
|
\begin{itemize} |
|
|
\item Performance: Speed is important! |
|
|
\item Portability: Framework should work on different architectures. |
|
|
|
|
|
Also: Useable in a not hurdisch environment with only |
|
|
small changes. |
|
|
|
|
|
\item Flexibility |
|
|
\item Convenient interfaces |
|
|
\item Consistency |
|
|
\item Safety: driver failure should have as minimal system impact as |
|
|
possible. |
|
|
\end{itemize} |
|
|
|
|
|
\subsection{Overview} |
|
|
|
|
|
The framework consists of: |
|
|
\begin{itemize} |
|
|
\item Bus drivers |
|
|
\item Device drivers |
|
|
\item Service servers (plugin managers, $\omega_0$, rootserver) |
|
|
\end{itemize} |
|
|
|
|
|
\subsubsection{Drivers and the filesystem} |
|
|
|
|
|
The device driver framework will only offer a physical device view. |
|
|
Ie. it will be a tree with devices as the leaves connected by |
|
|
various bus technologies. Any logical view and naming persistence |
|
|
will have to be build on top of this (translator). |
|
|
|
|
|
\subsubsection{Layer of the drivers} |
|
|
|
|
|
The device driver framework consists only of the lower level drivers |
|
|
and doesn't need to have a complicated scheme for access control. |
|
|
This is because it should be possible to share devices, e.g. for |
|
|
neighbour Hurd. The authentication is done by installing a virtual |
|
|
driver in each OS/neighour Hurd. The driver framework trusts these |
|
|
virtual drivers. So it's possible for a non Hurdish system to use |
|
|
the driver framework just by implementing these virtual drivers. |
|
|
|
|
|
Only threads which have registered as trusted are allowed to access |
|
|
device drivers. The check is simply done by checking the senders |
|
|
ID against a table of known threads. |
|
|
|
|
|
\subsubsection{Address spaces} |
|
|
|
|
|
Drivers always reside in their own AS. The overhead for cross AS IPC |
|
|
is small enough to do so. |
|
|
|
|
|
\subsubsection{Zero copying and DMA} |
|
|
|
|
|
It is assumed that there are no differences between physical memory |
|
|
pages. For example each physical memory page can be used for DMA |
|
|
transfers. Of course, older hardware like ISA devices can so not be |
|
|
supported. Who cares? |
|
|
|
|
|
With this assumption, the device driver framework can be given any |
|
|
physical memory page for DMA operation. This physical memory page |
|
|
must be pinned down. |
|
|
|
|
|
If an application wants to send or receive data to/from a device |
|
|
driver it has to tell the virtual driver the page on which the |
|
|
operation has to be executed. Since the application doesn't know |
|
|
the virtual-real memory mapping, it has to ask the physical memory |
|
|
manager for the real memory address of the page in question. If the |
|
|
page is not directly mapped from the physical memory manager the |
|
|
application ask the mapper (another application which has mapped |
|
|
this memory region the first application) to resolve the mapping. |
|
|
This can be done recursively. Normally, this resolving of mapping |
|
|
can be speed up using a cache services, since a small number of |
|
|
pages are reused very often. |
|
|
|
|
|
With the scheme, the drivers do not have to take special care of |
|
|
zero copying if there is only one virtual driver. When there is |
|
|
more than one virtual driver pages have to copied for all other |
|
|
virtual drivers. |
|
|
|
|
|
\subsubsection{Root bus driver} |
|
|
|
|
|
The root bus is the entrypoint to look up devices. |
|
|
|
|
|
XXX There should be iterators/visitors for operating on |
|
|
busses/devices. (daniel) |
|
|
|
|
|
\subsubsection{Physical versus logical device view} |
|
|
|
|
|
The device driver framework will only offer a physical device view. |
|
|
Ie. it will be a tree with devices as the leaves connected by |
|
|
various bus technologies. Any logical view and naming persistence |
|
|
will have to be build on top of this (translator). |
|
|
|
|
|
\subsubsection{Things for the future} |
|
|
|
|
|
\begin{itemize} |
|
|
\item Interaction with the task server (e.g. listings driver threads |
|
|
with ps,etc.) |
|
|
\item Powermanagement |
|
|
\end{itemize} |
|
|
|
|
|
\subsection{Bus Drivers} |
|
|
|
|
|
A bus driver is responsible to manage the bus and provide access to |
|
|
devices connected to it. In practice it means a bus driver has to |
|
|
perform the following tasks: |
|
|
|
|
|
\begin{itemize} |
|
|
\item Handle hotplug events |
|
|
|
|
|
Busses which do not support hotplugging, will treated as if there is |
|
|
1 insertion event for every device connected to it when the bus |
|
|
driver is started. Drivers which don't support autoprobing of |
|
|
devices will probably have to read some configuration data from a |
|
|
file or if the driver is a needed for bootstrapping configuration |
|
|
can be given as argument on its stack. In some cases the bus |
|
|
doesn't generate insertion/removal events, but can still support |
|
|
some form of hotplug functionality if the user tells the driver when |
|
|
a change to the bus configuration has happened (eg. SCSI). |
|
|
|
|
|
\item Configure client device drivers |
|
|
|
|
|
The bus driver should start the appropriate client device driver |
|
|
translator when an insertion event is detected. It should also |
|
|
provide the client device driver with all necessary configuration |
|
|
info, so it can access the device it needs. This configuration data |
|
|
typically consists of the bus addresses of the device and possibly |
|
|
IRQ numbers or DMA channel ID's. The device driver is loaded by the |
|
|
assotiatet plugin manager. |
|
|
|
|
|
\item Provide access to devices |
|
|
|
|
|
This means the bus driver should be able to perform a bus |
|
|
transaction on behalf of a client device driver. In some cases this |
|
|
involves sending a message and waiting for reply (eg. SCSI, USB, |
|
|
IEEE 1394, Fibre Channel,...). The driver should provide |
|
|
send/receive message primitives in this case. In other cases |
|
|
devices on the bus can be accessed by doing a memory accesses or by |
|
|
using special I/O instructions. In this case the driver should |
|
|
provide mapping and unmapping primitives so a client device driver |
|
|
can get access to the memory range or is allowed to access the I/O |
|
|
addresses. The client device driver should use a library, which is |
|
|
bus dependant, to access the device on the bus. This library hides |
|
|
the platform specific details of accessing the bus. |
|
|
|
|
|
Furthermore the bus driver must also support rescans for hardware. |
|
|
It might be that not all drivers are found during bootstrapping and |
|
|
hence later on drivers could be loaded. This is done by regenerate |
|
|
new attach notification sending to bus's plugin manager. The plugin |
|
|
manager loads then if possible a new driver. A probe funtion is not |
|
|
needed since all supported hardware can be identified by |
|
|
vendor/device identifactions (unlike ISA hardware). For hardware |
|
|
busses which don't support such identifaction (ISA) only static |
|
|
configuration is possible (configuration scripts etc.) |
|
|
\end{itemize} |
|
|
|
|
|
|
|
|
\subsubsection{Plugin Manager} |
|
|
|
|
|
Each bus driver has a handle/reference to which insert/remove events |
|
|
are send. The owner of the handle/refence must then take |
|
|
appropriate action like loading the drivers. These actors are |
|
|
called plugin managers. |
|
|
|
|
|
\subsubsection{Generic Bus Driver} |
|
|
|
|
|
Operations: |
|
|
\begin{itemize} |
|
|
\item notify (attach, detach) |
|
|
\item string enumerate |
|
|
\end{itemize} |
|
|
|
|
|
XXX Extract generic bus services from the PCI Bus Driver section |
|
|
which could be also be used other PCI related busses (ISA) be used. |
|
|
The name for this service is missleading, since a SCSI Bus Driver |
|
|
does not have anything in common with a PCI bus. (daniel) |
|
|
|
|
|
\subsubsection{ISA Bus Driver} |
|
|
Inherits from: |
|
|
|
|
|
\begin{itemize} |
|
|
\item Generic Bus Driver |
|
|
\end{itemize} |
|
|
|
|
|
Operations: |
|
|
\begin{itemize} |
|
|
\item (none) |
|
|
\end{itemize} |
|
|
|
|
|
XXX The interface has not been defined up to now. (daniel) |
|
|
|
|
|
|
|
|
\subsubsection{PCI Bus Driver} |
|
|
|
|
|
Inherits from: |
|
|
\begin{itemize} |
|
|
\item Generic Bus Driver |
|
|
\end{itemize} |
|
|
|
|
|
Operations: |
|
|
\begin{itemize} |
|
|
\item map\_mmio: map a PCI BAR for MMIO |
|
|
\item map\_io: map a PCI BAR for I/O |
|
|
\item map\_mem: map a PCI BAR for memory |
|
|
\item read\_mmio\_{8,16,32,64}: read from a MMIO register |
|
|
\item write\_mmio\_{8,16,32,64}: write to a MMIO register |
|
|
\item read\_io\_{8,16,32,64}: read from an IO register |
|
|
\item write\_io\_{8,16,32,64}: write to an IO register |
|
|
\item read\_config\_{8,16,32,?}: read from a PCI config register |
|
|
\item write\_config\_{8,16,32,?}: write to a PCI config register |
|
|
\item alloc\_dma\_mem(for non zero copying): allocate main memory useable for DMA |
|
|
\item free\_dma\_mem (for non zero copying): free main memory useable for DMA |
|
|
\item prepare\_dma\_read: write back CPU cachelines for DMAable memory area |
|
|
\item sync\_dma\_write: discard CPU cachelines for DMAable memory area |
|
|
\item alloc\_consistent\_mem: allocate memory which is consistent between CPU |
|
|
and device |
|
|
\item free\_consistent\_mem: free memory which |
|
|
is consistent between CPU and device |
|
|
\item get\_irq\_mapping (A,B,C,D): get the IRQ matching the INT(A,B,C,D) line |
|
|
\end{itemize} |
|
|
|
|
|
\subsection{Device Drivers} |
|
|
\subsubsection{Classes} |
|
|
\begin{itemize} |
|
|
\item character: This the standard tty as known in the Unix environment. |
|
|
\item block |
|
|
\item human input: Keyboard, mouse, ... |
|
|
\item packet switched network |
|
|
\item circuit switched network |
|
|
\item framebuffer |
|
|
\item streaming audio |
|
|
\item streaming video |
|
|
\item solid state storage: flash memory |
|
|
\end{itemize} |
|
|
|
|
|
\subsubsection{Human input devices (HID) and the console} |
|
|
|
|
|
The HIDs and the console are critical for user interaction with the |
|
|
system. Furthmore, the console should be working as soons as possible |
|
|
to give feedback. Log messages which are send to the console before |
|
|
the hardware has been initialized should be buffered. |
|
|
|
|
|
\subsubsection{Generic Device Driver} |
|
|
Operations: |
|
|
\begin{itemize} |
|
|
\item init : prepare hardware for use |
|
|
\item start : start normal operation |
|
|
\item stop : stop normal operation |
|
|
\item deinit : shutdown hardware |
|
|
\item change\_irq\_peer : change peer thread to propagate irq message to. |
|
|
\end{itemize} |
|
|
|
|
|
|
|
|
\subsubsection{ISA Devices} |
|
|
Inherits from: |
|
|
\begin{itemize} |
|
|
\item Generic Device Driver |
|
|
\end{itemize} |
|
|
|
|
|
Supported devices |
|
|
\begin{itemize} |
|
|
\item Keyboard (ps2) |
|
|
\item serial port (mainly for debugging purposses) |
|
|
\item parallel port |
|
|
\end{itemize} |
|
|
|
|
|
XXX interface definition for each device driver is missing. (daniel) |
|
|
|
|
|
|
|
|
\subsubsection{PCI Devices} |
|
|
Inherits from: |
|
|
\begin{itemize} |
|
|
\item Generic Device Driver |
|
|
\end{itemize} |
|
|
|
|
|
Supported devices: |
|
|
\begin{itemize} |
|
|
\item block devices |
|
|
\item ... |
|
|
\end{itemize} |
|
|
|
|
|
XXX interface definition for each device driver is missing. (daniel) |
|
|
|
|
|
|
|
|
\subsection{Resource Management} |
|
|
|
|
|
|
|
|
\subsubsection{IRQ handling} |
|
|
|
|
|
\paragraph{IRQ based interrupt vectors} |
|
|
|
|
|
Some CPU architectures (eg 68k, IA32) can directly jump to an |
|
|
interrupt vector depending on the IRQ number. This is typically the |
|
|
case on CISC CPU's. In this case there is some priorization scheme. On |
|
|
IA32 for example, the lowest IRQ number has the highest priority. |
|
|
Sometimes the priorities are programmable. Most RISC CPU's have only |
|
|
a few interrupt vectors which are connected external IRQs. (typically |
|
|
1 or 2). This means the IRQ handler should read a register in the |
|
|
interrupt controller to determine which IRQ handler has to be |
|
|
executed. Sometimes the hardware assists here by providing a register |
|
|
which indicates the highest priority interrupt according to some |
|
|
(programmable) scheme. |
|
|
|
|
|
\paragraph{IRQ acknowlegdement} |
|
|
|
|
|
The IRQ acknowledgement is done in two steps. First inform the |
|
|
hardware about the successful IRQ acceptance. Then inform the ISRs |
|
|
about the IRQ event. |
|
|
|
|
|
\paragraph{Edge versus level triggered IRQs} |
|
|
|
|
|
Edge triggered IRQs typically don't need explicit acknowledgment by |
|
|
the CPU at the device level. You can just acknowledge them at the |
|
|
interrupt controller level. Level triggered IRQs typically need to |
|
|
explicitly acknowledged by the CPU at the device level. The CPU has to |
|
|
read or write a register from the IRQ generating peripheral to make |
|
|
the IRQ go away. If this is not done, the IRQ handler will be |
|
|
reentered immediatly after it ended, effectively creating an endless |
|
|
loop. Another way of preventing this would be to mask the IRQ. |
|
|
|
|
|
\paragraph{Multiple interrupt controllers} |
|
|
|
|
|
Some systems have multiple interrupt controllers in cascade. This is |
|
|
for example the case on a PC, where you have 2 8259 interrupt |
|
|
controllers. The second controller is connected to the IRQ 2 pin of |
|
|
the first controller. It is also common in non PC systems which still |
|
|
use some standard PC components such as a Super IO controller. In this |
|
|
case the 2 8259's are connected to 1 pin of the primary interrupt |
|
|
controller. Important for the software here is that you need to |
|
|
acknowledge IRQ's at each controller. So to acknowledge an IRQ from |
|
|
the second 8259 connected to the first 8259 connected to another |
|
|
interrupt controller, you have to give an ACK command to each of those |
|
|
controllers. Another import fact is that on PC architecture the order |
|
|
of the ACKs is important. |
|
|
|
|
|
\paragraph{Shared IRQs} |
|
|
|
|
|
Some systems have shared IRQs. In this case the IRQ handler has to |
|
|
look at all devices using the same IRQ... |
|
|
|
|
|
\paragraph{IRQ priorities} |
|
|
|
|
|
All IRQs on L4 have priorities, so if an IRQ occurs any IRQ lower then |
|
|
the first IRQ will be blocked until the first IRQ has been |
|
|
acknowlegded. ISR priorities must much the hardware priority (danger |
|
|
of priority inversion). Furthermore the IRQ acknowledgment order is |
|
|
important. |
|
|
|
|
|
The 8259 also supports a specific IRQ acknowledge iirc. But, this |
|
|
scheme does not work in most level triggered IRQ environments. In |
|
|
these environments you must acknowledge (or mask) the IRQ before |
|
|
leaving the IRQ handler, otherwise the CPU will immediately reenter |
|
|
the IRQ handler, effectively creating an endless loop. In this case L4 |
|
|
would have to mask the IRQ. The IRQ thread would have to unmask it |
|
|
after acknowledgement and processing. |
|
|
|
|
|
\paragraph{IRQ handling by L4/x86} |
|
|
|
|
|
The L4 kernel does handle IRQ acknowlegdment. |
|
|
|
|
|
|
|
|
\subsubsection{$\omega_0$} |
|
|
|
|
|
$\omega_0$ is a system-central IRQ-logic server. It runs in the |
|
|
privileged AS space in order to be allowed rerouting IRQ IPC. |
|
|
|
|
|
If an IRQ is shared between several devices, the drivers are daisy |
|
|
chained and have to notify their peers if an IRQ IPC has arrived. |
|
|
|
|
|
XXX For more detail see XXX URL missing |
|
|
|
|
|
Operations: |
|
|
\begin{itemize} |
|
|
\item attach\_irq : attach an ISR thread to the IRQ |
|
|
\item detach\_irq : detach an ISR thread form the IRQ |
|
|
\end{itemize} |
|
|
|
|
|
|
|
|
\subsubsection{Memory} |
|
|
If no physical memory pages are provided by the OS the device driver |
|
|
framework alloces pages from the physical memory manager. The device |
|
|
driver framework has at no point of time to handle any virtual to |
|
|
physical page mapping. |
|
|
|
|
|
|
|
|
\subsection{Bootstrapping} |
|
|
|
|
|
A simpleFS provides initial drivers for bootstraping. The root bus |
|
|
driver and simpleFS is loaded by grub as module. It then signals for |
|
|
loading new (bus) drivers. As before if there is no driver avaible |
|
|
for some reason for the device, the bus driver doesn't change the |
|
|
device state and waits for a notifaction that there are new drivers |
|
|
avaible. This simpleFS might be based on BSD libstand (library for |
|
|
standalone applications). simpleFS doesn't need to be writeable |
|
|
either. |
|
|
|
|
|
|
|
|
\subsubsection{Plugin Manager} |
|
|
A Plugin manager handles driver loading for devices. It searches for |
|
|
driver in seach pathes (on filesystems). It's possible to add new |
|
|
search pathes later. This allows the system to bootstrap with only |
|
|
one search path (the simpleFS). When the search path is changed, the |
|
|
device tree will be scanned for devices which don't have a driver |
|
|
loaded yet. If a driver has become available, it will be loaded. |
|
|
|
|
|
|
|
|
\subsection{Order of implementation} |
|
|
|
|
|
\begin{enumerate} |
|
|
\item rootserver, plugin server |
|
|
\item root bus server |
|
|
\item pci bus |
|
|
\item isa bus |
|
|
\item serial port (isa bus) |
|
|
\item console |
|
|
\end{enumerate} |
|
|
|
|
27 |
|
|
28 |
\end{document} |
\end{document} |