zephyr/doc/kernel/usermode/kernelobjects.rst

261 lines
11 KiB
ReStructuredText
Raw Normal View History

.. _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
and can be located anywhere in the binary, or even declared on stacks. However,
to prevent accidental or intentional corruption by user threads, they must
not be located in any memory that user threads have direct access to.
In order for a kernel object to be usable by a user thread via system call
APIs, several conditions must be met on how the kernel object is declared:
* The 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.
Kernel objects that are found but do not meet the above conditions will not be
included in the generated table that is used to validate kernel object pointers
passed in from user mode.
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. The size
of this bitfield is controlled by the :option:`CONFIG_MAX_THREAD_BYTES`
option and the build system will generate an error if this value is too low.
* 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.
API calls from supervisor mode to set permissions on kernel objects that are
not being tracked by the kernel will be no-ops. Doing the same from user mode
will result in a fatal error for the calling thread.
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.
Configuration Options
*********************
Related configuration options:
* :option:`CONFIG_USERSPACE`
* :option:`CONFIG_APPLICATION_MEMORY`
* :option:`CONFIG_MAX_THREAD_BYTES`
APIs
****
* :c:func:`k_object_access_grant()`
* :c:func:`k_object_access_revoke()`
* :c:func:`k_object_access_all_grant()`
* :c:func:`k_thread_access_grant()`
* :c:func:`k_thread_user_mode_enter()`
* :c:macro:`K_THREAD_ACCESS_GRANT()`