diff --git a/.buildbot.sh b/.buildbot.sh new file mode 100644 index 0000000..33a79dd --- /dev/null +++ b/.buildbot.sh @@ -0,0 +1,40 @@ +#!/bin/bash + +set -e + +build_dir="$(pwd)/build" +src_dir="$(pwd)" +cheri_dir="/home/buildbot/cheri/output" + +# Build project locally +export CC=$cheri_dir/morello-sdk/bin/clang +export CFLAGS="--config cheribsd-morello-hybrid.cfg" +export ASMFLAGS="--config cheribsd-morello-hybrid.cfg" + +cmake \ + -G Ninja \ + -DCMAKE_BUILD_TYPE=DEBUG \ + -DLLVM_DIR=$cheri_dir/morello-sdk/lib/cmake/llvm \ + -DClang_DIR=$cheri_dir/morello-sdk/lib/cmake/clang \ + -B $build_dir \ + -S $src_dir +cmake --build $build_dir + +# Set arguments for Morello hybrid instance +export SSHPORT=10086 +export PYTHONPATH="/home/buildbot/build/test-scripts" + +args=( + --architecture morello-hybrid + # Qemu System to use + --qemu-cmd $cheri_dir/morello-sdk/bin/qemu-system-morello + --bios edk2-aarch64-code.fd + --disk-image $cheri_dir/cheribsd-morello-hybrid.img + # Required build-dir in CheriBSD + --build-dir . + --ssh-port $SSHPORT + --ssh-key $HOME/.ssh/id_ed25519.pub + ) + +# Spawn CHERI QEMU instance, then execute `ctest` +python3 $src_dir/.run_cheri_qemu_and_test.py "${args[@]}" diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8972cb6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +build/ + +**/*.swp + +build.sh +up.sh diff --git a/.run_cheri_qemu_and_test.py b/.run_cheri_qemu_and_test.py new file mode 100644 index 0000000..e56a2bc --- /dev/null +++ b/.run_cheri_qemu_and_test.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python3 + +# Adapted from +# https://github.com/capablevms/cheri-examples/blob/master/tests/run_cheri_examples.py + +import argparse +import importlib.util +import os +import subprocess +import sys + +from pathlib import Path + +# Emulate `sys.path` from path of module `run_tests_common` (found via +# environment variable `PYTHONPATH`), as required by the CHERI testing +# infrastructure which we are using to simplify booting a QEMU instance +test_scripts_dir = str(Path(importlib.util.find_spec("run_tests_common").origin).parent.absolute()) +sys.path = sys.path[sys.path.index(test_scripts_dir):] + +from run_tests_common import boot_cheribsd, run_tests_main + +def run_tests(qemu: boot_cheribsd.QemuCheriBSDInstance, args: argparse.Namespace) -> bool: + if args.sysroot_dir is not None: + boot_cheribsd.set_ld_library_path_with_sysroot(qemu) + boot_cheribsd.info("Running tests for cheri-morello-compartmentalisation") + + # Run command on host to test the executed client + os.chdir(f"{args.build_dir}/build") + subprocess.run(["/usr/bin/ctest", "-V"], check=True) + return True + +if __name__ == '__main__': + # This call has the side-effect of booting a QEMU instance + run_tests_main(test_function=run_tests, need_ssh=True, should_mount_builddir=False) diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..d73a9a9 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,24 @@ +cmake_minimum_required(VERSION 3.16) +project(CheriMorelloCompartments LANGUAGES C ASM) + +set(INCLUDE_DIR ${CMAKE_SOURCE_DIR}/include) +set(SRC_DIR ${CMAKE_SOURCE_DIR}/src) +set(TEST_DIR ${CMAKE_SOURCE_DIR}/tests) + +# Setup +include_directories("${INCLUDE_DIR}") + +find_package(Clang REQUIRED) +find_package(LLVM REQUIRED CONFIG) + +# Building +add_subdirectory(${SRC_DIR}) + +# Testing +if (CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME) + include(CTest) +endif() + +if (CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME AND BUILD_TESTING) + add_subdirectory(${TEST_DIR}) +endif() diff --git a/COPYRIGHT b/COPYRIGHT new file mode 100644 index 0000000..8b2ad70 --- /dev/null +++ b/COPYRIGHT @@ -0,0 +1,17 @@ +Except as otherwise noted (below and/or in individual files), this project is +licensed under the Apache License, Version 2.0 + or the MIT license +, at your option. + +Copyright is retained by contributors and/or the organisations they +represent(ed) -- this project does not require copyright assignment. Please see +version control history for a full list of contributors. Note that some files +may include explicit copyright and/or licensing notices. + +The following contributors wish to explicitly make it known that the copyright +of their contributions is retained by an organisation: + + Jacob Bramley : + copyright retained by Arm Limited + Laurence Tratt : + copyright retained by King's College London diff --git a/LICENSE-APACHE b/LICENSE-APACHE new file mode 100644 index 0000000..3d127b7 --- /dev/null +++ b/LICENSE-APACHE @@ -0,0 +1,10 @@ +Licensed under the Apache License, Version 2.0 (the "License"); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. diff --git a/LICENSE-MIT b/LICENSE-MIT new file mode 100644 index 0000000..410e04e --- /dev/null +++ b/LICENSE-MIT @@ -0,0 +1,17 @@ +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/bors.toml b/bors.toml new file mode 100644 index 0000000..db1bdaa --- /dev/null +++ b/bors.toml @@ -0,0 +1,10 @@ +# Sourced from +# https://github.com/capablevms/cheri-examples/blob/master/bors.toml +status = ["buildbot/capablevms-test-script"] + +timeout_sec = 600 # 10 minutes + +# Have bors delete auto-merged branches +delete_merged_branches = true + +cut_body_after = "" diff --git a/include/comps_offsets.h b/include/comps_offsets.h new file mode 100644 index 0000000..f1edbfe --- /dev/null +++ b/include/comps_offsets.h @@ -0,0 +1,6 @@ +#define COMP_SIZE 48 +#define COMP_OFFSET_DDC 0 +#define COMP_OFFSET_PCC 16 +#define COMP_OFFSET_STK_ADDR 32 + +#define MAX_COMP_COUNT 2 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..f14286d --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,4 @@ +add_library(morello-compartments STATIC + ${SRC_DIR}/manager.S + ${SRC_DIR}/switcher.S + ) diff --git a/src/manager.S b/src/manager.S new file mode 100644 index 0000000..f6915fd --- /dev/null +++ b/src/manager.S @@ -0,0 +1,131 @@ +.data + +comps_cnt: .dword 0 +comps_addr: .dword 0 +switcher_caps: .dword 0 + +.text +.balign 4 + +#include "comps_offsets.h" + +// Variables +.global comps_cnt +.global comps_addr +.global switcher_caps + +// Functions +.global init_compartments +.global add_compartment +.global del_compartment + +/** + * Sets up memory for compartments + * + * Initializes required memory for compartments. This involves allocating a + * memory region for use by switcher code, to contain required capabilities, + * and deriving appropriate PCC/DDC values containing the executable switcher + * code, and the aforementioned memory region, respectively. + * + * @return Pointer to newly allocated memory region + */ +.type init_compartments, "function" +init_compartments: + // Compute size of required memory, equivalent to `length` parameter of + // `mmap` + mov x1, #(32 + (COMP_SIZE * MAX_COMP_COUNT)) + + // Store length and `lr` on stack, as we'll need them later + stp x1, lr, [sp, #-16]! + + // Allocate memory for switcher + mov x0, xzr // address + // length - already stored in `x1` + mov w2, #3 // prot == PROT_READ | PROT_WRITE + mov w3, #4098 // flags == MAP_PRIVATE | MAP_ANONYMOUS + mov w4, #-1 // fd + mov w5, wzr // offset + bl mmap + + // Restore length and `lr` + ldp x1, lr, [sp], #16 + + // Save pointer to new allocated memory in `switcher_caps` + adr x2, switcher_caps + str x0, [x2] + + // Derive DDC for switcher + cvtp c2, x0 + scbnds c2, c2, x1 + + // Derive PCC for `switch_compartments` and friends + adr x3, switcher_entry + adr x4, switch_compartment_end + sub x4, x4, x3 + cvtp c3, x3 + scbndse c3, c3, x4 + + // Store (DDC, PCC) at `switcher_caps` (saved in `x0`) + mov x1, x0 + stp c2, c3, [x1], #32 + + // Save start address for compartment capabilities in `comps_addr` + adr x2, comps_addr + str x1, [x2] + + ret + +/** + * Function to add information for a compartment + * + * @param x0 Compartment memory size + * @param x1 Compartment executable function + * + * @return Pointer to newly allocated memory region + */ +.type add_compartment, "function" +add_compartment: + // Store inputs and `lr` so we can call `mmap` + stp x0, x1, [sp, #-32]! + str lr, [sp, #16] + mov x1, x0 + + // Allocate memory for new compartment + mov x0, xzr // address + // length - already stored in `x1` + mov w2, #3 // prot == PROT_READ | PROT_WRITE + mov w3, #4098 // flags == MAP_PRIVATE | MAP_ANONYMOUS + mov w4, #-1 // fd + mov w5, wzr // offset + bl mmap + + // Restore memory size and function address + ldp x1, x2, [sp], #32 + ldr lr, [sp, #-16] + + // Derive compartment DDC + cvtp c0, x0 + scbnds c0, c0, x1 + + // Derive compartment PCC + cvtp c1, x2 + mov x2, #320 // TODO dynamic value + scbndse c1, c1, x2 + + // Store new PCC and DDC + ldr x2, comps_addr + ldr x3, comps_cnt + mov x4, #COMP_SIZE + madd x2, x3, x4, x2 + stp c0, c1, [x2] + + // Increment counter + adr x4, comps_cnt + add x3, x3, #1 + str x3, [x4] + + // Check we have not exceeded max number of compartments + cmp x3, #MAX_COMP_COUNT + b.gt abort + + ret diff --git a/src/switcher.S b/src/switcher.S new file mode 100644 index 0000000..4058b05 --- /dev/null +++ b/src/switcher.S @@ -0,0 +1,132 @@ +#include "comps_offsets.h" + +.text +.balign 4 + +.global switcher_entry +.global switch_compartment +.global switch_compartment_end + +/** + * Entry point from user code to switcher function + * + * @param c0 DDC of switcher, containing compartment information + */ +.type switcher_entry, "function" +switcher_entry: + mov c29, c0 + mov x0, #0 + cvtp clr, lr + // fall through into switch_compartment + +/** Code to perform actual switch + * + * @param x0 ID (in `comps` array) of compartment to switch to + */ +.type switch_compartment, "function" +switch_compartment: + // Store entering compartment's DDC, and move to memory containing + // compartment info + mrs c2, DDC + mov x10, x0 + + // Expect switcher DDC in c29 + msr DDC, c29 + + // Get compartment to switch to data + mov x11, #COMP_SIZE + mul x10, x10, x11 + + // Load PCC, including function we are jumping to within compartment + add x11, x10, #COMP_OFFSET_PCC + ldr c0, [x29, x11] + + // Load DDC + add x11, x10, #COMP_OFFSET_DDC + ldr c1, [x29, x11] + + // Setup SP + mov x12, sp + add x11, x10, #COMP_OFFSET_STK_ADDR + ldr x11, [x29, x11] + mov sp, x11 + + // Install compartment DDC + msr DDC, c1 + + // Save old DDC (c2), old SP (x12), old CLR (clr) on stack + stp c2, clr, [sp, #-48]! + str x12, [sp, #32] + + // Stack layout at this point: + // + // `stack + size` -> ________________________ + // sp + 40 -> [ ] ^ + // sp + 32 -> [ old SP ] | + // sp + 24 -> [ old CLR (hi64) ] | + // sp + 16 -> [ old CLR (lo64) ] | + // sp + 8 -> [ old DDC (high 64) ] | DDC bounds + // sp + 0 -> [ old DDC (low 64) ] | + // : : + // `stack` -> ________________________v + + // Clean all registers, except register used to call function within + // compartment we are transitioning to + bl clean+4 + + // Jump to the function within the compartment we are switching to (this + // also sets PCC) + blr c0 + + // Clean capabilities left in the return value. + bl clean + + // Restore the caller's context and compartment. + ldp c10, clr, [sp] + ldr x12, [sp, #32] + msr DDC, c10 + mov x10, #0 + mov sp, x12 + + ret clr + +clean: + mov x0, #0 + mov x1, #0 + mov x2, #0 + mov x3, #0 + mov x4, #0 + mov x5, #0 + mov x6, #0 + mov x7, #0 + mov x8, #0 + mov x9, #0 + mov x10, #0 + mov x11, #0 + mov x12, #0 + mov x13, #0 + mov x14, #0 + mov x15, #0 + mov x16, #0 + mov x17, #0 + // x18 is the "platform register" (for some platforms). If so, it needs to + // be preserved, but here we assume that only the lower 64 bits are + // required. + mov x18, x18 + // x19-x29 are callee-saved, but only the lower 64 bits. + mov x19, x19 + mov x20, x20 + mov x21, x21 + mov x22, x22 + mov x23, x23 + mov x24, x24 + mov x25, x25 + mov x26, x26 + mov x27, x27 + mov x28, x28 + mov x29, x29 // FP + // We need LR (x30) to return. The call to this helper already cleaned it. + // Don't replace SP; this needs special handling by the caller anyway. + ret + +switch_compartment_end: diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..17bc8e6 --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,10 @@ +function(new_proj_test test_name) + add_executable(${test_name} + ${test_name}.c) + target_link_libraries(${test_name} morello-compartments) + add_test(NAME ${test_name} + COMMAND ${CMAKE_SOURCE_DIR}/tests/run_test.sh $) +endfunction() + +new_proj_test(simple_init) +new_proj_test(simple_add) diff --git a/tests/run_test.sh b/tests/run_test.sh new file mode 100755 index 0000000..06a8039 --- /dev/null +++ b/tests/run_test.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +set -x +set -e + +if [ $# -ne 1 ] +then + echo "Expected one parameter: path to executable." + exit 1 +fi + +CHERIBSD_PORT=10086 +CHERIBSD_USER=root +CHERIBSD_HOST=localhost + +scp -o "StrictHostKeyChecking no" -P $CHERIBSD_PORT $1 $CHERIBSD_USER@$CHERIBSD_HOST: +ssh -o "StrictHostKeyChecking no" -p $CHERIBSD_PORT $CHERIBSD_USER@$CHERIBSD_HOST -t ./$(basename $1) diff --git a/tests/simple_add.c b/tests/simple_add.c new file mode 100644 index 0000000..af334a7 --- /dev/null +++ b/tests/simple_add.c @@ -0,0 +1,60 @@ +#include +#include +#include +#include + +#include "cheriintrin.h" + +#include "comps_offsets.h" + +static_assert(COMP_SIZE == sizeof(void* __capability) * 3, "Invalid `COMP_SIZE` provided"); +static_assert(COMP_OFFSET_DDC == 0, "Invalid `COMP_OFFSET_DDC` provided."); +static_assert(COMP_OFFSET_PCC == sizeof(void* __capability) * 1, "Invalid `COMP_OFFSET_PCC` provided."); +static_assert(COMP_OFFSET_STK_ADDR == sizeof(void* __capability) * 2, "Invalid `COMP_OFFSET_STK_LEN` provided."); + +/******************************************************************************* + * Extern functions + ******************************************************************************/ + +extern void* __capability * comps_addr; +extern size_t comps_cnt; + +extern void* init_compartments(); +extern void* add_compartment(size_t, void*); + +/******************************************************************************* + * Main + ******************************************************************************/ + +int comp_f_fn(); + +int +main() +{ + init_compartments(); + + size_t comp_size = 2000; + assert(add_compartment(comp_size, comp_f_fn) != MAP_FAILED); + + assert(comps_cnt == 1); + + void* __capability comp_ddc = comps_addr[0]; + assert(cheri_is_valid(comp_ddc)); + assert(cheri_length_get(comp_ddc) == comp_size); + + void* __capability comp_pcc = comps_addr[1]; + assert(cheri_is_valid(comp_pcc)); + assert(cheri_address_get(comp_pcc) == (unsigned long) comp_f_fn); + + return 0; +} + +/******************************************************************************* + * Compartments + ******************************************************************************/ + +int +comp_f_fn() +{ + return 42; +} diff --git a/tests/simple_init.c b/tests/simple_init.c new file mode 100644 index 0000000..d897c68 --- /dev/null +++ b/tests/simple_init.c @@ -0,0 +1,49 @@ +#include +#include +#include +#include + +#include "cheriintrin.h" + +#include "comps_offsets.h" + +static_assert(COMP_SIZE == sizeof(void* __capability) * 3, "Invalid `COMP_SIZE` provided"); +static_assert(COMP_OFFSET_DDC == 0, "Invalid `COMP_OFFSET_DDC` provided."); +static_assert(COMP_OFFSET_PCC == sizeof(void* __capability) * 1, "Invalid `COMP_OFFSET_PCC` provided."); +static_assert(COMP_OFFSET_STK_ADDR == sizeof(void* __capability) * 2, "Invalid `COMP_OFFSET_STK_LEN` provided."); + +/******************************************************************************* + * Extern functions + ******************************************************************************/ + +extern void* init_compartments(); +extern void* __capability * switcher_caps; + +extern void switcher_entry(); +extern void switch_compartment_end(); + +/******************************************************************************* + * Main + ******************************************************************************/ + +int +main() +{ + void* inner_addr = init_compartments(); + + assert(inner_addr != MAP_FAILED); + assert(switcher_caps == inner_addr); + + void* __capability switcher_ddc = switcher_caps[0]; + assert(cheri_is_valid(switcher_ddc)); + assert(cheri_length_get(switcher_ddc) == + COMP_SIZE * MAX_COMP_COUNT + 2 * sizeof(void* __capability)); + + void* __capability switcher_pcc = switcher_caps[1]; + assert(cheri_is_valid(switcher_pcc)); + assert(cheri_address_get(switcher_pcc) == (unsigned long) switcher_entry); + assert(cheri_address_get(switcher_pcc) + cheri_length_get(switcher_pcc) == + (unsigned long) switch_compartment_end); + + return 0; +}