“Hello World” Example¶
Create Compartment¶
First we define a struct to describe the compartment.
/**
* Compartment descriptor
*/
typedef struct {
void *entry; // sentry for entry into compartment
void *exit; // sentry for return from compartment
void *stack; // callee's stack
void *target; // target function (sentry)
} cmpt_desc_t;
Then we can define a helper function to create a compartment.
static const cmpt_t *create_cmpt(cmpt_fun_t *target, unsigned stack_pages)
Inside this helper function, we first allocate some memory as the callee function’s stack.
#define STACK_PERMS (PERM_GLOBAL | READ_CAP_PERMS | WRITE_CAP_PERMS)
size_t pgsz = getpagesize();
size_t sz = pgsz * stack_pages;
void *stack = mmap(NULL, sz, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
// Note: setting bounds is going to be redundant here
// once kernel returns bounded capability.
stack = cheri_bounds_set(stack, sz);
stack = cheri_offset_set(stack, sz);
stack = cheri_perms_and(stack, STACK_PERMS);
Then we can allocate some memory for the metadata, which contains a compartment descriptor and then we can create a capability pair.
void *data = mmap(NULL, pgsz, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
cmpt_desc_t *desc = (cmpt_desc_t *)cheri_bounds_set_exact(data, sizeof(cmpt_desc_t));
// Compartment descriptor:
desc->stack = stack;
desc->target = target;
And we have two ways to call the function in the compartment, and they’re LB- and LPB-sealed capabilities.
LB-sealed Capabilities¶
LB-sealed capabilities will leave unsealed copy in C29 because it uses instruction
B(L)R [C29, #off]
to unseal C29 and branches to capability at [C29,#off]
.
When using LB-sealed, we can simply fill in the relevant information in the compartment descriptor.
desc->entry = cmpt_entry;
desc->exit = cmpt_return;
desc->stack = stack;
if (!cheri_is_sealed(target)) {
target = cheri_sentry_create(target);
}
desc->target = target;
// Return read-only LB-sealed pointer to cap pair:
return morello_lb_sentry_create(cheri_perms_and(desc, PERM_GLOBAL | READ_CAP_PERMS));
Also note that the cmpt_t
type is defined as
/**
* Compartment handle type (opaque).
*/
typedef struct {
void *data;
} cmpt_t;
And where the morello_lb_sentry_create
is a simple function with inline ASM that seals the cap pair.
/**
* Create LB-sealed sentry.
*/
inline static void * __capability morello_lb_sentry_create(void *cap)
{
void * __capability ret;
__asm__ ("seal %0, %1, lb" : "=C"(ret) : "C"(cap));
return ret;
}
LPB-sealed Capabilities¶
In contrast to LB-sealed capabilities, LPB-sealed capabilities will unseal temporarily
to load and branch capability pair. It uses the instruction LDPB(L)R C29, [Cn]
to
load C29<=[Cn]
and branches to [Cn,#16]
. In this way, we can access through LPB
cap without exposing unsealed copy.
When using LPB-sealed cap, we should do
// Compartment descriptor:
desc->stack = stack;
desc->target = target;
// Capability pair:
cmpt_t *cmpt = (cmpt_t *)cheri_bounds_set_exact(data + sizeof(cmpt_desc_t), sizeof(cmpt_t));
if (!cheri_is_sealed(target)) {
target = cheri_sentry_create(target);
}
cmpt->data[0] = cheri_perms_and(desc, PERM_GLOBAL | READ_CAP_PERMS); // data capability
cmpt->data[1] = cmpt_switch; // code capability
// Return read-only LPB-sealed pointer to cap pair:
return morello_lpb_sentry_create(cheri_perms_and(cmpt, PERM_GLOBAL | READ_CAP_PERMS));
and the cmpt_t
type is defined as follows.
/**
* Compartment handle type (opaque).
*/
typedef struct {
void *data[2];
} cmpt_t;
And where the morello_lpb_sentry_create
is a simple function with inline ASM that seals the cap pair.
/**
* Create LB-sealed sentry.
*/
inline static void * __capability morello_lpb_sentry_create(void *cap)
{
void * __capability ret;
__asm__ ("seal %0, %1, lpb" : "=C"(ret) : "C"(cap));
return ret;
}
Besides these code, we also have three trampline functions in assembly code when using LB-sealed cap,
namely, cmpt_call
, cmpt_entry
and cmpt_return
. They’re defined in
[lb.S]
while there two trampline functions when using LPB-sealed cap, which are cmpt_call
and cmpt_switch
,
and they’re defined in [lbp.S]
“Hello World” Example¶
Now we can create a hello world example based on what we have above. You can find the full code here, [compartmentalisation-helloworld]
We only show the main part of this Hello World example at the end. To compile them, we can first clone
the repo and then run gmake
. There will be two executables, hellolb
and hellolpb
, and as the
name suggests, they use LB- and LPB-sealed cap respectively. We can run them and see the output.
$ gmake
cc -march=morello+c64 -mabi=purecap -Xclang -morello-vararg=new -Iutil -DUSE_LB_SEALED_CAP util/capprint.c util/morello.c src/lb.S main.c -o hellolb
cc -march=morello+c64 -mabi=purecap -Xclang -morello-vararg=new -Iutil util/capprint.c util/morello.c src/lpb.S main.c -o hellolpb
$ ./hellolb
using LB-sealed capability
before...
csp: 0000fffffff7fe70 1 [0000ffffbff80000:0000fffffff80000) GrRMwWL-----I-V-23 none 1073741424 of 1073741824
inside...
csp: 0000000040a1ff40 1 [0000000040a1c000:0000000040a20000) GrRMwWL----------- none 16192 of 16384
after...
csp: 0000fffffff7fe70 1 [0000ffffbff80000:0000fffffff80000) GrRMwWL-----I-V-23 none 1073741424 of 1073741824
result: 2 + 3 = 5
$ ./hellolpb
using LPB-sealed capability
before...
csp: 0000fffffff7fe70 1 [0000ffffbff80000:0000fffffff80000) GrRMwWL-----I-V-23 none 1073741424 of 1073741824
inside...
csp: 0000000040a1ff40 1 [0000000040a1c000:0000000040a20000) GrRMwWL----------- none 16192 of 16384
after...
csp: 0000fffffff7fe70 1 [0000ffffbff80000:0000fffffff80000) GrRMwWL-----I-V-23 none 1073741424 of 1073741824
result: 2 + 3 = 5
#define _GNU_SOURCE
#include <stdio.h>
#include <stddef.h>
#include <stdbool.h>
#include <unistd.h>
#include <sys/mman.h>
#include "morello.h"
/**
* Wrappable function type.
*/
typedef void *(cmpt_fun_t)(void* arg);
#define STACK_PERMS (PERM_GLOBAL | READ_CAP_PERMS | WRITE_CAP_PERMS)
#ifdef USE_LB_SEALED_CAP
/**
* LB-sealed
* Compartment handle type (opaque).
*/
typedef struct {
void *data;
} cmpt_t;
/**
* Compartment descriptor
*/
typedef struct {
void *entry; // sentry for entry into compartment
void *exit; // sentry for return from compartment
void *stack; // callee's stack
void *target; // target function (sentry)
} cmpt_desc_t;
static const cmpt_t *create_cmpt(cmpt_fun_t *target, unsigned stack_pages)
{
size_t pgsz = getpagesize();
size_t sz = pgsz * stack_pages;
void *stack = mmap(NULL, sz, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
// Note: setting bounds is going to be redundant here
// once kernel returns bounded capability.
stack = cheri_bounds_set(stack, sz);
stack = cheri_offset_set(stack, sz);
stack = cheri_perms_and(stack, STACK_PERMS);
void *data = mmap(NULL, pgsz, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
cmpt_desc_t *desc = (cmpt_desc_t *)cheri_bounds_set_exact(data, sizeof(cmpt_desc_t));
desc->entry = cmpt_entry;
desc->exit = cmpt_return;
desc->stack = stack;
if (!cheri_is_sealed(target)) {
target = cheri_sentry_create(target);
}
desc->target = target;
// Return read-only LB-sealed pointer to cap pair:
return morello_lb_sentry_create(cheri_perms_and(desc, PERM_GLOBAL | READ_CAP_PERMS));
}
// See src/lb.S
// https://git.morello-project.org/morello/morello-examples/-/blob/main/src/compartments/src/lb.S
extern void *cmpt_call(const cmpt_t *cmpt, void *arg);
extern void cmpt_entry(void *arg);
extern void cmpt_return();
#else
/**
* LPB-sealed
* Compartment handle type (opaque).
*/
typedef struct {
void *data[2];
} cmpt_t;
/**
* Compartment descriptor
*/
typedef struct {
void *stack;
void *target;
} cmpt_desc_t;
static const cmpt_t *create_cmpt(cmpt_fun_t *target, unsigned stack_pages)
{
size_t pgsz = getpagesize();
size_t sz = pgsz * stack_pages;
void *stack = mmap(NULL, sz, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
// Note: setting bounds is going to be redundant here
// once kernel returns bounded capability.
stack = cheri_bounds_set(stack, sz);
stack = cheri_offset_set(stack, sz);
stack = cheri_perms_and(stack, STACK_PERMS);
void *data = mmap(NULL, pgsz, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
cmpt_desc_t *desc = (cmpt_desc_t *)cheri_bounds_set_exact(data, sizeof(cmpt_desc_t));
// Compartment descriptor:
desc->stack = stack;
desc->target = target;
// Capability pair:
cmpt_t *cmpt = (cmpt_t *)cheri_bounds_set_exact(data + sizeof(cmpt_desc_t), sizeof(cmpt_t));
if (!cheri_is_sealed(target)) {
target = cheri_sentry_create(target);
}
cmpt->data[0] = cheri_perms_and(desc, PERM_GLOBAL | READ_CAP_PERMS); // data capability
cmpt->data[1] = cmpt_switch; // code capability
// Return read-only LPB-sealed pointer to cap pair:
return morello_lpb_sentry_create(cheri_perms_and(cmpt, PERM_GLOBAL | READ_CAP_PERMS));
}
// See src/lpb.S
// https://git.morello-project.org/morello/morello-examples/-/blob/main/src/compartments/src/lpb.S
extern void *cmpt_call(const cmpt_t *cmpt, void *arg);
extern void *cmpt_switch(void *arg);
#endif
static void *fun(void *buffer)
{
printf("inside...\n");
printf("csp: %s\n", cap_to_str(NULL, cheri_csp_get()));
int *data = buffer;
int x = data[0];
int y = data[1];
int z = x + y;
data[2] = z;
return data;
}
int main(int argc, char *argv[])
{
#ifdef USE_LB_SEALED_CAP
printf("using LB-sealed capability\n");
#else
printf("using LPB-sealed capability\n");
#endif
const cmpt_t *cmpt = create_cmpt(fun, 4 /* pages */);
int buffer[3] = {2, 3, 0};
printf("before...\n");
printf("csp: %s\n", cap_to_str(NULL, cheri_csp_get()));
int *res = cmpt_call(cmpt, buffer);
printf("after...\n");
printf("csp: %s\n", cap_to_str(NULL, cheri_csp_get()));
printf("result: %d + %d = %d\n", res[0], res[1], res[2]);
return 0;
}