Introduction

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.

Overview of features

  • targeting primarily 32-bit microcontrollers with both RAM and Flash in ranges from kiB to MiB
  • portable microkernel with real-time properties
  • kernel isolated from userspace processes via hardware memory protection
  • userspace processes isolated from each other via hardware memory protection
  • multi-threaded prioritized scheduler
  • drivers running outside kernel in userspace processes and thus also isolated from the rest of the system via hardware memory protection
  • memory protection unit managed fully automatically
  • IPC model based on remote procedure calling allowing processes talk to each other
  • zero-allocation kernel - no component allocates any memory dynamically, all pools and memory requirements are configured / determined on compile time
  • threading, timing and synchronization facilities provided by the kernel

Architecture

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.

Design goals

Maximum robustness

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.

Pragmatism

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:

  • Interrupt handling is fully in hands of firmware developer. CMRX doesn’t provide any automatic interrupt routing to tasks or ability to register usermode IRQ handlers. This breaks out of concept of fully memory isolated software to allow low-latency interrupt handling.
  • We assume trustworthy server, malicious client security model. CMRX RPC mechanism exposes selected regions of caller’s memory to callee during RPC. This model backs the ability to share memory between processes with minimal overhead and without hitting hardware-imposed limits.
  • All code is reachable from everywhere. This is a design decision that simplifies use of vendor HALs which are often distributed as a library of functions. While some authors see this as potential security risk, we assume that data is what determines security boundaries. Code ultimately needs to modify data to make permanent effects and data is what we ultimately protect.

Compatibility with existing ecosystems

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.

Minimalism

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:

  • We did not need it yet - CMRX is used in production systems and features needed to develop these systems were added to CMRX kernel. If you see a feature in CMRX, it is already being used somewhere in the world.
  • There’s better way to do it - Architecture implemented by CMRX makes certain approaches to problems infeasible or poorly performing. That’s why we decided not to implement them the way other systems do, or ignore it completely. Examples of features commonly present in other RTOS kernels but missing from CMRX could be queues and mutexes. Both are implemented almost completely as userspace libraries.

In many cases where functionality seems to be missing from kernel, it is due to the proces of userspaceification of said features.