.. _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.