Non-Secure Interrupt Handling
Updated 22 Days AgoPublic

Terms

TermMeaning
AIRCRApplication Interrupt and Reset Control Register
AIRCR.PRISPRIoritize Secure exceptions
ISRInterrupt Service Routin
NSNon-Secure
NSPMNon-Secure Partition Manager
SAUSecurity attribution unit
SPMSecure partition Manager
TF-MTrusted Firmware-M

Introduction

In the current design it is possible to use Non-secure interrupts, however the Non-secure interrupts cannot pre-empt Secure service execution. TF-M core achieves this by making the following configurations:

  1. AIRCR.PRIS is set to 1 during TF-M core initialisation. This de-prioritizes Non-secure exceptions compared to Secure exceptions, so that they cannot interupt Secure Handler mode. the AIRCR.PRIS bit remains set during TF-M run. The bit is set in the function
static int32_t tfm_core_set_secure_exception_priorities(void);

Note: Setting AIRCR.PRIS in itself doesn't prevent NS interrupts to pre-empt Secure Thread mode when it runs on normal priority i.e., 256.

  1. On Secure service entry PRIMASK_NS is set, to boost the Non-secure execution priority so that all NS interrupts are masked. This is done in the TFM_NS_EXC_DISABLE() macro called from
static int32_t tfm_start_partition(struct tfm_sfn_req_s *desc_ptr, uint32_t excReturn)

Note: '2.' is only in Library model.

In the below chapters a design is proposed to enable Non-secure interrupts to pre-empt Secure Thread mode.

Limitations of the proposed design

Library model

The proposed design keeps the Secure lock in place, which means Non-secure code can do a single Secure service call, all further calls to Secure services will be rejected until the first Secure service call returns.

IPC model

The PSA client API can only be entered once. All the functions in the client API (psa_framework_version, psa_version, psa_connect, psa_call, psa_close) are part of the same critical section.

Non-secure software

The Non-secure API functions that wraps the Secure service veneers (either Library or IPC model) should continue to use the NS locking mechanism currently implemented by calling tfm_ns_lock_dispatch(...).

If any of the Non-secure software decides to bypass the locking mechanism, then concurrent access of veneer functions is detected by TF-M, and NS software execution is halted.

Note: This makes denial of service attack possible by a malicious NS application

Enabling NS interrupts to pre-empt Secure execution

To enable NS interrupts, 2) described in chapter 'Current design' must be turned off. (For details see implementation notes)

When a Non-secure interrupt is triggered during Secure code execution, and the ISR have to be executed based on the priority settings, the hardware saves the current execution context on the current Secure stack, and clears the general purpose registers, to prevents data leakage. After that the NS ISR starts execution.

When the Non-secure ISR returns with the EXC_RETURN value provided to it in the link register, the context is fetched from the Secure stack, and the Secure code continues execution.

If TF-M is used with a single threaded NS software, the mechanisms provided by the HW is enough to maintain the consistency of the system, and keep the secrets.

However if the NS software is allowed to change execution context during an interrupt (e.g an NS operating system schedules another thread during a SysTick interrupt), then the Secure code can return execution to an NS thread, with the context of a different thread. So extra measures needs to be introduced in TF-M to prevent this.

For IPC model

In the current implementation there is no locking mechanism on the Secure side that would prevent the Non-secure code to enter psa_client functions multiple times. (For the Library model the tfm_secure_lock global variable is used for this purpose). Note, that the tfm_ns_lock_dispatch(...) function that is used by the NS service API implementations to prevent Secure services to be called simultaneously can be bypassed by a malicious Non-secure application, so a Secure side locking mechanism have to be implemented.

When an NS client calls a PSA client API function, the client ID of the calling NS context have to be saved, and execution can only return to NS if the current scheduled NS thread is the one that did the call.

For Library model

As currently there is no scheduling in the Library model, the calls follow each other just like in an ordinary function call scheme. Then when the original Secure service that was called from the NS code is about to return, it has to check for the current NS client ID, and only return if it is the same as the one saved on Secure service entry from NS. If the ID's don't match, the Secure side waits so that NS OS can do context switch.

Common measures

Exception priorities

The priority of the Secure SVC and the Secure faults must be higher than any Secure exception in the system. Once this is done, the following fixme can be removed from secure_fw\core\tfm_core.c: tfm_core_set_secure_exception_priorities:

\* FixMe: Explicitly set secure fault and Secure SVC priority to highest */

Note: The priority of PendSV Is set to be the lowest priority Secure interrupt, but still higher than the maximum possible NS execution priority when AIRCR.PRIS is set.

NSPM

If the Non-secure software allows the use of multiple threads, it needs to use the NSPM feature of TF-M. It is expected, that all the NS context that use Secure services have a unique client ID, and the other contexts, that don't use Secure service need to have a client ID that doesn't match with any of the client IDs of the Secure service calling contexts.

In other words, for all the cs(0), cs(1), ..., cs(n) NS contexts that use Secure services and for all cn(0), cn(1), ..., cn(m) NS contexts that don't use Secure service (where n > 0, m >= 0):

  • cs(i).client_id != cs(j).client_id (where 0 <= i < j <= n)
  • cs(i).client_id != cn(j).client_id (where 0 <= i <= n and 0 <= j <= m)

Entering from Non-secure to Secure

