You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
274 lines
12 KiB
274 lines
12 KiB
.. _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. |
|
|
|
Permission on an object also has the semantics of a reference to an object. |
|
This is significant for certain object APIs which do temporary allocations, |
|
or objects which themselves have been allocated from a runtime memory pool. |
|
|
|
If an object loses all references, two events may happen: |
|
|
|
* If the object has an associated cleanup function, the cleanup function |
|
may be called to release any runtime-allocated buffers the object was using. |
|
|
|
* If the object itself was dynamically allocated, the memory for the object |
|
will be freed. |
|
|
|
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 static 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. They |
|
must not be located in any memory partitions that are user-accessible. |
|
|
|
* 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. |
|
|
|
Dynamic Objects |
|
*************** |
|
|
|
Kernel objects may also be allocated at runtime if |
|
:option:`CONFIG_DYNAMIC_OBJECTS` is enabled. In this case, the |
|
:cpp:func:`k_object_alloc()` API may be used to instantiate an object from |
|
the calling thread's resource pool. Such allocations may be freed in two |
|
ways: |
|
|
|
* Supervisor threads may call :cpp:func:`k_object_free()` to force a dynamic |
|
object to be released. |
|
|
|
* If an object's references drop to zero (which happens when no threads have |
|
permissions on it) the object will be automatically freed. User threads |
|
may drop their own permission on an object with |
|
:cpp:func:`k_object_release()`, and their permissions are automatically |
|
cleared when a thread terminates. Supervisor threads may additionally |
|
revoke references for another thread using |
|
:cpp:func:`k_object_access_revoke()`. |
|
|
|
Because permissions are also used for reference counting, it is important for |
|
supervisor threads to acquire permissions on objects they are using even though |
|
the access control aspects of the permission system are not enforced. |
|
|
|
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. |
|
|
|
Dynamic objects allocated at runtime are tracked in a runtime red/black tree |
|
which is used in parallel to the gperf table when validating object pointers. |
|
|
|
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. This API is not |
|
available to user threads, however user threads may use |
|
:c:func:`k_object_release()` to relinquish their own permissions on an |
|
object. |
|
|
|
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. |
|
|
|
Objects allocated with :cpp:func:`k_object_alloc()` implicitly grant |
|
permission on the allocated object to 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()` called 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; |
|
... |
|
}; |
|
|
|
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. |
|
|
|
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. |
|
|
|
Driver instances of the new subsystem should now be tracked. |
|
|
|
Configuration Options |
|
********************* |
|
|
|
Related configuration options: |
|
|
|
* :option:`CONFIG_USERSPACE` |
|
* :option:`CONFIG_MAX_THREAD_BYTES` |
|
|
|
API Reference |
|
************* |
|
|
|
.. doxygengroup:: usermode_apis |
|
:project: Zephyr
|
|
|