| .. _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()` |
| |