diff --git a/text/compartments.tex b/text/compartments.tex index b876b7e..5bab54f 100644 --- a/text/compartments.tex +++ b/text/compartments.tex @@ -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} @@ -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. @@ -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. @@ -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} @@ -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} diff --git a/text/examples/check_arguments/hello.cc b/text/examples/check_arguments/hello.cc new file mode 100644 index 0000000..0b4f034 --- /dev/null +++ b/text/examples/check_arguments/hello.cc @@ -0,0 +1,23 @@ +// Copyright CHERIoT Contributors. +// SPDX-License-Identifier: MIT + +#include "hello.h" +#include + +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(12345); + uart_puts(invalidPointer); + uart_puts("Non-malicious string"); + // attacks#end +} diff --git a/text/examples/check_arguments/hello.h b/text/examples/check_arguments/hello.h new file mode 100644 index 0000000..a233174 --- /dev/null +++ b/text/examples/check_arguments/hello.h @@ -0,0 +1,16 @@ +// Copyright CHERIoT Contributors. +// SPDX-License-Identifier: MIT + +#include + +/** + * 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); diff --git a/text/examples/check_arguments/uart.cc b/text/examples/check_arguments/uart.cc new file mode 100644 index 0000000..33b07c5 --- /dev/null +++ b/text/examples/check_arguments/uart.cc @@ -0,0 +1,50 @@ +// Copyright CHERIoT Contributors. +// SPDX-License-Identifier: MIT + +#include "hello.h" +#include +#include +#include +#include + +// 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(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 diff --git a/text/examples/check_arguments/xmake.lua b/text/examples/check_arguments/xmake.lua new file mode 100644 index 0000000..1f8348b --- /dev/null +++ b/text/examples/check_arguments/xmake.lua @@ -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) diff --git a/text/examples/safebox/runner.cc b/text/examples/safebox/runner.cc index 30052d9..9bddad7 100644 --- a/text/examples/safebox/runner.cc +++ b/text/examples/safebox/runner.cc @@ -1,6 +1,6 @@ #include "safebox.h" -#include #include +#include // runner#begin using Debug = ConditionalDebug; @@ -8,7 +8,8 @@ using Debug = ConditionalDebug; __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')) { @@ -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); } } } diff --git a/text/examples/safebox/safebox.cc b/text/examples/safebox/safebox.cc index 5eea412..a4749a3 100644 --- a/text/examples/safebox/safebox.cc +++ b/text/examples/safebox/safebox.cc @@ -1,6 +1,6 @@ #include "safebox.h" -#include #include +#include // safebox#begin using Debug = ConditionalDebug; @@ -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; } diff --git a/text/examples/safebox/safebox.h b/text/examples/safebox/safebox.h index 6692fa8..45bab02 100644 --- a/text/examples/safebox/safebox.h +++ b/text/examples/safebox/safebox.h @@ -1,9 +1,8 @@ #include /** - * 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); diff --git a/text/examples/yolo_arguments/hello.cc b/text/examples/yolo_arguments/hello.cc new file mode 100644 index 0000000..0b4f034 --- /dev/null +++ b/text/examples/yolo_arguments/hello.cc @@ -0,0 +1,23 @@ +// Copyright CHERIoT Contributors. +// SPDX-License-Identifier: MIT + +#include "hello.h" +#include + +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(12345); + uart_puts(invalidPointer); + uart_puts("Non-malicious string"); + // attacks#end +} diff --git a/text/examples/yolo_arguments/hello.h b/text/examples/yolo_arguments/hello.h new file mode 100644 index 0000000..a233174 --- /dev/null +++ b/text/examples/yolo_arguments/hello.h @@ -0,0 +1,16 @@ +// Copyright CHERIoT Contributors. +// SPDX-License-Identifier: MIT + +#include + +/** + * 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); diff --git a/text/examples/yolo_arguments/uart.cc b/text/examples/yolo_arguments/uart.cc new file mode 100644 index 0000000..52879a7 --- /dev/null +++ b/text/examples/yolo_arguments/uart.cc @@ -0,0 +1,42 @@ +// Copyright CHERIoT Contributors. +// SPDX-License-Identifier: MIT + +#include "hello.h" +#include +#include +#include +#include +#include + +// 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 diff --git a/text/examples/yolo_arguments/xmake.lua b/text/examples/yolo_arguments/xmake.lua new file mode 100644 index 0000000..b9a84d3 --- /dev/null +++ b/text/examples/yolo_arguments/xmake.lua @@ -0,0 +1,45 @@ +-- 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") + +-- uart#begin +-- An example compartment that we can call +compartment("uart") + add_files("uart.cc") + add_deps("unwind_error_handler") +-- uart#end + +-- 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) diff --git a/text/language_extensions.tex b/text/language_extensions.tex index 8e76207..d34146c 100644 --- a/text/language_extensions.tex +++ b/text/language_extensions.tex @@ -2,7 +2,7 @@ The CHERIoT platform adds a small number of C/C++ annotations to support the compartment model. -\section{Exposing compartment entry points} +\section[label=exporting_functions]{Exposing compartment entry points} Compartments are discussed in detail in \ref{compartments}. A compartment can expose functions as entry points via a simple attribute.