312 lines
12 KiB
ReStructuredText
312 lines
12 KiB
ReStructuredText
.. _secvar-driver-api:
|
|
|
|
Secvar Drivers
|
|
==============
|
|
|
|
This document will attempt to define the expected behavior of the two secvar
|
|
drivers, and how a developer should implement a new one.
|
|
|
|
|
|
Storage vs Backend drivers
|
|
--------------------------
|
|
|
|
There are two types of drivers for secure variable support, storage and backend
|
|
drivers. Storage drivers are the most simple: they control how and where secure
|
|
variables are stored for a given platform. Backend drivers on the other hand,
|
|
can be bit more complex. They control the overall scheme of OS secureboot --
|
|
from what variables are used, what format the variables are intended to be, how
|
|
they are updated, and how to determine the platform's OS secure boot state.
|
|
|
|
These drivers are intended to be as self-contained as possible, so that ideally
|
|
any combination of storage and backend drivers in the future should be
|
|
compatible.
|
|
|
|
|
|
Storage Driver API
|
|
------------------
|
|
|
|
The storage driver is expected to:
|
|
* persist secure variables in a tamper-resistant manner
|
|
* handle two logical types of variable lists (referred to as "banks")
|
|
* the "variable bank" stores the active list of variables
|
|
* the "update bank" stores proposed updates to the variable bank
|
|
* handle variables using a specific secvar flag in a sensible manner
|
|
|
|
Storage drivers must implement the following hooks for secvar to properly
|
|
utilize:
|
|
|
|
.. code-block:: c
|
|
|
|
struct secvar_storage_driver {
|
|
int (*load_bank)(struct list_head *bank, int section);
|
|
int (*write_bank)(struct list_head *bank, int section);
|
|
int (*store_init)(void);
|
|
void (*lock)(void);
|
|
uint64_t max_var_size;
|
|
};
|
|
|
|
The following subsections will give a summary of each hook, when they are used,
|
|
and their expected behavior.
|
|
|
|
|
|
store_init
|
|
^^^^^^^^^^
|
|
|
|
The ``store_init`` hook is called at the beginning of secure variable
|
|
intialization. This hook should perform any initialization logic required for
|
|
the other hooks to operate.
|
|
|
|
IMPORTANT: If this hook returns an error (non-zero) code, secvar will
|
|
immediately halt the boot. When implementing this hook, consider the
|
|
implications of any errors in initialization, and whether they may affect the
|
|
secure state. For example, if secure state is indeterminable due to some
|
|
hardware failure, this is grounds for a halt.
|
|
|
|
This hook should only be called once. Subsequent calls should have no effect,
|
|
or raise an error.
|
|
|
|
|
|
load_bank
|
|
^^^^^^^^^
|
|
|
|
The ``load_bank`` hook should load variables from persistent storage into the
|
|
in-memory linked lists, for the rest of secvar to operate on.
|
|
|
|
The ``bank`` parameter should be an initialized linked list. This list may not
|
|
be empty, and this hook should only append variables to the list.
|
|
|
|
The variables this hook loads should depend on the ``section`` flag:
|
|
* if ``SECVAR_VARIABLE_BANK``, load the active variables
|
|
* if ``SECVAR_UPDATE_BANK``, load the proposed updates
|
|
|
|
This hook is called twice at the beginning of secure variable initialization,
|
|
one for loading each bank type into their respective lists. This hook may be
|
|
called again afterwards (e.g. a reset mechanism by a backend).
|
|
|
|
|
|
write_bank
|
|
^^^^^^^^^^
|
|
|
|
The ``write_bank`` hook should persist variables using some non-volatile
|
|
storage (e.g. flash).
|
|
|
|
The ``bank`` parameter should be an initialized linked list. This list may be
|
|
empty. It is up to the storage driver to determine how to handle this, but it is
|
|
strongly recommended to zeroize the storage location.
|
|
|
|
The ``section`` parameter indicates which list of variables is to be written
|
|
following the same pattern as in ``load_bank``.
|
|
|
|
This hook is called for the variable bank if the backend driver reports that
|
|
updates were processed. This hook is called for the update bank in all cases
|
|
EXCEPT where no updates were found by the backend (this includes error cases).
|
|
|
|
This hook should not be called more than once for the variable bank. This hook
|
|
is called once in the secvar initialization procedure, and then each time
|
|
``opal_secvar_enqueue_update()`` is successfully called.
|
|
|
|
|
|
lock
|
|
^^^^
|
|
|
|
The ``lock`` hook may perform any write-lock protections as necessary by the
|
|
platform. This hook is unconditionally called after the processing step
|
|
performed in the main secure variable logic, and should only be called once.
|
|
Subsequent calls should have no effect, or raise an error.
|
|
|
|
This hook MUST also be called in any error cases that may interrupt the regular
|
|
secure variable initialization flow, to prevent leaving the storage mechanism
|
|
open to unauthorized writes.
|
|
|
|
This hook MUST halt the boot if any internal errors arise that may compromise
|
|
the protection of the storage.
|
|
|
|
If locking is not applicable to the storage mechanism, this hook may be
|
|
implemented as a no-op.
|
|
|
|
|
|
max_var_size
|
|
^^^^^^^^^^^^
|
|
|
|
The ``max_var_size`` field is not a function hook, but a value to be referenced
|
|
by other components to determine the maximum variable size. As this driver is
|
|
responsible for persisting variables somewhere, it has the option to determine
|
|
the maximum size to use.
|
|
|
|
|
|
A Quick Note on Secvar Flags
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
|
|
While "communication" between the storage and backend drivers has been
|
|
minimized as best as possible, there are a few cases where the storage driver
|
|
may need to take a few hints from the backend.
|
|
|
|
The ``flags`` field in ``struct secvar_node`` may contain one of the following
|
|
values:
|
|
|
|
.. code-block:: c
|
|
|
|
#define SECVAR_FLAG_VOLATILE 0x1
|
|
#define SECVAR_FLAG_PROTECTED 0x2
|
|
|
|
At time of writing this document, the flags are mutually exclusive, however
|
|
this may change in the future.
|
|
|
|
``VOLATILE`` indicates that the storage driver should NOT persist this variable
|
|
to storage.
|
|
|
|
``PROTECTED`` indicates that this variable has a heightened importance than
|
|
other variables, and if applicable to the storage driver, stored in a more
|
|
secure/tamper-resistant region (e.g. store variables important to secureboot
|
|
state in TPM NV rather than PNOR on p9).
|
|
|
|
|
|
Backend Driver API
|
|
------------------
|
|
|
|
The backend driver at the core defines how secure variables are defined and
|
|
processed, and by extension, also how operate the platform's secure boot modes.
|
|
|
|
.. code-block:: c
|
|
|
|
struct secvar_backend_driver {
|
|
int (*pre_process)(struct list_head *variable_bank
|
|
struct list_head *update_bank);
|
|
int (*process)(struct list_head *variable_bank
|
|
struct list_head *update_bank);
|
|
int (*post_process)(struct list_head *variable_bank
|
|
struct list_head *update_bank);
|
|
int (*validate)(struct secvar *var);
|
|
const char *compatible;
|
|
};
|
|
|
|
The following subsections will give a summary of each hook, when they are used,
|
|
and their expected behaviors.
|
|
|
|
|
|
pre_process
|
|
^^^^^^^^^^^
|
|
|
|
The ``pre_process`` hook is an optional hook that a backend driver may implement
|
|
to handle any early logic prior to processing. If this hook is set to ``NULL``,
|
|
it is skipped.
|
|
|
|
As this hook is called just after loading the variables from the storage driver
|
|
but just before ``process``, this hook is provided for convenience to do any
|
|
early initialization logic as necessary.
|
|
|
|
Any error code returned by this hook will be treated as a failure, and halt
|
|
secure variable initialization.
|
|
|
|
Example usage:
|
|
* initialize empty variables that were not loaded from storage
|
|
* allocate any internal structures that may be needed for processing
|
|
|
|
|
|
process
|
|
^^^^^^^
|
|
|
|
The ``process`` hook is the only required hook, and should contain all variable
|
|
update process logic. Unlike the other two hooks, this hook must be defined, or
|
|
secure variable initialization will halt.
|
|
|
|
This hook is expected to iterate through any variables contained in the
|
|
``update_bank`` list argument, and perform any action on the
|
|
``variable_bank`` list argument as the backend seems appropriate for the given
|
|
update (e.g. add/remove/update variable)
|
|
|
|
NOTE: the state of these bank lists will be written to persistent storage as-is,
|
|
so for example, if the update bank should be cleared, it should be done prior to
|
|
returning from this hook.
|
|
|
|
Unlike the other two hooks, this hook may return a series of return codes
|
|
indicating various status situations. This return code is exposed in the device
|
|
tree at ``secvar/update-status``. See the table below for an expected definition
|
|
of the return code meanings. Backends SHOULD document any deviations or
|
|
extensions to these definitions for their specific implementation.
|
|
|
|
To prevent excessive writes to flash, the main secure variable flow will only
|
|
perform writes when the ``process`` hook returns a status that declares
|
|
something has been changed. The variable bank is only written to storage if
|
|
``process`` returns ``OPAL_SUCCESS``.
|
|
|
|
On the other hand, the update bank is written to storage if the return code is
|
|
anything other than ``OPAL_EMPTY`` (which signals that there were no updates to
|
|
process). This includes all error cases, therefore the backend is responsible
|
|
for emptying the update bank prior to exiting with an error, if the bank is to
|
|
be cleared.
|
|
|
|
|
|
Status codes
|
|
""""""""""""
|
|
|
|
+-----------------+-----------------------------------------------+
|
|
| update-status | Generic Reason |
|
|
+-----------------+-----------------------------------------------+
|
|
| OPAL_SUCCESS | Updates were found and processed successfully |
|
|
+-----------------+-----------------------------------------------+
|
|
| OPAL_EMPTY | No updates were found, none processed |
|
|
+-----------------+-----------------------------------------------+
|
|
| OPAL_PARAMETER | Malformed, or unexpected update data blob |
|
|
+-----------------+-----------------------------------------------+
|
|
| OPAL_PERMISSION | Update failed to apply, possible auth failure |
|
|
+-----------------+-----------------------------------------------+
|
|
| OPAL_HARDWARE | Misc. storage-related error |
|
|
+-----------------+-----------------------------------------------+
|
|
| OPAL_RESOURCE | Out of space (reported by storage) |
|
|
+-----------------+-----------------------------------------------+
|
|
| OPAL_NO_MEM | Out of memory |
|
|
+-----------------+-----------------------------------------------+
|
|
|
|
See also: ``device-tree/ibm,opal/secvar/secvar.rst``.
|
|
|
|
|
|
post_process
|
|
^^^^^^^^^^^^
|
|
|
|
The ``post_process`` hook is an optional hook that a backend driver may
|
|
implement to handle any additional logic after the processing step. Like
|
|
``pre_process``, it may be set to ``NULL`` if unused.
|
|
|
|
This hook is called AFTER performing any writes to storage, and AFTER locking
|
|
the persistant storage. Any changes to the variable bank list in this hook will
|
|
NOT be persisted to storage.
|
|
|
|
Any error code returned by this hook will be treated as a failure, and halt
|
|
secure variable initialization.
|
|
|
|
Example usage:
|
|
* determine secure boot state (and set ``os-secure-enforcing``)
|
|
* remove any variables from the variable bank that do not need to be exposed
|
|
* append any additional volatile variables
|
|
|
|
|
|
validate
|
|
^^^^^^^^
|
|
|
|
!!NOTE!! This is not currently implemented, and the detail below is subject to
|
|
change.
|
|
|
|
The ``validate`` hook is an optional hook that a backend may implement to check
|
|
if a single variable is valid. If implemented, this hook is called during
|
|
``opal_secvar_enqueue_update`` to provide more immediate feedback to the caller
|
|
on proposed variable validity.
|
|
|
|
This hook should return ``OPAL_SUCCESS`` if the validity check passes. Any
|
|
other return code is treated as a failure, and will be passed through the
|
|
``enqueue_update`` call.
|
|
|
|
Example usage:
|
|
* check for valid payload data structure
|
|
* check for valid signature format
|
|
* validate the signature against current variables
|
|
* implement a variable white/blacklist
|
|
|
|
|
|
compatible
|
|
^^^^^^^^^^
|
|
|
|
The compatible field is a required field that declares the compatibility of
|
|
this backend driver. This compatible field is exposed in the
|
|
``secvar/compatible`` device tree node for subsequent kernels, etc to
|
|
determine how to interact with the secure variables.
|