docs: initial user mode documentation
This initial batch of documentation describes policies and mechanism related to kernel objects and system calls. Some details on porting user mode to a new arch have been provided in the architecture porting guide. Thread documentation updated with some user mode consideration. This is not the final documentation, more to come in subsequent patches. Signed-off-by: Andrew Boie <andrew.p.boie@intel.com>
This commit is contained in:
parent
517caef5a4
commit
2d2a97b3f6
|
@ -17,4 +17,5 @@ the code in the Zephyr source tree.
|
|||
memory/memory.rst
|
||||
synchronization/synchronization.rst
|
||||
data_passing/data_passing.rst
|
||||
usermode/usermode.rst
|
||||
other/other.rst
|
||||
|
|
|
@ -39,6 +39,12 @@ A thread has the following key properties:
|
|||
* A **start delay**, which specifies how long the kernel should wait before
|
||||
starting the thread.
|
||||
|
||||
* An **execution mode**, which can either be supervisor or user mode.
|
||||
By default, threads run in supervisor mode and allow access to
|
||||
privileged CPU instructions, the entire memory address space, and
|
||||
peripherals. User mode threads have a reduced set of privileges.
|
||||
This depends on the :option:`CONFIG_USERSPACE` option. See :ref:`usermode`.
|
||||
|
||||
.. _spawning_thread:
|
||||
|
||||
Thread Creation
|
||||
|
@ -142,6 +148,16 @@ The following thread options are supported.
|
|||
By default, the kernel does not attempt to save and restore the contents
|
||||
of these registers when scheduling the thread.
|
||||
|
||||
:c:macro:`K_USER`
|
||||
If :option:`CONFIG_USERSPACE` is enabled, this thread will be created in
|
||||
user mode and will have reduced privileges. See :ref:`usermode`. Otherwise
|
||||
this flag does nothing.
|
||||
|
||||
:c:macro:`K_INHERIT_PERMS`
|
||||
If :option:`CONFIG_USERSPACE` is enabled, this thread will inherit all
|
||||
kernel object permissions that the parent thread had, except the parent
|
||||
thread object. See :ref:`usermode`.
|
||||
|
||||
Implementation
|
||||
**************
|
||||
|
||||
|
@ -191,6 +207,41 @@ The following code has the same effect as the code segment above.
|
|||
my_entry_point, NULL, NULL, NULL,
|
||||
MY_PRIORITY, 0, K_NO_WAIT);
|
||||
|
||||
User Mode Constraints
|
||||
---------------------
|
||||
|
||||
This section only applies if :option:`CONFIG_USERSPACE` is enabled, and a user
|
||||
thread tries to create a new thread. The :c:func:`k_thread_create()` API is
|
||||
still used, but there are additional constraints which must be met or the
|
||||
calling thread will be terminated:
|
||||
|
||||
* The calling thread must have permissions granted on both the child thread
|
||||
and stack parameters; both are tracked by the kernel as kernel objects.
|
||||
|
||||
* The child thread and stack objects must be in an uninitialized state,
|
||||
i.e. it is not currently running and the stack memory is unused.
|
||||
|
||||
* The stack size parameter passed in must be equal to or less than the
|
||||
bounds of the stack object when it was declared.
|
||||
|
||||
* The :c:macro:`K_USER` option must be used, as user threads can only create
|
||||
other user threads.
|
||||
|
||||
* The :c:macro:`K_ESSENTIAL` option must not be used, user threads may not be
|
||||
considered essential threads.
|
||||
|
||||
* The priority of the child thread must be a valid priority value, and equal to
|
||||
or lower than the parent thread.
|
||||
|
||||
Dropping Permissions
|
||||
====================
|
||||
|
||||
If :option:`CONFIG_USERSPACE` is enabled, a thread running in supervisor mode
|
||||
may perform a one-way transition to user mode using the
|
||||
:cpp:func:`k_thread_user_mode_enter()` API. This is a one-way operation which
|
||||
will reset and zero the thread's stack memory. The thread will be marked
|
||||
as non-essential.
|
||||
|
||||
Terminating a Thread
|
||||
====================
|
||||
|
||||
|
@ -213,6 +264,8 @@ The following code illustrates the ways a thread can terminate.
|
|||
/* thread terminates at end of entry point function */
|
||||
}
|
||||
|
||||
If CONFIG_USERSPACE is enabled, aborting a thread will additionally mark the
|
||||
thread and stack objects as uninitialized so that they may be re-used.
|
||||
|
||||
Suggested Uses
|
||||
**************
|
||||
|
@ -227,7 +280,7 @@ Configuration Options
|
|||
|
||||
Related configuration options:
|
||||
|
||||
* None.
|
||||
* :option:`CONFIG_USERSPACE`
|
||||
|
||||
APIs
|
||||
****
|
||||
|
|
226
doc/kernel/usermode/kernelobjects.rst
Normal file
226
doc/kernel/usermode/kernelobjects.rst
Normal file
|
@ -0,0 +1,226 @@
|
|||
.. _kernelobjects:
|
||||
|
||||
Kernel Objects
|
||||
##############
|
||||
|
||||
A kernel object can be one of three classes of data:
|
||||
|
||||
* A core kernel object, such as a semaphore, thread, pipe, etc.
|
||||
* A thread stack, which is an array of :c:type:`struct _k_thread_stack_element`
|
||||
and declared with :c:macro:`K_THREAD_STACK_DEFINE()`
|
||||
* A device driver instance (struct device) that belongs to one of a defined
|
||||
set of subsystems
|
||||
|
||||
The set of known kernel objects and driver subsystems is defined in
|
||||
include/kernel.h as :cpp:enum:`k_objects`.
|
||||
|
||||
Kernel objects are completely opaque to user threads. User threads work
|
||||
with addresses to kernel objects when making API calls, but may never
|
||||
dereference these addresses, doing so will cause a memory protection fault.
|
||||
All kernel objects must be placed in memory that is not accessible by
|
||||
user threads.
|
||||
|
||||
Since user threads may not directly manipulate kernel objects, all use of
|
||||
them must go through system calls. In order to perform a system call on
|
||||
a kernel object, checks are performed by system call handler functions
|
||||
that the kernel object address is valid and that the calling thread
|
||||
has sufficient permissions to work with it.
|
||||
|
||||
Object Placement
|
||||
================
|
||||
|
||||
Kernel objects that are only used by supervisor threads have no restrictions.
|
||||
In order for a kernel object to be usable by a user thread, several conditions
|
||||
must be met on how it is declared.
|
||||
|
||||
First, that object must be declared as a top-level global at build time, such
|
||||
that it appears in the ELF symbol table. It is permitted to declare kernel
|
||||
objects with static scope. The post-build script ``gen_kobject_list.py`` scans
|
||||
the generated ELF file to find kernel objects and places their memory addresses
|
||||
in a special table of kernel object metadata. Kernel objects may be members of
|
||||
arrays or embedded within other data structures.
|
||||
|
||||
Kernel objects must be located in memory reserved for the kernel. If
|
||||
:option:`CONFIG_APPLICATION_MEMORY` is used, all declarations of kernel objects
|
||||
inside application code must be prefixed with the :c:macro:`__kernel` attribute
|
||||
so that they are placed in the right memory sections. The APIs for statically
|
||||
declaring and initializing kernel objects (such as :c:macro:`K_SEM_DEFINE()`)
|
||||
automatically do this. However, uninitialized kernel objects need to be tagged
|
||||
like this:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
__kernel struct k_sem my_sem;
|
||||
|
||||
Any memory reserved for a kernel object must be used exclusively for that
|
||||
object. Kernel objects may not be members of a union data type.
|
||||
|
||||
The debug output of the ``gen_kobject_list.py`` script may be useful when
|
||||
debugging why some object was unexpectedly not being tracked. This
|
||||
information will be printed if the script is run with the ``--verbose`` flag,
|
||||
or if the build system is invoked with verbose output.
|
||||
|
||||
Implementation Details
|
||||
----------------------
|
||||
|
||||
The ``gen_kobject_list.py`` script is a post-build step which finds all the
|
||||
valid kernel object instances in the binary. It accomplishes this by parsing
|
||||
the DWARF debug information present in the generated ELF file for the kernel.
|
||||
|
||||
Any instances of structs or arrays corresponding to kernel objects that meet
|
||||
the object placement criteria will have their memory addresses placed in a
|
||||
special perfect hash table of kernel objects generated by the 'gperf' tool.
|
||||
When a system call is made and the kernel is presented with a memory address
|
||||
of what may or may not be a valid kernel object, the address can be validated
|
||||
with a constant-time lookup in this table.
|
||||
|
||||
Drivers are a special case. All drivers are instances of :c:type:`struct
|
||||
device`, but it is important to know what subsystem a driver belongs to so that
|
||||
incorrect operations, such as calling a UART API on a sensor driver object, can
|
||||
be prevented. When a device struct is found, its API pointer is examined to
|
||||
determine what subsystem the driver belongs to.
|
||||
|
||||
The table itself maps kernel object memory addresses to instances of
|
||||
:c:type:`struct _k_object`, which has all the metadata for that object. This
|
||||
includes:
|
||||
|
||||
* A bitfield indicating permissions on that object. All threads have a
|
||||
numerical ID assigned to them at build time, used to index the permission
|
||||
bitfield for an object to see if that thread has permission on it.
|
||||
* A type field indicating what kind of object this is, which is some
|
||||
instance of :cpp:enum:`k_objects`.
|
||||
* A set of flags for that object. This is currently used to track
|
||||
initialization state and whether an object is public or not.
|
||||
* An extra data field. This is currently used for thread stack objects
|
||||
to denote how large the stack is, and for thread objects to indicate
|
||||
the thread's index in kernel object permission bitfields.
|
||||
|
||||
Supervisor Thread Access Permission
|
||||
===================================
|
||||
|
||||
Supervisor threads can access any kernel object. However, permissions for
|
||||
supervisor threads are still tracked for two reasons:
|
||||
|
||||
* If a supervisor thread calls :cpp:func:`k_thread_user_mode_enter()`, the
|
||||
thread will then run in user mode with any permissions it had been granted
|
||||
(in many cases, by itself) when it was a supervisor thread.
|
||||
|
||||
* If a supervisor thread creates a user thread with the
|
||||
:c:macro:`K_INHERIT_PERMS` option, the child thread will be granted the
|
||||
same permissions as the parent thread, except the parent thread object.
|
||||
|
||||
User Thread Access Permission
|
||||
=============================
|
||||
|
||||
By default, when a user thread is created, it will only have access permissions
|
||||
on its own thread object. Other kernel objects by default are not usable.
|
||||
Access to them needs to be explicitly or implicitly granted. There are several
|
||||
ways to do this.
|
||||
|
||||
* If a thread is created with the :c:macro:`K_INHERIT_PERMS`, that thread
|
||||
will inherit all the permissions of the parent thread, except the parent
|
||||
thread object.
|
||||
|
||||
* A thread that has permission on an object, or is running in supervisor mode,
|
||||
may grant permission on that object to another thread via the
|
||||
:c:func:`k_object_access_grant()` API. The convenience function
|
||||
:c:func:`k_thread_access_grant()` may also be used, which accepts a
|
||||
NULL-terminated list of kernel objects and calls
|
||||
:c:func:`k_object_access_grant()` on each of them. The thread being granted
|
||||
permission, or the object whose access is being granted, do not need to be in
|
||||
an initialized state. If the caller is from user mode, the caller must have
|
||||
permissions on both the kernel object and the target thread object.
|
||||
|
||||
* Supervisor threads may declare a particular kernel object to be a public
|
||||
object, usable by all current and future threads with the
|
||||
:c:func:`k_object_access_all_grant()` API. You must assume that any
|
||||
untrusted or exploited code will then be able to access the object. Use
|
||||
this API with caution!
|
||||
|
||||
* If a thread was declared statically with :c:macro:`K_THREAD_DEFINE()`,
|
||||
then the :c:macro:`K_THREAD_ACCESS_GRANT()` may be used to grant that thread
|
||||
access to a set of kernel objects at boot time.
|
||||
|
||||
Once a thread has been granted access to an object, such access may be
|
||||
removed with the :c:func:`k_object_access_revoke()` API. User threads using
|
||||
this API must have permission on both the object in question, and the thread
|
||||
object that is having access revoked.
|
||||
|
||||
Initialization State
|
||||
====================
|
||||
|
||||
Most operations on kernel objects will fail if the object is considered to be
|
||||
in an uninitialized state. The appropriate init function for the object must
|
||||
be performed first.
|
||||
|
||||
Some objects will be implicitly initialized at boot:
|
||||
|
||||
* Kernel objects that were declared with static initialization macros
|
||||
(such as :c:macro:`K_SEM_DEFINE` for semaphores) will be in an initialized
|
||||
state at build time.
|
||||
|
||||
* Device driver objects are considered initialized after their init function
|
||||
is run by the kernel early in the boot process.
|
||||
|
||||
If a kernel object is initialized with a private static initializer, the
|
||||
object must have :c:func:`_k_object_init()` on it at some point by a supervisor
|
||||
thread, otherwise the kernel will consider the object uninitialized if accessed
|
||||
by a user thread. This is very uncommon, typically only for kernel objects that
|
||||
are embedded within some larger struct and initialized statically.
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
struct foo {
|
||||
struct k_sem sem;
|
||||
...
|
||||
};
|
||||
|
||||
__kernel struct foo my_foo = {
|
||||
.sem = _K_SEM_INITIALIZER(my_foo.sem, 0, 1),
|
||||
...
|
||||
};
|
||||
|
||||
...
|
||||
_k_object_init(&my_foo.sem);
|
||||
...
|
||||
|
||||
|
||||
Creating New Kernel Object Types
|
||||
================================
|
||||
|
||||
When implementing new kernel features or driver subsystems, it may be necessary
|
||||
to define some new kernel object types. There are different steps needed
|
||||
for creating core kernel objects and new driver subsystems.
|
||||
|
||||
Creating New Core Kernel Objects
|
||||
--------------------------------
|
||||
|
||||
* In ``scripts/gen_kobject_list.py``, add the name of the struct to the
|
||||
:py:data:`kobjects` list.
|
||||
* The name of the enumerated type is derived from the name of the struct.
|
||||
Take the name of the struct, remove the first two characters, convert to
|
||||
uppercase, and prepend ``K_OBJ_`` to it. Add the enum to
|
||||
:cpp:enum:`k_objects` in include/kernel.h. For example, ``struct k_foo``
|
||||
should be enumerated as ``K_OBJ_FOO``.
|
||||
* Add a string representation for the enum to the
|
||||
:c:func:`otype_to_str()` function in kernel/userspace.c
|
||||
|
||||
Instances of the new struct should now be tracked.
|
||||
|
||||
Creating New Driver Subsystem Kernel Objects
|
||||
--------------------------------------------
|
||||
|
||||
All driver instances are :c:type:`struct device`. They are differentiated by
|
||||
what API struct they are set to.
|
||||
|
||||
* In ``scripts/gen_kobject_list.py``, add the name of the API struct for the
|
||||
new subsystem to the :py:data:`subsystems` list.
|
||||
* Take the name of the API struct, remove the trailing "_driver_api" from its
|
||||
name, convert to uppercase, and prepend ``K_OBJ_DRIVER_`` to it. This is
|
||||
the name of the enumerated type, which should be added to
|
||||
:cpp:enum:`k_objects` in include/kernel.h. For example, ``foo_driver_api``
|
||||
should be enumerated as ``K_OBJ_DRIVER_FOO``.
|
||||
* Add a string representation for the enum to the
|
||||
:c:func:`otype_to_str()` function in ``kernel/userspace.c``
|
||||
|
||||
Driver instances of the new subsystem should now be tracked.
|
253
doc/kernel/usermode/syscalls.rst
Normal file
253
doc/kernel/usermode/syscalls.rst
Normal file
|
@ -0,0 +1,253 @@
|
|||
.. _syscalls:
|
||||
|
||||
System Calls
|
||||
############
|
||||
User threads run with a reduced set of privileges than supervisor threads:
|
||||
certain CPU instructions may not be used, and they have access to only a
|
||||
limited part of the memory map. System calls (may) allow user threads to
|
||||
perform operations not directly available to them.
|
||||
|
||||
This section describes how to declare new system calls and discusses a few
|
||||
implementation details relevant to them.
|
||||
|
||||
Components
|
||||
==========
|
||||
|
||||
All system calls have the following components:
|
||||
|
||||
* A **C prototype** for the API, declared in some header under ``include/`` and
|
||||
prefixed with :c:macro:`__syscall`. This prototype is never implemented
|
||||
manually, instead it gets created by the ``scripts/gen_syscalls.py`` script.
|
||||
What gets generated is an inline function which either calls the
|
||||
implementation function directly (if called from supervisor mode) or goes
|
||||
through privilege elevation and validation steps (if called from user
|
||||
mode).
|
||||
|
||||
* An **implementation function**, which is the real implementation of the
|
||||
system call. The implementation function may assume that all parameters
|
||||
passed in have been validated if it was invoked from user mode.
|
||||
|
||||
* A **handler function**, which wraps the implementation function and does
|
||||
validation of all the arguments passed in.
|
||||
|
||||
C Prototype
|
||||
===========
|
||||
|
||||
The C prototype represents how the API is invoked from either user or
|
||||
supervisor mode. For example, to initialize a semaphore:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
__syscall void k_sem_init(struct k_sem *sem, unsigned int initial_count,
|
||||
unsigned int limit);
|
||||
|
||||
The :c:macro:`__syscall` attribute is very special. To the C compiler, it
|
||||
simply expands to 'static inline'. However to the post-build
|
||||
``gen_syscalls.py`` script, it indicates that this API is a system call and
|
||||
generates the body of the function. The ``gen_syscalls.py`` script does some
|
||||
parsing of the function prototype, to determine the data types of its return
|
||||
value and arguments, and has some limitations:
|
||||
|
||||
* Array arguments must be passed in as pointers, not arrays. For example,
|
||||
``int foo[]`` or ``int foo[12]`` is not allowed, but should instead be
|
||||
expressed as ``int *foo``.
|
||||
|
||||
* Function pointers horribly confuse the limited parser. The workaround is
|
||||
to typedef them first, and then express in the argument list in terms
|
||||
of that typedef.
|
||||
|
||||
* :c:macro:`__syscall` must be the first thing in the prototype.
|
||||
|
||||
Any header file that declares system calls must include a special generated
|
||||
header at the very bottom of the header file. This header follows the
|
||||
naming convention ``syscalls/<name of header file>``. For example, at the
|
||||
bottom of ``include/sensor.h``:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
#include <syscalls/sensor.h>
|
||||
|
||||
Implementation Details
|
||||
----------------------
|
||||
|
||||
Declaring an API with :c:macro:`__syscall` causes some code to be generated in
|
||||
C and header files, all of which can be found in the project out directory
|
||||
under ``include/generated/``:
|
||||
|
||||
* The system call is added to the enumerated type of system call IDs,
|
||||
which is expressed in ``include/generated/syscall_list.h``. It is the name
|
||||
of the API in uppercase, prefixed with ``K_SYSCALL_``.
|
||||
|
||||
* A prototype for the handler function is also created in
|
||||
``include/generated/syscall_list.h``
|
||||
|
||||
* An entry for the system call is created in the dispatch table
|
||||
``_k_sycall_table``, expressed in ``include/generated/syscall_dispatch.c``
|
||||
|
||||
* A weak handler function is declared, which is just an alias of the
|
||||
'unimplemented system call' handler. This is necessary since the real
|
||||
handler function may or may not be built depending on the kernel
|
||||
configuration. For example, if a user thread makes a sensor subsystem
|
||||
API call, but the sensor subsystem is not enabled, the weak handler
|
||||
will be invoked instead.
|
||||
|
||||
The body of the API is created in the generated system header. Using the
|
||||
example of :c:func:`k_sem_init()`, this API is declared in
|
||||
``include/kernel.h``. At the bottom of ``include/kernel.h`` is::
|
||||
|
||||
#include <syscalls/kernel.h>
|
||||
|
||||
Inside this header is the body of :c:func:`k_sem_init()`::
|
||||
|
||||
K_SYSCALL_DECLARE3_VOID(K_SYSCALL_K_SEM_INIT, k_sem_init, struct k_sem *,
|
||||
sem, unsigned int, initial_count,
|
||||
unsigned int, limit);
|
||||
|
||||
This generates an inline function that takes three arguments with void
|
||||
return value. Depending on context it will either directly call the
|
||||
implementation function or go through a system call elevation. A
|
||||
prototype for the implementation function is also automatically generated.
|
||||
|
||||
The header containing :c:macro:`K_SYSCALL_DECLARE3_VOID()` is itself
|
||||
generated due to its repetitive nature and can be found in
|
||||
``include/generated/syscall_macros.h``.
|
||||
|
||||
Implementation Function
|
||||
=======================
|
||||
|
||||
The implementation function is what actually does the work for the API.
|
||||
Zephyr normally does little to no error checking of arguments, or does this
|
||||
kind of checking with assertions. When writing the implementation function,
|
||||
validation of any parameters is optional and should be done with assertions.
|
||||
|
||||
All implementation functions must follow the naming convention, which is the
|
||||
name of the API prefixed with ``_impl_``. Implementation functions may be
|
||||
declared in the same header as the API as a static inline function or
|
||||
declared in some C file. There is no prototype needed for implementation
|
||||
functions, these are automatically generated.
|
||||
|
||||
Handler Function
|
||||
================
|
||||
|
||||
The handler function runs on the kernel side when a user thread makes
|
||||
a system call. When the user thread makes a software interrupt to elevate to
|
||||
supervisor mode, the common system call entry point uses the system call ID
|
||||
provided by the user to look up the appropriate handler function for that
|
||||
system call and jump into it.
|
||||
|
||||
Handler functions only run when system call APIs are invoked from user mode.
|
||||
If an API is invoked from supervisor mode, the implementation is simply called.
|
||||
|
||||
The purpose of the handler function is to validate all the arguments passed in.
|
||||
This includes:
|
||||
|
||||
* Any kernel object pointers provided. For example, the semaphore APIs must
|
||||
ensure that the semaphore object passed in is a valid semaphore and that
|
||||
the calling thread has permission on it.
|
||||
|
||||
* Any memory buffers passed in from user mode. Checks must be made that the
|
||||
calling thread has read or write permissions on the provided buffer.
|
||||
|
||||
* Any other arguments that have a limited range of valid values.
|
||||
|
||||
Handler functions involve a great deal of boilerplate code which has been
|
||||
made simpler by some macros in ``kernel/include/syscall_handlers.h``.
|
||||
Handler functions should be declared using these macros.
|
||||
|
||||
Argument Validation
|
||||
-------------------
|
||||
|
||||
Several macros exist to validate arguments:
|
||||
|
||||
* :c:macro:`_SYSCALL_OBJ()` Checks a memory address to assert that it is
|
||||
a valid kernel object of the expected type, that the calling thread
|
||||
has permissions on it, and that the object is initialized.
|
||||
|
||||
* :c:macro:`_SYSCALL_OBJ_INIT()` is the same as
|
||||
:c:macro:`_SYSCALL_OBJ()`, except that the provided object may be
|
||||
uninitialized. This is useful for handlers of object init functions.
|
||||
|
||||
* :c:macro:`_SYSCALL_OBJ_NEVER_INIT()` is the same as
|
||||
:c:macro:`_SYSCALL_OBJ()`, except that the provided object must be
|
||||
uninitialized. This is not used very often, currently only for
|
||||
:c:func:`k_thread_create()`.
|
||||
|
||||
* :c:macro:`_SYSCALL_MEMORY_READ()` validates a memory buffer of a particular
|
||||
size. The calling thread must have read permissions on the entire buffer.
|
||||
|
||||
* :c:macro:`_SYSCALL_MEMORY_WRITE()` is the same as
|
||||
:c:macro:`_SYSCALL_MEMORY_READ()` but the calling thread must additionally
|
||||
have write permissions.
|
||||
|
||||
* :c:macro:`_SYSCALL_MEMORY_ARRAY_READ()` validates an array whose total size
|
||||
is expressed as separate arguments for the number of elements and the
|
||||
element size. This macro correctly accounts for multiplication overflow
|
||||
when computing the total size. The calling thread must have read permissions
|
||||
on the total size.
|
||||
|
||||
* :c:macro:`_SYSCALL_MEMORY_ARRAY_WRITE()` is the same as
|
||||
:c:macro:`_SYSCALL_MEMORY_ARRAY_READ()` but the calling thread must
|
||||
additionally have write permissions.
|
||||
|
||||
* :c:macro:`_SYSCALL_VERIFY_MSG()` does a runtime check of some boolean
|
||||
expression which must evaluate to true otherwise the check will fail.
|
||||
A variant :c:macro:`_SYSCALL_VERIFY` exists which does not take
|
||||
a message parameter, instead printing the expression tested if it
|
||||
fails. The latter should only be used for the most obvious of tests.
|
||||
|
||||
If any check fails, a kernel oops will be triggered which will kill the
|
||||
calling thread. This is done instead of returning some error condition to
|
||||
keep the APIs the same when calling from supervisor mode.
|
||||
|
||||
Handler Declaration
|
||||
-------------------
|
||||
|
||||
All handler functions have the same prototype:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
u32_t _handler_<API name>(u32_t arg1, u32_t arg2, u32_t arg3,
|
||||
u32_t arg4, u32_t arg5, u32_t arg6, void *ssf)
|
||||
|
||||
All handlers return a value. Handlers are passed exactly six arguments, which
|
||||
were sent from user mode to the kernel via registers in the
|
||||
architecture-specific system call implementation, plus an opaque context
|
||||
pointer which indicates the system state when the system call was invoked from
|
||||
user code.
|
||||
|
||||
To simplify the prototype, the variadic :c:macro:`_SYSCALL_HANDLER()` macro
|
||||
should be used to declare the handler name and names of each argument. Type
|
||||
information is not necessary since all arguments and the return value are
|
||||
:c:type:`u32_t`. Using :c:func:`k_sem_init()` as an example:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
_SYSCALL_HANDLER(k_sem_init, sem, initial_count, limit)
|
||||
{
|
||||
...
|
||||
}
|
||||
|
||||
Note that system calls may have more than six arguments. In this case,
|
||||
the sixth and subsequent arguments to the system call are placed into a struct,
|
||||
and a pointer to that struct is passed to the handler as its sixth argument.
|
||||
See ``include/syscall.h`` to see how this is done; the struct passed in must be
|
||||
validated like any other memory buffer.
|
||||
|
||||
After validating all the arguments, the handler function needs to then call
|
||||
the implementation function. If the implementation function returns a value,
|
||||
this needs to be returned by the handler, otherwise the handler should return
|
||||
0.
|
||||
|
||||
Using :c:func:`k_sem_init()` as an example again, we need to enforce that the
|
||||
semaphore object passed in is a valid semaphore object (but not necessarily
|
||||
initialized), and that the limit parameter is nonzero:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
_SYSCALL_HANDLER(k_sem_init, sem, initial_count, limit)
|
||||
{
|
||||
_SYSCALL_OBJ_INIT(sem, K_OBJ_SEM);
|
||||
_SYSCALL_VERIFY(limit != 0);
|
||||
_impl_k_sem_init((struct k_sem *)sem, initial_count, limit);
|
||||
return 0;
|
||||
}
|
13
doc/kernel/usermode/usermode.rst
Normal file
13
doc/kernel/usermode/usermode.rst
Normal file
|
@ -0,0 +1,13 @@
|
|||
.. _usermode:
|
||||
|
||||
User Mode
|
||||
#########
|
||||
|
||||
This section describes how threads may be configured to run in user mode,
|
||||
and how permissions for these threads are managed.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
kernelobjects.rst
|
||||
syscalls.rst
|
|
@ -458,3 +458,106 @@ Each architecture also needs its own linker script, even if most sections can
|
|||
be derived from the linker scripts of other architectures. Some sections might
|
||||
be specific to the new architecture, for example the SCB section on ARM and the
|
||||
IDT section on x86.
|
||||
|
||||
Hardware Stack Protection
|
||||
=========================
|
||||
|
||||
This option uses hardware features to generate a fatal error if a thread
|
||||
in supervisor mode overflows its stack. This is useful for debugging, although
|
||||
for a couple reasons, you can't reliably make any assertions about the state
|
||||
of the system after this happens:
|
||||
|
||||
* The kernel could have been inside a critical section when the overflow
|
||||
occurs, leaving important global data structures in a corrupted state.
|
||||
* For systems that implement stack protection using a guard memory region,
|
||||
it's possible to overshoot the guard and corrupt adjacent data structures
|
||||
before the hardware detects this situation.
|
||||
|
||||
To enable the :option:`CONFIG_HW_STACK_PROTECTION` feature, the system must
|
||||
provide some kind of hardware-based stack overflow protection, and enable the
|
||||
:option:`CONFIG_ARCH_HAS_STACK_PROTECTION` option.
|
||||
|
||||
There are no C APIs that need to be implemented to support stack protection,
|
||||
and it's entirely implemented within the ``arch/`` code. However in most cases
|
||||
(such as if a guard region needs to be defined) the architecture will need to
|
||||
declare its own versions of the K_THREAD_STACK macros in ``arch/cpu.h``:
|
||||
|
||||
* :c:macro:`_ARCH_THREAD_STACK_DEFINE()`
|
||||
* :c:macro:`_ARCH_THREAD_STACK_ARRAY_DEFINE()`
|
||||
* :c:macro:`_ARCH_THREAD_STACK_MEMBER()`
|
||||
* :c:macro:`_ARCH_THREAD_STACK_SIZEOF()`
|
||||
|
||||
For systems that implement stack protection using a Memory Protection Unit
|
||||
(MPU) or Memory Management Unit (MMU), this is typically done by declaring a
|
||||
guard memory region immediately before the stack area.
|
||||
|
||||
* On MMU systems, this guard area is an entire page whose permissions in the
|
||||
page table will generate a fault on writes. This page needs to be
|
||||
configured in the arch's _new_thread() function.
|
||||
|
||||
* On MPU systems, one of the MPU regions needs to be reserved for the thread
|
||||
stack guard area, whose size should be minimized. The region in the MPU
|
||||
should be reconfigured on context switch such that the guard region
|
||||
for the incoming thread is not writable.
|
||||
|
||||
User Mode Threads
|
||||
=================
|
||||
|
||||
To support user mode threads, several kernel-to-arch APIs need to be
|
||||
implemented, and the system must enable the :option:`CONFIG_ARCH_HAS_USERSPACE`
|
||||
option. Please see the documentation for each of these functions for more
|
||||
details:
|
||||
|
||||
* :cpp:func:`_arch_buffer_validate()` to test whether the current thread has
|
||||
access permissions to a particular memory region
|
||||
|
||||
* :cpp:func:`_arch_user_mode_enter()` which will irreversably drop a supervisor
|
||||
thread to user mode privileges. The stack must be wiped.
|
||||
|
||||
* :cpp:func:`_arch_syscall_oops()` which generates a kernel oops when system
|
||||
call parameters can't be validated, in such a way that the oops appears to be
|
||||
generated from where the system call was invoked in the user thread
|
||||
|
||||
* :cpp:func:`_arch_syscall_invoke0()` through
|
||||
:cpp:func:`_arch_syscall_invoke6()` invoke a system call with the
|
||||
appropriate number of arguments which must all be passed in during the
|
||||
privilege elevation via registers.
|
||||
|
||||
* :cpp:func:`_arch_is_user_context()` return nonzero if the CPU is currently
|
||||
running in user mode
|
||||
|
||||
* :cpp:func:`_arch_mem_domain_max_partitions_get()` which indicates the max
|
||||
number of regions for a memory domain. MMU systems have an unlimited amount,
|
||||
MPU systems have constraints on this.
|
||||
|
||||
* :cpp:func:`_arch_mem_domain_partition_remove()` Remove a partition from
|
||||
a memory domain if the currently executing thread was part of that domain.
|
||||
|
||||
* :cpp:func:`_arch_mem_domain_destroy()` Reset the thread's memory domain
|
||||
configuration
|
||||
|
||||
In addition to implementing these APIs, there are some other tasks as well:
|
||||
|
||||
* :cpp:func:`_new_thread()` needs to spawn threads with :c:macro:`K_USER` in
|
||||
user mode
|
||||
|
||||
* On context switch, the outgoing thread's stack memory should be marked
|
||||
inaccessible to user mode by making the appropriate configuration changes in
|
||||
the memory management hardware.. The incoming thread's stack memory should
|
||||
likewaise be marked as accessible. This ensures that threads can't mess with
|
||||
other thread stacks.
|
||||
|
||||
* On context switch, the system needs to switch between memory domains for
|
||||
the incoming and outgoing threads.
|
||||
|
||||
* Thread stack areas must include a kernel stack region. This should be
|
||||
inaccessible to user threads at all times. This stack will be used when
|
||||
system calls are made. This should be fixed size for all threads, and must
|
||||
be large enough to handle any system call.
|
||||
|
||||
* A software interrupt or some kind of privilege elevation mechanism needs to
|
||||
be established. This is closely tied to how the _arch_syscall_invoke macros
|
||||
are implemented. On system call, the appropriate handler function needs to
|
||||
be looked up in _k_syscall_table. Bad system call IDs should jump to the
|
||||
:cpp:enum:`K_SYSCALL_BAD` handler. Upon completion of the system call, care
|
||||
must be taken not to leak any register state back to user mode.
|
||||
|
|
Loading…
Reference in a new issue