CHERIoT-Platform/cheriot-rtos

Add a compartment initalisation framework

Opened this issue · 1 comments

Sometimes there is code in a compartment that you want to run once at startup - for example in the configuration broker example each parser needs to register a callback with the broker (to avoid hard coding the list of parers into to the broker).

Running a thread in each compartment to do this is wasteful and since threads aren't expected to exit means they are left hanging around. Doing it from a single thread means exposing a compartment-call that's not otherwise need.

Following the pattern of compartment_error_handler() with something like compartment_init() would work ?

I agree that something in this space would be nice. There are a lot of problems that are similar to the ones with shared-library global initialisers.

To start with, this is what we have now:

  • C++ static initialisers work, so you can lazily initialise variables the first time they enter scope.
  • We build on this to run global constructors, so it's easy to have code that runs all constructors the first time that a compartment entry point is invoked.
  • Compartments that are thread entry points can run construction code early.
  • We can build barriers with futexes so that threads can block until some initialisation is run.

If we have a generic mechanism, a few problems arise immediately:

  • What order do the constructors run in? If I have two compartments, A and B, how do I express the idea that B's constructors depend on A's having run and so must happen first? What should happen in more complex cases where there's circular dependency (ideally, we'd catch that at link time somehow).
  • What thread do constructors run on and how do we ensure that it is large enough (both stack and trusted stack) for all constructor logic?
  • What happens if one of the constructors doesn't terminate? Does this block the boot? Do we also require another thread as a watchdog that can handle partial startup?
  • What happens if one compartment's initialisation faults and leaves it in an undefined state? Related to the first one, what happens if B has constructors that depend on A being initialised, but A faults during construction.

I don't think we can solve this in the general case (happy to be wrong!), but I would welcome suggestions for how we can at least provide some general infrastructure for the common cases.

Currently, the simplest pattern is:

  • Designate one thread to run constructors and have it run them on startup.
  • Block all other threads behind a barrier.
  • Add a policy that says that the initialisation entry points can be called only from the startup compartment.

The thread that runs the cleanup needs one trusted stack frame to handle the startup bits before it enters its main entry point. It can then define the initialisation order and what to do if any fail. If you want to handle infinite looping, a second thread can do a timed wait on the barrier and handle errors if the timeout is triggered. This requires a lot of bespoke code, so is not ideal.