Skip to content

Commit

Permalink
More examples in the compartments and libraries chapter.
Browse files Browse the repository at this point in the history
  • Loading branch information
davidchisnall committed Dec 30, 2024
1 parent eed4ecb commit 58a32fe
Show file tree
Hide file tree
Showing 13 changed files with 336 additions and 29 deletions.
84 changes: 66 additions & 18 deletions text/compartments.tex
Original file line number Diff line number Diff line change
Expand Up @@ -251,9 +251,9 @@ \section{Refining trust}
At a high level, components are typically trusted (or not) with respect to three properties:

\begin{description}
\item[tag=Confidentiality]{: How does information flow out of this component?}
\item[tag=Integrity]{: What how can information be modified by this component?}
\item[tag=Availability]{: What can this component prevent from working?}
\item[tag=Confidentiality]{How does information flow out of this component?}
\item[tag=Integrity]{What how can information be modified by this component?}
\item[tag=Availability]{What can this component prevent from working?}
\end{description}

\begin{note}
Expand Down Expand Up @@ -302,22 +302,9 @@ \section{Refining trust}
The TLS stack will decode complete messages and forward them to the interpreter compartment.
TLS packets have cryptographic integrity protection and so anything that comes through this path is probably safe, unless the TLS compartment is compromised, but putting the interpreter in a separate compartment ensures that invalid interpreter code can provide different colours to the LEDs but can't damage the LEDs and can't launch attacks over the network.

\section[label=exporting_functions]{Exporting functions from libraries and compartments}

Functions are exported using the attributes described in \ref{language_extensions}.
Functions exported from a library are annotated with \c{__cheri_libcall}, those from a compartment with \c{__cheri_compartment()}, with the latter providing the name of the compartment.

If you've written shared libraries on Windows, you may have had to add DLL export and import directives on function prototypes in headers.
These are usually wrapped in a macro that allows you to define the export attribute when compiling the library and import when compiling anything else.

The CHERIoT attributes are designed to avoid the need for this by operating in concert with the \flag{-cheri-compartment=} compiler flag.
When you compile a C/C++ source file that will end up in a compartment, the compiler knows the compartment that it is being built for.
It can therefore generate cross-compartment calls for functions that are in other compartments and direct calls for functions in the same compartment.
It can also do some additional error checking and will refuse to compile functions in one compilation unit if they are defined in another.

\section{Validating arguments}

\functiondoc{check_pointer}
\functiondoc[usr="c:@N@CHERI@FT@>4#N$@N@CHERI@S@PermissionSet#Nb#Nb#Tcheck_pointer#&t0.3#i#b#"]{check_pointer}
If a function that is exported from a compartment takes primitive values as arguments, there's little that an attacker can do other than provide invalid values.
For things like integers, this doesn't matter, for enumerations it's important to ensure that they are valid values.
Expand Down Expand Up @@ -358,6 +345,35 @@ \section{Validating arguments}
\functiondoc{heap_address_is_valid}
Alternatively, you can use the ephemeral claim mechanism (also documented in \ref{heap_claim} to ensure that a pointer is either a pointer that cannot be freed, or to ensure that it remains live until the next cross-compartment call.
These techniques are all combined in \ref{lst:safeuartchecks}, which is a simple function that prints a string to the UART, defensively.
This first uses a lock (see \ref{threads}) to ensure that only one thread will access this function at a time.
If this compartment exposed more than one function that used the UART then the lock would need to be shared between all of them.
\codelisting[marker=safe_uart,caption=Checks to ensure that a function does not crash.,label=lst:safeuartchecks, filename=examples/check_arguments/uart.cc]{}
Next, it uses \c{heap_claim_fast} to prevent concurrent deallocation.
After this, it is safe to use \cxx{check_pointer} to ensure that the permissions are correct and that this pointer does not overlap the current compartment's stack.
For a C string, this checks only that a single byte is readable.
The function then gets the length explicitly and prints either the full length of the buffer, or the buffer up to a null terminator, whichever is shorter.
You can see how well this works with the attacks shown in \ref{lst:safeuartattacks}.
This tries passing a string that is not null-terminated, a string without load permission, an untagged capability, and finally a valid capability.
\codelisting[marker=attacks,caption=Attempting to attack the safe UART.,label=lst:safeuartattacks, filename=examples/check_arguments/hello.cc]{}
When you run this example, you should see output like this:
\begin{console}
No null
Non-malicious string
\end{console}
The string that misses the null terminator is written, but then there's no overflow.
The string that has the wrong permissions and the string that is not a valid capability at all are simply not printed.
Finally, the non-malicious string is printed correctly, showing that the attacker has not been able to corrupt internal state.
\section{Ensuring adequate stack space}
The stack is shared between compartments invoked on the same thread.
Expand All @@ -379,7 +395,7 @@ \section{Ensuring adequate stack space}
\functiondoc{stack_lowest_used_address}
\begin{note}
This helper checks the amount of stack usage *of the current compartment*.
This helper checks the amount of stack usage \textem{of the current compartment}.
The switcher check is not intended to ensure that the invocation of the current compartment can succeed, only that failures are detectable and recoverable.
If you want to ensure that a called compartment *also* has enough stack then you will need to add its stack requirements to those of your compartment.
\end{note}
Expand Down Expand Up @@ -517,6 +533,38 @@ \section{Conventions for cross-compartment calls}
A CHERIoT capability is effectively a tagged union of a pointer and 64 bits of data.
You can take advantage of this in functions that return pointers to return either an integer or, if the result is not tagged, an error code.
To see a slightly less-contrived version of error handling, \ref{lst:safeuartyolo} is a version of \ref{lst:safeuartchecks}, rewritten to use error handling instead of checking.
The original version checked all of the properties of the string and protected itself against concurrent mutation.
This version still uses \cxx{check_pointer}, because this also prevents information disclosure by ensuring that the string does not overlap the current compartment invocation's stack.
You could omit this check if you are not worried about information disclosure.
For capabilities that you write through, this check is very important because otherwise you may write over parts of your own stack and corrupt internal state by accident.
The example then simply tries to read the bytes, assuming that they are a valid null-terminated C string.
\codelisting[marker=safe_uart,caption=Using structured error handling to ensure that a function does not crash.,label=lst:safeuartyolo, filename=examples/yolo_arguments/uart.cc]{}
This version uses the C++ wrappers that take lambdas, rather than the macro versions, but the semantics are the same as described earlier.
When you run this, you should see exactly the same output as the original version:
\begin{console}
No null
Non-malicious string
\end{console}
The string that lacks a null terminator will now simply be written looking for one.
The attempt to read one byte past the end will trigger a bounds exception.
This will then invoke the second lambda passed to \cxx{on_error}, which will set the error code and resume.
The other errors are caught by the \cxx{check_pointer} call.
If you remove that call, you will instead see the following output:
\begin{console}
No null
Non-malicious string
\end{console}
Now, each call is printing the trailing newline, even if it encountered a fault while reading the message.
\section[label=software_capabilities]{Building software capabilities with sealing}
Expand Down
23 changes: 23 additions & 0 deletions text/examples/check_arguments/hello.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Copyright CHERIoT Contributors.
// SPDX-License-Identifier: MIT

#include "hello.h"
#include <cheri.hh>

using namespace CHERI;

/// Thread entry point.
void __cheri_compartment("hello") entry()
{
// attacks#begin
char unterminatedString[] = {
'N', 'o', ' ', 'n', 'u', 'l', 'l'};
uart_puts(unterminatedString);
Capability invalidPermissions = "Invalid permissions";
invalidPermissions.permissions() &= Permission::Store;
uart_puts(invalidPermissions);
char *invalidPointer = reinterpret_cast<char *>(12345);
uart_puts(invalidPointer);
uart_puts("Non-malicious string");
// attacks#end
}
16 changes: 16 additions & 0 deletions text/examples/check_arguments/hello.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Copyright CHERIoT Contributors.
// SPDX-License-Identifier: MIT

#include <compartment-macros.h>

/**
* Write `msg` to the default UART, including a trailing
* newline.
*
* Returns 0 on success, or a negative error code on
* failure.
*
* If the string is not null-terminated, prints only the
* length of the capability.
*/
int __cheri_compartment("uart") uart_puts(const char *msg);
50 changes: 50 additions & 0 deletions text/examples/check_arguments/uart.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Copyright CHERIoT Contributors.
// SPDX-License-Identifier: MIT

#include "hello.h"
#include <debug.hh>
#include <futex.h>
#include <locks.hh>
#include <platform-uart.hh>

// Import some useful things from the CHERI namespace.
using namespace CHERI;

// safe_uart#begin
/// Write a message to the UART.
int uart_puts(const char *msg)
{
static FlagLockPriorityInherited lock;
// Prevent concurrent invocation
LockGuard g(lock);
Timeout t{UnlimitedTimeout};
// Make sure that this is not going to be deallocated out
// from under us.
if (heap_claim_fast(&t, msg) != 0)
{
return -EINVAL;
}
// Check that this is a valid pointer with the correct
// permissions.
if (!check_pointer<PermissionSet{Permission::Load}>(msg))
{
return -EINVAL;
}
// Get the bounds (distance from address to top) of the
// pointer.
Capability buffer{msg};
size_t length = buffer.bounds();
// Write the data, one byte at a time.
for (size_t i = 0; i < length; i++)
{
char c = msg[i];
if (c == '\0')
{
break;
}
MMIO_CAPABILITY(Uart, uart)->blocking_write(c);
}
MMIO_CAPABILITY(Uart, uart)->blocking_write('\n');
return 0;
}
// safe_uart#end
42 changes: 42 additions & 0 deletions text/examples/check_arguments/xmake.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
-- Copyright Microsoft and CHERIoT Contributors.
-- SPDX-License-Identifier: MIT

set_project("CHERIoT example")

sdkdir = os.getenv("CHERIOT_SDK") or
"../../../rtos-source/sdk/"
includes(sdkdir)

set_toolchains("cheriot-clang")

option("board")
set_default("sail")

-- An example compartment that we can call
compartment("uart")
add_files("uart.cc")

-- Our entry-point compartment
compartment("hello")
add_files("hello.cc")

-- Firmware image for the example.
firmware("hello_world")
-- RTOS-provided libraries
add_deps("freestanding", "compartment_helpers")
-- Our compartments
add_deps("hello", "uart")
on_load(function(target)
-- The board to target
target:values_set("board", "$(board)")
-- Threads to select
target:values_set("threads", {
{
compartment = "hello",
priority = 1,
entry_point = "entry",
stack_size = 0x400,
trusted_stack_frames = 2
}
}, {expand = false})
end)
8 changes: 5 additions & 3 deletions text/examples/safebox/runner.cc
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
#include "safebox.h"
#include <platform-uart.hh>
#include <debug.hh>
#include <platform-uart.hh>

// runner#begin
using Debug = ConditionalDebug<true, "Runner">;

__cheri_compartment("runner") void entry()
{
Debug::log("Guess a number between 0 and 9 (inclusive)");
while (int c = MMIO_CAPABILITY(Uart, uart)->blocking_read())
while (int c =
MMIO_CAPABILITY(Uart, uart)->blocking_read())
{
if ((c < '0') || (c > '9'))
{
Expand All @@ -18,7 +19,8 @@ __cheri_compartment("runner") void entry()
c -= '0';
if (check_guess(c))
{
Debug::log("Correct! You guessed the secret was {}", c);
Debug::log("Correct! You guessed the secret was {}",
c);
}
}
}
Expand Down
5 changes: 3 additions & 2 deletions text/examples/safebox/safebox.cc
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#include "safebox.h"
#include <riscvreg.h>
#include <debug.hh>
#include <riscvreg.h>

// safebox#begin
using Debug = ConditionalDebug<true, "Safebox">;
Expand All @@ -10,7 +10,8 @@ bool check_guess(int guess)
static int secret = rdcycle64() % 10;
if (guess != secret)
{
Debug::log("Guess was {}, secret was {}", guess, secret);
Debug::log(
"Guess was {}, secret was {}", guess, secret);
secret = rdcycle64() % 10;
return false;
}
Expand Down
9 changes: 4 additions & 5 deletions text/examples/safebox/safebox.h
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
#include <compartment-macros.h>

/**
* Check whether a guess is correct. The compartment holds a secret value that
* is a number from 0-9. Returns true if the guess is correct.
* Check whether a guess is correct. The compartment holds
* a secret value that is a number from 0-9. Returns true
* if the guess is correct.
*/
__cheri_compartment("safebox")
bool check_guess(int guess);

__cheri_compartment("safebox") bool check_guess(int guess);
23 changes: 23 additions & 0 deletions text/examples/yolo_arguments/hello.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Copyright CHERIoT Contributors.
// SPDX-License-Identifier: MIT

#include "hello.h"
#include <cheri.hh>

using namespace CHERI;

/// Thread entry point.
void __cheri_compartment("hello") entry()
{
// attacks#begin
char unterminatedString[] = {
'N', 'o', ' ', 'n', 'u', 'l', 'l'};
uart_puts(unterminatedString);
Capability invalidPermissions = "Invalid permissions";
invalidPermissions.permissions() &= Permission::Store;
uart_puts(invalidPermissions);
char *invalidPointer = reinterpret_cast<char *>(12345);
uart_puts(invalidPointer);
uart_puts("Non-malicious string");
// attacks#end
}
16 changes: 16 additions & 0 deletions text/examples/yolo_arguments/hello.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Copyright CHERIoT Contributors.
// SPDX-License-Identifier: MIT

#include <compartment-macros.h>

/**
* Write `msg` to the default UART, including a trailing
* newline.
*
* Returns 0 on success, or a negative error code on
* failure.
*
* If the string is not null-terminated, prints only the
* length of the capability.
*/
int __cheri_compartment("uart") uart_puts(const char *msg);
42 changes: 42 additions & 0 deletions text/examples/yolo_arguments/uart.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Copyright CHERIoT Contributors.
// SPDX-License-Identifier: MIT

#include "hello.h"
#include <debug.hh>
#include <futex.h>
#include <locks.hh>
#include <platform-uart.hh>
#include <unwind.h>

// Import some useful things from the CHERI namespace.
using namespace CHERI;

// safe_uart#begin
/// Write a message to the UART.
int uart_puts(const char *msg)
{
// Prevent information disclosure, check that this does
// not overlap with our stack region. Check for obvious
// errors at the same time.
if (!check_pointer(msg))
{
return -EINVAL;
}
static FlagLockPriorityInherited lock;
// Prevent concurrent invocation
LockGuard g(lock);
int result = 0;
// Assume this is a null-terminated string, report an
// error on exceptions if not.
on_error(
[&]() {
for (const char *m = msg; *m != '\0'; m++)
{
MMIO_CAPABILITY(Uart, uart)->blocking_write(*m);
}
},
[&]() { result = -EINVAL; });
MMIO_CAPABILITY(Uart, uart)->blocking_write('\n');
return result;
}
// safe_uart#end
Loading

0 comments on commit 58a32fe

Please sign in to comment.