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 ].
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()
.
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: ccall wrappers
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
();
// 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);
...
}
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:
libcheri_ccall_init()
, more;// 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
// 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
// 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
If you could revise
the fundmental principles of
computer system design
to improve security...
... what would you change?