Userlevel software compartmentalization (experimental)

CheriBSD implements two different CHERI-enabled software compartmentalization models:

  • Colocated processes (co-processes) compartmentalization accelerates UNIX Inter-Process Communication (IPC) and context switching by colocating multiple processes in the same address space, separating them using CHERI capabilities. Support for co-processes is maintained in an experimental development branch and is not included in current software releases. For more information see the Colocation Tutorial wiki page.
  • Dynamic-linker-based compartmentalization isolates shared libraries within a process using CHERI capabilities limiting the access of attackers who have achieved arbitrary code execution within a library. The linker-based library compartmentalization model has been included since the 22.12 release of CheriBSD. See the c18n(3) man page on an installed system for more information.

Library compartmentalization

CheriBSD's library compartmentalization feature (c18n) executes each dynamic library within a compartmentalization-enabled process in its own protection domain. The non-default c18n-enabled run-time linker grants libraries capabilities only to resources (global variables, APIs) declared in their ELF linkage. Function calls that cross domain boundaries are interposed on by domain-crossing shims implemented by the run-time linker.

The adversary model for these compartments is one of trusted code but untrustworthy execution: a library such as libpng or libjpeg is trusted until it begins dynamic execution -- and has potentially been exposed to malicious data. With library compartmentalization, an adversary who achieves arbitrary code execution within the library at run time will be able to reach only the resources (and further attack surfaces) declared statically through its linkage. The programmer must then harden that linkage, and any involved APIs, to make them suitable for adversarial engagement -- but the foundation of isolation, controlled access, and controlled domain transition is provided by the c18n implementation.

In addition to a modified run-time linker, modest changes have been made to the aarch64c calling convention to avoid assumptions such as implicit stack sharing between callers and callees across library boundaries when passing variadic argument lists. This modified ABI is now used by all CheriABI binaries in CheriBSD, and so off-the-shelf aarch64c binaries and libraries can be used with library compartmentalization without recompilation to the modified ABI. More information on library compartmentalization can be found in the c18n(3) man page:

man c18n

Compiling applications for library compartmentalization

To compile a main application to use library compartmentalization, add the following flags to compilation of the program binary:

-Wl,--dynamic-linker=/libexec/ld-elf-c18n.so.1

For example, compile helloworld.c using:

cc -Wall -g -o helloworld helloworld.c -Wl,--dynamic-linker=/libexec/ld-elf-c18n.so.1

You can confirm whether a binary uses the c18n run-time linker by inspecting it using the file command:

file helloworld

Tracing compartment-boundary crossings

The BSD ktrace(1) command is able to trace compartment-boundary crossings. To enable this feature, set the LD_C18N_UTRACE_COMPARTMENT environmental variable, which will cause the c18n run-time linker to emit records using the utrace(2) system call. Run the program under ktrace with the -tu argument to capture only those records (and not a full system-call trace):

env LD_C18N_UTRACE_COMPARTMENT=1 ktrace -tu ./helloworld

The resulting ktrace.out file can be viewed using the kdump(1) command:

kdump

It is important to understand, however, that simply isolating running code is almost always insufficient to achieve robust sandboxing. The competent adversary will now consider further rights and attack surfaces to explore in search of further vulnerabilities. While this increased work factor of finding additional vulnerabilities is an important part of compartmentalization, internal software APIs are rarely well suited to be security boundaries without performing additional hardening. With this in mind, you can:

  • Inspect the source code, output from objdump, and output from chericat to assess the robustness of this compartmentalization. You can install objdump with pkg64 install llvm-base and chericat with pkg64c install chericat.
  • Consider larger software architectural changes that will allow a library to be used more robustly when running within a computerment.

Library compartmentalization has the potential to significantly improve software integrity and confidentiality properties in the presence of a strong adversary. However, it is also limited by the abstraction being around the current library operational model.