-
Notifications
You must be signed in to change notification settings - Fork 13
Description
#44 is our first attempt to write a DDC-altering compartmentalisation scheme. We're trying to keep that PR simple, so it doesn't do everything we think we want. This issue is a quick attempt to sketch out a more powerful (and secure) API. This won't be complete, but it might help us think about where we want to go.
I think the basic C API for a data-only compartmentalisation scheme might look something like:
// Create a compartment with an initial capacity of `size` bytes.
void *__capability compartment_create(size_t size);
// Call function `f` in compartment `comp`, passing `param` to `f`
// and returning the capability returned by `f`. `f` will be passed
// a pointer to the range of memory on the heap covered by the DDC.
void *__capability compartment_call(
void *__capability comp,
void *__capability (*f)(void *heap, void *__capability), void *__capability param
);where the compartment_* functions must (I think) be sentries that can access any compartment created by them. My assumption is that the compartments returned by compartment_malloc are sealed so that a caller can't do anything with them other than call compartment_call (and compartment_call can carefully validate the capability!). An interesting question is whether f has to be passed heap -- I guess it could recreate it from the DDC but that seems horrible. One will also need compartment_free and probably compartment_realloc: both seem easy enough not to need sketching out.
To use this API in a basic way, I would do something like:
void *__capability f(void *heap, void *__capability param) {
// Do whatever with `heap` or `param`.
return NULL;
}
void main() {
void *__capability comp1 = compartment_malloc(100);
void *__capability comp2 = compartment_malloc(100);
compartment_call(comp1, f, NULL);
compartment_call(comp2, f, NULL);
}This would create two compartments and then call f in the context of comp1 and then call f in the context of comp2.
I can pass compartments around and call one compartment from another:
void *__capability f1(void *heap, void *__capability param) {
// f is called in the context of `comp1`.
compartment_call(param, f2, NULL);
return NULL;
}
void *__capability f2(void *heap, void *__capability param) {
// f2 is called in the context of `comp2`.
return NULL;
}
void main() {
void *__capability comp1 = compartment_malloc(100);
void *__capability comp2 = compartment_malloc(100);
compartment_call(comp1, f1, comp2);
}In terms of the underlying memory layout, my assumption is that compartment_malloc allocate size+<something> bytes where something is enough to hold the new call stack that the compartment will need. There are some slightly tricky questions to think about like: where should the stack go (before or after the compartments main data?) and how does that affect things like resizing a compartment (should we actually pre-allocate huge virtual address ranges to a compartment in the expectation that it will grow?). At least at first I don't think we need to worry about those considerations and, hopefully, they mostly wouldn't leak out into our API either.