The Secure code can be entered through the following gateways:

  1. NSPM related functions (TZ_<operation>(...), tfm_register_client_id(...))

    These functions are expected to be called from Handler mode. The execution priority, after the execution crosses the security boundary will be the same as it was during NS execution. This means a malicious Non-secure application, can set up Non-secure interrupt priorities in a way that it can enter one or more of the NSPM APIs simultaneously.

    This might leave the NSPM database in an inconsistent state, however if the attacker has influence over the interrupt priorities, they can gain no additional privilege by this.

    Note: The NS software is able to consume the main stack of the Secure software. The Main Secure stack have to be protected by MSPLIM, to prevent stack overflow. However a denial of service attack is still possible.
  2. PSA Client API, Library model service veneers

    When a veneer is called from Non-secure, the Secure code have to check whether the veneer is only entered by a single NS thread. This can be done by checking the veneer stack usage. It can only contain the locals of the veneer implementation. If the veneer has been entered from multiple NS threads, there is at least one extra context stack frame that was created by the hardware when the veneer execution had been interrupted by the NS systick.

Return from Secure to Non-secure

  1. NSPM related functions (TZ_<operation>(...), tfm_register_client_id(...))

    No extra measures are necessary.
  2. PSA Client API, Library model service veneers

    The transition from Non-secure to Secure has to happen in Thread mode (This is a requirement defined in TF-M). As a result of this, the return to Non-secure also happens in Thread mode.

    The expectation is that the Secure code only returns to the Non-secure, if it is sure, that the client ID associated with the current Secure operation (IPC or Library model service call) is the client ID of the currently active Non-secure context.

    Since the transition back to NS is in Thread mode, it may be interrupted by NS interrupt, and a taskswitch can happen. This implies that the client ID validation and the transition from Secure to NS have to be an atomic operation (as viewed from Non-secure. Secure interrupts may be triggered).

    This can be implemented by disabling the Non-secure interrupts before checking the NS client ID, and re-enable them from NS code. Enabling interrupts have to be done in privileged mode, so there should be an API in the Non-secure software that can be called by the Non-secure service API to re-enable interrupts.

    Setting PRIMASK_NS is not a solution in this case for disabling NS interrupts, as SVCall has a maximum possible priority of 0, so the SVC handler cannot be executed while PRIMASK_NS is set.

    Instead BASEPRI_NS have to be set to 1, and Non-secure code have to make sure that no interrupt has higher priority than 1.

Implementation notes

IPC model

Save NS client ID on Secure service veneer entry

As long as the Secure lock is in place, a single client ID have to be stored, so it can be done in a global variable.

The caller client ID can be saved in the function void tfm_psa_ipc_request_handler(uint32_t svc_ctx[]) depending on the return value of the PSA API function. (Doesn't execute any Secure service code, only sets signals, and triggrs scheduling. If the return value is success, that means a scheduling is to happen, and a secure service is about to be entered.)

Check client ID on Secure service return

The saved client ID can be compared with the current client ID in the function tfm_core_ns_ipc_request, after the SVC return. Before doing the comparison, BASEPRI_NS must be set to 1.

The original BASEPRI_NS value can be stored in a global variable (because of the single context).

If the client ID's don't match, BASEPRI_NS must be reset, WFI to be issued, and start the checking sequence from the beginning.

Library model

Save NS client ID on Secure veneer entry

As long as the Secure lock is in place, only a single client ID have to be stored, so it can be done in a global variable.

The caller client ID can be saved in the function uint32_t tfm_core_partition_request_svc_handler(uint32_t *svc_args, uint32_t excReturn).

Check client ID on SP return

The saved client ID can be compared with the current client ID in the function tfm_core_partition_request, after the tfm_core_sfn_request return. Before doing the comparison, BASEPRI_NS must be set to 1.

The original BASEPRI_NS value can be stored in a global variable (because of the single context).

If the client ID's don't match, BASEPRI_NS must be reset, WFI to be issued, and the checking sequence have to be started from the beginning.

Common

Restore BASEPRI_NS from NS code

implement the function

/**
 * \brief Enables interrupts after a secure service call returns.
 *
 * This function sets BASEPRI to its default value. Calls SVC.
 */
void enable_interrupt_after_secure_call_return();

The original BASEPRI_NS value must be reset by the function.

This function must be called from tfm_ns_lock_dispatch.

Query the original BASEPRI_NS

A Secure function, that can only be called from NS Handler mode (just like tfm_register_client_id) have to be created for the BSAEPRI_NS to be queried.

Enforce single NS entry to Secure

On Secure service entry (from the SVC implementation) check that (pseudocode)

svc_handler()
{
    /* If there are multiple context stacked in veneer stack, hang NSPE */
    expected_sp_top = veneer_stack_addr -
                        sizeof(svc_state_context) + sizeof(locals);
    if (__get_PSP() != expected_sp_top) {
        /* Multiple frames are existing, panic */
        panic();
    }
}

Testing

Basic scenario

Basic testing of the feature is possible, by adding a new scenario to the existing IRQ test. The flow of the test would be something like this:

IRQ_TEST_1prepare test scenarioDo nothing
CORE_TEST_2prepare test scenarioDo nothing
NSprepare test scenarioInitialise and start timer
IRQ_TEST_1execute test scenarioDo nothing
CORE_TEST_2execute test scenarioBusy wait until NS interrupt is acknowledged (a flag in non Secure data is set) set flag CORE_TEST_2 waits on
NSexecute test scenarioDo nothing

The test is successful if NS execute test scenario returns

Advanced scenarios

Testing advanced scenarios (that involves NS scheduling during secure execution, NS interrupting Secure interrupt, Secure interrupting NS interrupt) would require more advanced test framework and are not covered in this proposal.

Last Author
matetothpal
Projects
Subscribers
ademars