192 lines
8.4 KiB
ReStructuredText
192 lines
8.4 KiB
ReStructuredText
.. _usermode_overview:
|
|
|
|
Overview
|
|
########
|
|
|
|
Threat Model
|
|
************
|
|
|
|
User mode threads are considered to be untrusted by Zephyr and are therefore
|
|
isolated from other user mode threads and from the kernel. A flawed or
|
|
malicious user mode thread cannot leak or modify the private data/resources
|
|
of another thread or the kernel, and cannot interfere with or
|
|
control another user mode thread or the kernel.
|
|
|
|
Example use-cases of Zephyr's user mode features:
|
|
|
|
- The kernel can protect against many unintentional programming errors which
|
|
could otherwise silently or spectacularly corrupt the system.
|
|
|
|
- The kernel can sandbox complex data parsers such as interpreters, network
|
|
protocols, and filesystems such that malicious third-party code or data
|
|
cannot compromise the kernel or other threads.
|
|
|
|
- The kernel can support the notion of multiple logical "applications", each
|
|
with their own group of threads and private data structures, which are
|
|
isolated from each other if one crashes or is otherwise compromised.
|
|
|
|
Design Goals
|
|
============
|
|
|
|
For threads running in a non-privileged CPU state (hereafter referred to as
|
|
'user mode') we aim to protect against the following:
|
|
|
|
- We prevent access to memory not specifically granted, or incorrect access to
|
|
memory that has an incompatible policy, such as attempting to write to a
|
|
read-only area.
|
|
|
|
- Access to thread stack buffers will be controlled with a policy which
|
|
partially depends on the underlying memory protection hardware.
|
|
|
|
- A user thread will by default have read/write access to its own stack
|
|
buffer.
|
|
|
|
- A user thread will never by default have access to user thread stacks
|
|
that are not members of the same memory domain.
|
|
|
|
- A user thread will never by default have access to thread stacks owned
|
|
by a supervisor thread, or thread stacks used to handle system call
|
|
privilege elevations, interrupts, or CPU exceptions.
|
|
|
|
- A user thread may have read/write access to the stacks of other user
|
|
threads in the same memory domain, depending on hardware.
|
|
|
|
- On MPU systems, threads may only access their own stack buffer.
|
|
|
|
- On MMU systems, threads may access any user thread stack in the same
|
|
memory domain. Portable code should not assume this.
|
|
|
|
- By default, program text and read-only data are accessible to all threads
|
|
on read-only basis, kernel-wide. This policy may be adjusted.
|
|
|
|
- User threads by default are not granted default access to any memory
|
|
except what is noted above.
|
|
|
|
- We prevent use of device drivers or kernel objects not specifically granted,
|
|
with the permission granularity on a per object or per driver instance
|
|
basis.
|
|
|
|
- We validate kernel or driver API calls with incorrect parameters that would
|
|
otherwise cause a crash or corruption of data structures private to the
|
|
kernel. This includes:
|
|
|
|
- Using the wrong kernel object type.
|
|
|
|
- Using parameters outside of proper bounds or with nonsensical values.
|
|
|
|
- Passing memory buffers that the calling thread does not have sufficient
|
|
access to read or write, depending on the semantics of the API.
|
|
|
|
- Use of kernel objects that are not in a proper initialization state.
|
|
|
|
- We ensure the detection and safe handling of user mode stack overflows.
|
|
|
|
- We prevent invoking system calls to functions excluded by the kernel
|
|
configuration.
|
|
|
|
- We prevent disabling of or tampering with kernel-defined and
|
|
hardware-enforced memory protections.
|
|
|
|
- We prevent re-entry from user to supervisor mode except through the
|
|
kernel-defined system calls and interrupt handlers.
|
|
|
|
- We prevent the introduction of new executable code by user mode threads,
|
|
except to the extent to which this is supported by kernel system calls.
|
|
|
|
We are specifically not protecting against the following attacks:
|
|
|
|
- The kernel itself, and any threads that are executing in supervisor mode,
|
|
are assumed to be trusted.
|
|
|
|
- The toolchain and any supplemental programs used by the build system are
|
|
assumed to be trusted.
|
|
|
|
- The kernel build is assumed to be trusted. There is considerable build-time
|
|
logic for creating the tables of valid kernel objects, defining system calls,
|
|
and configuring interrupts. The .elf binary files that are worked with
|
|
during this process are all assumed to be trusted code.
|
|
|
|
- We can't protect against mistakes made in memory domain configuration done in
|
|
kernel mode that exposes private kernel data structures to a user thread. RAM
|
|
for kernel objects should always be configured as supervisor-only.
|
|
|
|
- It is possible to make top-level declarations of user mode threads and
|
|
assign them permissions to kernel objects. In general, all C and header
|
|
files that are part of the kernel build producing zephyr.elf are assumed to
|
|
be trusted.
|
|
|
|
- We do not protect against denial of service attacks through thread CPU
|
|
starvation. Zephyr has no thread priority aging and a user thread of a
|
|
particular priority can starve all threads of lower priority, and also other
|
|
threads of the same priority if time-slicing is not enabled.
|
|
|
|
- There are build-time defined limits on how many threads can be active
|
|
simultaneously, after which creation of new user threads will fail.
|
|
|
|
- Stack overflows for threads running in supervisor mode may be caught,
|
|
but the integrity of the system cannot be guaranteed.
|
|
|
|
High-level Policy Details
|
|
*************************
|
|
|
|
Broadly speaking, we accomplish these thread-level memory protection goals
|
|
through the following mechanisms:
|
|
|
|
- Any user thread will only have access to a subset of memory:
|
|
typically its stack, program text, read-only data, and any partitions
|
|
configured in the :ref:`memory_domain` it belongs to. Access to any other RAM
|
|
must be done on the thread's behalf through system calls, or specifically
|
|
granted by a supervisor thread using the memory domain APIs. Newly created
|
|
threads inherit the memory domain configuration of the parent. Threads may
|
|
communicate with each other by having shared membership of the same memory
|
|
domains, or via kernel objects such as semaphores and pipes.
|
|
|
|
- User threads cannot directly access memory belonging to kernel objects.
|
|
Although pointers to kernel objects are used to reference them, actual
|
|
manipulation of kernel objects is done through system call interfaces. Device
|
|
drivers and threads stacks are also considered kernel objects. This ensures
|
|
that any data inside a kernel object that is private to the kernel cannot be
|
|
tampered with.
|
|
|
|
- User threads by default have no permission to access any kernel object or
|
|
driver other than their own thread object. Such access must be granted by
|
|
another thread that is either in supervisor mode or has permission on both
|
|
the receiving thread object and the kernel object being granted access to.
|
|
The creation of new threads has an option to automatically inherit
|
|
permissions of all kernel objects granted to the parent, except the parent
|
|
thread itself.
|
|
|
|
- For performance and footprint reasons Zephyr normally does little or no
|
|
parameter error checking for kernel object or device driver APIs. Access from
|
|
user mode through system calls involves an extra layer of handler functions,
|
|
which are expected to rigorously validate access permissions and type of
|
|
the object, check the validity of other parameters through bounds checking or
|
|
other means, and verify proper read/write access to any memory buffers
|
|
involved.
|
|
|
|
- Thread stacks are defined in such a way that exceeding the specified stack
|
|
space will generate a hardware fault. The way this is done specifically
|
|
varies per architecture.
|
|
|
|
Constraints
|
|
***********
|
|
|
|
All kernel objects, thread stacks, and device driver instances must be defined
|
|
at build time if they are to be used from user mode. Dynamic use-cases for
|
|
kernel objects will need to go through pre-defined pools of available objects.
|
|
|
|
There are some constraints if additional application binary data is loaded
|
|
for execution after the kernel starts:
|
|
|
|
- Loaded object code will not be able to define any kernel objects that will be
|
|
recognized by the kernel. This code will instead need to use APIs for
|
|
requesting kernel objects from pools.
|
|
|
|
- Similarly, since the loaded object code will not be part of the kernel build
|
|
process, this code will not be able to install interrupt handlers,
|
|
instantiate device drivers, or define system calls, regardless of what
|
|
mode it runs in.
|
|
|
|
- Loaded object code that does not come from a verified source should always
|
|
be entered with the CPU already in user mode.
|