This blog is written by Roy Jamil, Embedded Systems Training Engineer at Ac6.
Prevent unauthorized use of kernel objects
Introduction
In the first two parts of this series, we explored the fundamentals of user mode in Zephyr, focusing on the benefits of memory domains and partitions, and demonstrating how to reorganize logical applications into separate modules for improved scalability and security.
In this third part, we will delve into how user mode manages permissions to access kernel objects such as mutexes, semaphores, and device drivers. Understanding these permissions is necessary for developing secure and reliable applications in Zephyr, as it ensures that user threads can only interact with kernel objects in controlled and intended ways. We will provide practical examples to illustrate the typical usage patterns and best practices for managing access to these critical resources.
Kernel objects in a nutshell
Kernel objects in Zephyr are fundamental components that the kernel manages to facilitate various system operations. These objects include entities like mutexes, semaphores, and device drivers, among others. They provide essential services such as synchronization, inter-thread communication, and hardware interaction.
In user mode, threads must have explicit permissions to access these kernel objects. This is a crucial aspect of Zephyr’s security model, ensuring that user threads can only interact with kernel objects in ways that have been explicitly allowed. Permissions are granted on a per-object basis, meaning that each thread can be given specific rights to interact with some objects while being restricted from others.
For example, a thread might need permissions to lock and unlock a mutex, post and pend on a semaphore, or interact with a device driver. These permissions are strictly controlled to prevent unauthorized access, thereby maintaining system stability and security.
Supervisor threads, on the other hand, have unrestricted access to any kernel object. This allows them to perform a wide range of operations that user threads cannot.
The concept
In Zephyr, system calls are special functions that let user applications safely interact with the operating system’s core features. Each time an application makes a system call, Zephyr checks all the information provided to ensure that it is correct and safe. This protects the system from harmful data or errors that could cause problems.
Furthermore, Zephyr assesses whether a system call originates from a user thread or a supervisor thread. If a user thread makes a system call, Zephyr verifies whether it has the explicit permission to perform the requested action. If permission is granted, the operation proceeds; if not, the system call returns an error. This stringent verification process helps prevent unauthorized actions, maintaining a protective barrier between user applications and the kernel, and keeping the system stable and secure.
Note: Granting permissions to kernel objects operates independently from logical applications or memory domains. While both features control user thread access, they can be used separately to tailor security and organization within Zephyr.
Practical example
This code was tested on NXP FRDM-MCXN947. Check this github repository if you want to clone the project.
Output:
This example code sets up three threads to interact with a shared semaphore. The first user thread has permission to access the semaphore and does so successfully. The second user thread lacks permission and its attempt to access the semaphore leads to a system error, specifically reported as “OOPS” because of its unauthorized access. However, this error doesn’t stop other threads from running: the first user thread accesses the semaphore as allowed, and the kernel thread operates normally, showcasing that the system effectively isolates faults and continues operation despite errors in one thread.
Conclusion
In this third installment of our exploration into Zephyr’s user mode, we’ve detailed how Zephyr manages permissions for kernel objects such as mutexes, semaphores, and device drivers. These mechanisms are important for maintaining the integrity and security of applications running on Zephyr by ensuring that access to critical system resources is tightly controlled and explicitly granted and operations are conducted strictly within their allowed boundaries.
Through practical examples, we’ve seen how Zephyr enforces these security measures. User threads are required to have specific permissions to access kernel objects, which prevents unauthorized interactions that could potentially corrupt the system. The permission checks embedded in system calls are a part of this security approach, filtering out inappropriate requests and handling errors gracefully without compromising the overall system functionality.
If you want to learn more about Zephyr, check out our comprehensive Zephyr training course, covering everything from fundamentals to advanced topics. Click here for more information.