RTOS Memory Protection: Define User-Task Boundaries with Zephyr Memory Domains

Begin with a concrete boundary

“Enable the MPU” is not a complete isolation design. Start with a specific claim: a user-mode telemetry parser may read its input mailbox and update its own working state, but must not write the actuator controller’s state or arbitrary kernel memory. That claim identifies the assets, permitted operations and expected failure behavior. This guide uses Zephyr 4.2.0 memory domains; actual enforcement depends on the selected architecture, userspace configuration and hardware.

A historical Zephyr migration account discusses memory protection as part of a broader platform decision. It is useful context, but it does not prove a particular board’s isolation. The technical basis here is the 4.2.0 user-mode overview and the target build. Keep reliability against accidental corruption separate from claims about malicious code, denial of service, privileged drivers, DMA, or physical attacks.

Parser and controller domains share only an explicit mailbox; privileged service access additionally requires object grants and validated arguments.
Original conceptual diagram. Arrows show ownership or lifecycle dependencies, not measured timing.

Separate user execution from supervisor execution

Memory-domain membership controls additional memory available to user-mode threads. It is not intended as a general access-control mechanism for supervisor code. A supervisor component that accepts an arbitrary user pointer and writes through it incorrectly can defeat an otherwise careful design. Review system-call argument validation and all privileged services exposed to the untrusted component.

Confirm CONFIG_USERSPACE support for the chosen board and architecture, and verify that the intended thread actually runs in user mode, for example through an appropriately configured K_USER thread creation path. A build that merely declares domains while executing every task with supervisor privileges does not establish the intended boundary. The memory-protection design explains domain membership, default-domain behavior, stack access and architecture-specific limits. Do not infer stack isolation within a domain without checking the architecture’s isolated-stack capability and configuration.

Design partitions around data ownership

A memory domain contains memory partitions with a base address, size and permissions. Hardware can impose alignment, region-count and size restrictions. A partition that includes the intended mailbox plus an adjacent secret object grants more access than the source-level structure suggests. Inspect the linked placement and padding, not just the declarations in one C file.

Create a partition inventory: private parser state, immutable configuration, shared input mailbox, and controller-only state. For each partition list which domain can read or write it and why. Partitions within a domain must not overlap. Shared memory can be included in multiple domains, but sharing should be narrow and intentional. Automatic application partitions can simplify placement across files; manually defined partitions need the same final-image review. Budget MPU regions before adding a separate domain for every small object.

Memory access and kernel-object permissions are different

Granting a buffer region does not automatically authorize every kernel operation involving that buffer. Zephyr also checks user access to kernel objects. Review the kernel-object permission model and grant only the specific semaphores, queues, devices or other objects required by the component. An object permission and the memory needed for a system-call argument must both be valid for the intended operation.

For a producer-consumer boundary, decide whether the interface copies a bounded record or shares a buffer with explicit ownership. A shared buffer is not magically immutable while a privileged service validates it. If user code can modify it concurrently, validation and later use may see different contents. Copy or otherwise stabilize the data at the trust boundary according to the service’s contract. Include length, range, enum and state validation even when the pointer itself is permitted.

Keep allocation and library state inside the design

A component can leak access through a broadly shared heap or a library-global partition that was added only to make a fault disappear. When a user task needs a C library partition, identify exactly why and what other state becomes reachable. Separate logical applications should not automatically draw all resources from one unrestricted pool. Memory allocation and kernel-side resource pools have different roles and need separate review.

Likewise, check initialization order. Privileged setup should define and initialize the domain, add the required partitions, associate the thread, grant kernel-object permissions, and only then allow the user workload to run. Audit inherited membership and permissions when the application creates child threads. A later refactor that moves thread creation into a different parent can silently change which boundary the new thread receives if inheritance is overlooked.

Validate both allowed and forbidden operations

Create a proposed negative-test suite in a disposable test build. From the intended user thread, attempt a write to controller-only data, a read outside its assigned memory, and an operation on an ungranted kernel object. Pair each negative case with a nearby permitted operation so a general startup failure is not mistaken for successful isolation. Do not place destructive tests on equipment controlling a live process.

Record fault type, address, thread identity, domain assignment and the application’s defined recovery response. Verify the system does not simply restart the same faulty operation forever. Then test legitimate sharing at partition boundaries and with maximum valid lengths. Inspect the map file after changing compiler options or adding globals, because layout changes can alter the practical exposure. No protected target or fault-injection suite was run for this article.

State the residual risks and acceptance criteria

Memory protection cannot by itself guarantee CPU availability, bounded heap use, peripheral correctness or safe behavior from privileged code. DMA access may require separate hardware controls; a CPU MPU policy should not be advertised as universal bus-master isolation. A user thread can still misuse resources it legitimately owns, so quotas, scheduling policy and service validation remain necessary.

  • The user-mode execution path is verified on the exact target configuration.
  • Each partition has explicit readers, writers, alignment and linked-image evidence.
  • Kernel-object grants are reviewed separately from ordinary memory permissions.
  • Positive and negative tests distinguish real enforcement from unrelated failures.
  • Fault recovery is defined without claiming protection against every attack class.
  • Architecture limits and remaining shared resources are documented before deployment.

Similar Posts