261 lines
11 KiB
ReStructuredText
261 lines
11 KiB
ReStructuredText
.. _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()`
|
|
|