Example: User Defined Object Capabilities

The object type in user space is splited into two ranges:

  • non-system type numbers: [1, $2^{22}$ - 1 ];
  • system type numbers: [$2^{22}$, $2^{23}$ - 1 ];

while the object types in kernel space is [$2^{23}$, $2^{24}$ -1 ].

Example 1: static sandboxes

Code is derived from cheritest_ccall.c.

(Note: This portion of testing code in CheriBSD is commented out in cheritest.c, which means no available by default. Original comments:

Disable CCall/CReturn tests universally as nose doesn’t work with missing tests.

The first example is a hand-crafted, minimalist sandbox that don’t even have stacks: no local data, just contain one instruction creturn. For stacks, see hello world example

In this example, five steps are presented to create and exectute a sanbox:

  • First, an object type is allocated by libcheri_type_alloc(), which will return a capability with a type. Cheri Object Type

  • Second, the code and data capability is created using codecap_create(), datacap_create();

  • Third, the capability with a type is used as a sealing capability to seal the code and data capability, then we get a sealed code and a sealed data capability.

  • Fourth, a CHERI object is created using the two seal capabilities as code and data.

  • Fifth, the CHERI object is invoked via libcheri_invoke().

For code capability, need a function pointer as base, and another function pointer as the boundary enclosing the last instruction.

In this example, the sandbox contains a function named sandbox_creturn and ends on sandbox_creturn_end:

/* file: bin/cheritest/cheritest_sandbox.S */

	.text
	.type sandbox_creturn,@function
	.global sandbox_creturn
	.ent sandbox_creturn
sandbox_creturn:

	creturn
	.end sandbox_creturn

	.global sandbox_creturn_end
sandbox_creturn_end:
	.size sandbox_creturn, sandbox_creturn_end - sandbox_creturn
	.size sandbox_creturn_end, 0

For assembly basics.

Main testing code to create/execute the above sandbox is defined as below. It will create a pair of sealed code and data capabilities; then define them as an cheri_object co;, finally invoke it via libcheri_invoke by using the cheri object co:

// file:
//  bin/cheritest/cheritest_ccall.c

static void * __capability sandbox_creturn_sealcap;
static void * __capability sandbox_creturn_codecap;
static void * __capability sandbox_creturn_datacap;

/*
 * One-type setup for ccall-related tests.
 */
void
cheritest_ccall_setup(void)
{

    /*
    * Create sealing, sealed code, and sealed data capabilities.
    */
    sandbox_creturn_sealcap = libcheri_type_alloc();

    sandbox_creturn_codecap = cheri_seal(
        codecap_create(&sandbox_creturn, &sandbox_creturn_end), 
        sandbox_creturn_sealcap);

    sandbox_creturn_datacap = cheri_seal(
        datacap_create(&sandbox_creturn, &sandbox_creturn_end),
        sandbox_creturn_sealcap);

}

/*
 * CCall code that will immediately CReturn.
 */
void
test_nofault_ccall_creturn(const struct cheri_test *ctp __unused)
{
	struct cheri_object co;

    co.co_codecap = sandbox_creturn_codecap;
    co.co_datacap = sandbox_creturn_datacap;
    (void)libcheri_invoke(co, 0,
        0, 0, 0, 0, 0, 0, 0, 0,
        NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
	cheritest_success();
}

From codecap_create(&sandbox_creturn, &sandbox_creturn_end) and datacap_create(&sandbox_creturn, &sandbox_creturn_end), we can see that data capability is set to have the same range (base and bounds) as code capability. The different permission on the code and data capabilities would limit how they can be used.

The example above create code and data capability pair by codecap_create() and datacap_create(). Then use cheri_seal to seal the code and data capability with a type allocated from libcheri_type_alloc().

codecap_create() and datacap_create()

There are two ABIs can be used to create data/code capability:

In pure-cap ABI, cheri_andperm is used to set permission bits for the already existing code or data capability (LLM: when the size of the code capability is determined ???? ).

For hybrid ABI, cheri_codeptrperm creates the code capability and cheri_ptrperm creates the data capability, from a pointer, where its size, and perms are given.

Here is the definition of codecap_create():

static void * __capability
codecap_create(void (*sandbox_base)(void), void *sandbox_end)
{
	void * __capability codecap;

#ifdef __CHERI_PURE_CAPABILITY__
	(void)sandbox_end;
	codecap = cheri_andperm(sandbox_base,
	    CHERI_PERM_GLOBAL | CHERI_PERM_LOAD | CHERI_PERM_EXECUTE);
#else
	codecap = cheri_codeptrperm(sandbox_base,
	    (size_t)sandbox_end - (size_t)sandbox_base,
	    CHERI_PERM_GLOBAL | CHERI_PERM_LOAD | CHERI_PERM_EXECUTE);
#endif
	return (codecap);
}

Definition of datacap_create():

//file:
//   ./bin/cheritest/cheritest_ccall.c

static void * __capability
datacap_create(void *sandbox_base, void *sandbox_end)
{
	void * __capability datacap;

#ifdef __CHERI_PURE_CAPABILITY__
	(void)sandbox_end;
	datacap = cheri_andperm(sandbox_base,
	    CHERI_PERM_GLOBAL | CHERI_PERM_LOAD | CHERI_PERM_STORE |
	    CHERI_PERM_LOAD_CAP | CHERI_PERM_STORE_CAP |
	    CHERI_PERM_STORE_LOCAL_CAP);
#else
	datacap = cheri_ptrperm(sandbox_base,
	    (size_t)sandbox_end - (size_t)sandbox_base,
	    CHERI_PERM_GLOBAL | CHERI_PERM_LOAD | CHERI_PERM_STORE |
	    CHERI_PERM_LOAD_CAP | CHERI_PERM_STORE_CAP |
	    CHERI_PERM_STORE_LOCAL_CAP);
#endif
	return (datacap);
}

libcheri_invoke()

libcheri_invoke: ccall wrappers

Cheri Perm definitions

Cheri Permission Constants

Example 2: hello world sandbox

Overview

Initialization of sandbox: sandbox mechanisms must be initialized before being used. This includes ???…

Creation

Once a user sandbox is created and need system service, the user sandbox need to call a method in the system sandbox. The path to system sandbox method:

bin/cheri_helloworld/cheri_helloworld.c: main() ->
lib/libhelloworld/compartment/helloworld.c: call_libcheri_system_helloworld() ->
lib/libcheri/libcheri_system.h: libcheri_system_helloworld();

main func

// file:
//  bin/cheri_helloworld/cheri_helloworld.c
main(void)
{
    struct sandbox_object *sbop;
    int ret;
    struct cheri_object stdout_fd;

    libcheri_init();

    if (libcheri_fd_new(STDOUT_FILENO, &sbop) < 0)
            err(EX_OSFILE, "libcheri_fd_new: stdout");

    ret = call_libcheri_system_helloworld();
    assert(ret == 123456);
    ...
}

Initialization of libcheri

libcheri_init()

libcheri_init() is a centralised constructor for libcheri to ensure that initialisation happens in the desired order [vs. multiple constructors]. Users wish to create sandboxes should call this to make sure libcheri is property initialized.

// file
//  lib/libcheri/libcheri_init.c

void
libcheri_init(void)
{

	if (libcheri_initialised)
		return;

	/*
	 * Must initialise sealing capabilities before other aspects of
	 * libcheri.
	 */
	libcheri_ccall_init();
	libcheri_stack_init();
	libcheri_enter_init();
	sandbox_init();
	libcheri_initialised = 1;
}

4 pieces of initialization:

  • The sealing capabilities used for invocation, rtld, and creturn are created; the sealed capabilities that are shared across many sandboxes are created; done in libcheri_ccall_init(), more;
  • Ensure thread-local storage for the first thread’s trusted stack is suitably aligned. in libcheri_stack_init();
  • Allocate and initialize a stack for landing system class code libcheri_enter_init();
  • load sandbox setups from binary ELF file: required/provided methods from program binary code sandbox_init();

Call system sandbox methods in user sandbox

// file:
//  bin/cheri_helloworld/cheri_helloworld.c

	if (libcheri_fd_new(STDOUT_FILENO, &sbop) < 0)
		err(EX_OSFILE, "libcheri_fd_new: stdout");

	ret = call_libcheri_system_helloworld();
	assert(ret == 123456);

	ret = call_libcheri_system_puts();
	assert(ret >= 0);

	stdout_fd = sandbox_object_getobject(sbop);
	ret = call_libcheri_fd_write_c(stdout_fd);
	assert(ret == 12);

	libcheri_fd_destroy(sbop);

libcheri_fd_new/destroy

libcheri_fd_new

// file:
//  lib/libcheri/libcheri_fd.c

/*
 * XXXRW: libcheri system objects must have a corresponding sandbox_object to
 * use during domain transition.  Define one here.
 */

/*
 * Allocate a new libcheri_fd object for an already-open file descriptor.
 *
 * XXXRW: What to return in the userspace CCall world order?  The sandbox..?
 */
int
libcheri_fd_new(int fd, struct sandbox_object **sbopp)
{
        void * __capability invoke_pcc;
        struct libcheri_fd *lcfp;

        lcfp = calloc(1, sizeof(*lcfp));
        if (lcfp == NULL) {
                errno = ENOMEM;
                return (-1);
        }
        lcfp->lcf_fd = fd;

        /*
         * Construct a code capability for this class; for system classes,
         * this is just the ambient $pcc with the offset set to the entry
         * address.
         *
         * XXXRW: Possibly, we should just pass libcheri_fd to sandbox
         * creation rather than embedding this logic in each system class?
         */
        invoke_pcc = cheri_setoffset(cheri_getpcc(),
            (register_t)LIBCHERI_CLASS_ENTRY(libcheri_fd));

        /*
         * Set up system-object state for the sandbox.
         */
        if (sandbox_object_new_system_object(
            (__cheri_tocap void * __capability)(void *)lcfp, invoke_pcc,
            libcheri_fd_vtable, &lcfp->lcf_sbop) != 0) {
                free(lcfp);
                return (-1);
        }
        *sbopp = lcfp->lcf_sbop;
        return (0);
}

libcheri_fd_destroy

// file:
//  lib/libcheri/libcheri_fd.c

/*
 * Actually free a libcheri_fd.  This can only be done if there are no
 * outstanding references in any sandboxes (etc).
 */
void
libcheri_fd_destroy(struct sandbox_object *sbop)
{
        struct libcheri_fd * __capability lcfp;

        lcfp = sandbox_object_getsandboxdata(sbop);
        sandbox_object_destroy(sbop);
        free((__cheri_fromcap struct libcheri_fd *)lcfp);
}

call_libcheri_system_helloworld/puts

call_libcheri_system_helloworld

// file
//  lib/libhelloworld/compartment/helloworld.c

int
call_libcheri_system_helloworld(void)
{

        return (libcheri_system_helloworld());
}

call_libcheri_system_puts

// file
//  lib/libhelloworld/compartment/helloworld.c

int call_libcheri_system_puts(void)
{
        char * __capability hello_world_str_c;

        hello_world_str_c = cheri_ptrperm(&hello_world_str,
            sizeof(hello_world_str), CHERI_PERM_LOAD); /* Nul-terminated. */

        return (libcheri_system_puts(hello_world_str_c));
}

Those are ‘wrappers’ of libcheri system methods. They call into system sandbox by calling libcheri_system_helloworld() and libcheri_system_puts(...), which is declared as LIBCHERI_SYSTEM_CALL attributes:

// file: libcheri_system.c

/*
 * Methods themselves.
 */
LIBCHERI_SYSTEM_CCALL
int     libcheri_system_helloworld(void);
LIBCHERI_SYSTEM_CCALL
int     libcheri_system_puts(const char * __capability str);

// file: lib/libcheri/libcheri_system.h
#ifdef LIBCHERI_SYSTEM_INTERNAL
#define LIBCHERI_SYSTEM_CCALL                                   \
    __attribute__((cheri_ccallee))                              \
    __attribute__((cheri_method_class(_libcheri_system_object)))
#else
#define LIBCHERI_SYSTEM_CCALL                                   \
    __attribute__((cheri_ccall))                                \
    __attribute__((cheri_method_suffix("_cap")))                \
    __attribute__((cheri_method_class(_libcheri_system_object)))
#endif
Created Jul 5, 2019 // Last Updated Sep 10, 2019

If you could revise
the fundmental principles of
computer system design
to improve security...

... what would you change?