CMRX RTOS is an operating system intended for processors that are not equipped with memory mapping unit, rather they possess memory protection unit. These kind of processors are usually used in embedded systems ranging from IoT devices to industrial operational technology. As many of these systems are getting online in recent years there is increased need to make these systems secure. CMRX RTOS is designed to be secure by default.
A firmware using CMRX RTOS consists of CMRX kernel and one or more processes. Each of these processes may have zero, one or multiple threads of execution. All processes are running in unprivileged mode, while kernel and interrupt service handlers are running privileged.
CMRX provides CMake functions for creating processes which are built along the kernel. Both kernel itself and each process are built as separate static library and are linked together to form an executable firmware, using any suitable vendor-provided HAL. As processes are memory isolated from each other they have to use RPC to interact. RPC is based on interfaces to determine function being called rather than direct addressing, which allows generalization of processes and creation of stable API between them.
Drivers are just a special case of process, they use same RPC mechanism to implement their APIs as ordinary processes. They are explicitly granted access to memory regions where controlled peripherals are mapped so they can execute their function.
CMRX kernel itself is portable and expectes minimal set of services to be provided by underlying system, known as CMRX abstract machine. Any processor, system or platform that can provide this interface is capable of running CMRX kernel.
CMRX was designed to be as practically secure as possible while still deliver real-time characteristics running on ordinary microcontroller hardware.
Memory safety by default. Kernel is the only major component running in privileged mode and is isolated from the rest of the system via hardware memory protection. Each individual firmware component is also isolated from everything else via hardware memory protection. This vastly limits effects of any possible memory errors to just one single component. Further, no writable memory is executable by default. This provides additional security by making remote code execution attacks much harder to perform even in case of vulnerable firmware.
Fault isolation. Any failing component will fail inside its memory protection boundaries without unknowingly affecting other components. CMRX is designed so that these components can be restarted independently, without affecting the rest of the system. Its up to firmware designer to decide whether they’ll design their firmware to be partially restartable or not as CMRX does not enforce any specific fault handling.
Microkernel design. CMRX kernel is a microkernel - it doesn’t implement any functionality that can be implemented in userspace. By moving drivers out of the kernel, the amount of code running in privileged mode is vastly reduced. This also reduces the possible attack surface as device drivers which are often the first component attackers talk to are subject of memory protection rules in CMRX. Kernel that doesn’t care about any subsystem allows for easy removal of unnecessary components - this can further improve stability, security and footprint by removing anything that is not used in your specific firmware.
Type safety CMRX is using facilities of C11 to ensure type safety of usermode code. This mostly applies to RPC calls, which are compile-time type checked to ensure correct arguments are passed to RPC call before the code even compiles. Type checking is applied to implementation of RPC interfaces too, where CMRX makes sure that there is no mismatch between declared and implemented interface for RPC service. Code that is found to be type-inconsistent won’t compile.
While CMRX is designed to be secure and follows microkernel paradigm, its goal to be usable outside of lab setup in real-world delivering reasonable performance. To facilitate this some decisions were made to ease use of CMRX and/or improve performance of CMRX applications:
CMRX is designed to be usable with existing HALs. CMRX doesn’t ship with its own HAL nor standard library. Kernel itself has minimal dependencies summarized in CMRX abstract machine specification. On ARM platform, vast majority of HALs are derived or compatible with ARM CMSIS specification fulfill abstract machine specification and can be used out of the box.
CMRX is also expected to be used with existing libraries and drivers with little to no modifications. Libraries which don’t allocate any internal state should be usable out of the box with no need to modify them.
Existing drivers will require some modifications as virtually all off-the-shelf drivers assume execution in privileged mode. Those which don’t will be easier to port to CMRX, where most of the work will boil down to creating operating system abstraction layer and wrapping worker-threads and driver data into CMRX process.
While this may be quite obvious, given the fact that CMRX is a microkernel, we want to push this trait even further. Features are added into CMRX on per-needed, solves real problem basis. This means that you may come across a feature that is routinely implemented by other RTOSes but seems to be missing in CMRX. This may happen for one of following reasons:
In many cases where functionality seems to be missing from kernel, it is due to the proces of userspaceification of said features.