Skip to content

DDC switching API #45

@ltratt

Description

@ltratt

#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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions