aboutsummaryrefslogtreecommitdiff
path: root/docs/technical_references/tfm_secure_partition_runtime_library.rst
blob: 97e8444174de26f3be4ce13a6b6b4167795a173a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
################################
Secure Partition Runtime Library
################################

:Organization: Arm Limited
:Contact: tf-m@lists.trustedfirmware.org

**********
Background
**********
Trusted Firmware - M (TF-M) uses a toolchain provided runtime library and
supervisor calls to easily implement the PSA Firmware Framework (PSA FF) API.
This working model works well under isolation level 1 since there are no data
isolation requirements. While TF-M is evolving, this model is not suitable
because:

  - The high-level isolation requires isolating data but some toolchain library
    interfaces have their own global data which cannot be shared between the
    Secure Partitions.
  - The toolchain libraries are designed without taking security as a core
    design principle.

A TF-M specific runtime library is needed for the following reasons:

  - Easier evaluation or certification by security standards.
  - Source code transparency.
  - Sharing code to save ROM and RAM space for TF-M.

PSA FF specification also describes the requirements of C runtime API for Secure
Partitions.

This runtime library is named the ``Secure Partition Runtime Library``, and the
abbreviation is ``SPRTL``.

****************
Design Principal
****************
The following requirements are mandatory for SPRTL implementation:

.. important::
  - **CODE ONLY** - No read-write data should be introduced into runtime library
    implementation.
  - **Thread safe** - All functions are designed with thread-safe consideration.
    These APIs access caller stack and caller provided memory only.
  - **Isolation** - Runtime API code is set as executable and read-only in
    higher isolation levels.
  - **Security first** - SPRTL is designed for security and it may come with
    some performance loss.

API Categories
==============
Several known types of functions are included in SPRTL:

  - C runtime API.
  - RoT Service API.
  - PSA Client and Service API.
  - [Future expansion, to be detailed later] other secure API.

Security Implementation Requirements
------------------------------------
If ``malloc/realloc/free`` are provided, they must obey additional requirements
compared to the C standard: newly allocated memory must be initialized to
ZERO, and freed memory must be wiped immediately in case the block contains
sensitive data.

The comparison API ('memcmp' e.g.), they should not return immediately when the
fault case is detected. The implementation should execute in linear time based
on input to avoid execution timing side channel attack.

The pointer validation needs to be considered. In general, at least the
'non-NULL' checking is mandatory. A detection for invalid pointer leads to a
``psa_panic()``.

The following section describes the first 3 API types and the implementation
requirements.

C Runtime API
-------------
PSA FF describes a small set of the C standard library. Part of toolchain
library API can be used as default if these APIs meet the `Design Principal`_
and `Security Implementation Requirements`_. The toolchain 'header' and 'types'
can be reused to simplify the implementation.

These APIs can take the toolchain provided version, or separately implemented
in case there are extra requirements:

.. note::
  - 'memcpy()/memmove()/memset()'
  - String API

These APIs are proposed to be implemented with the security consideration
mentioned in `Security Implementation Requirements`_:

.. note::
  - 'memcmp()'
  - Other comparison API if referenced ('strcmp' e.g.).

The following functions are optional, but if present, they must conform to
additional `Security Implementation Requirements`_:

.. note::
  - 'malloc()/free()/realloc()'
  - 'assert()/printf()'

The following APIs are coupled with toolchain library much so applying toolchain
library implementation is recommended:

.. note::
  - Division and modulo - arithmetic operations.
  - Other low level or compiler specific functions (such as 'va_list').

Besides the APIs mentioned above, the following runtime APIs are required for
runtime APIs with private runtime context ('malloc' e.g.):

.. note::
  - '__sprtmain()' - partition entry runtime wrapper.

RoT Service API
---------------
The description of RoT Service API in PSA FF:

.. note::
  Arm recommends that the RoT Service developer also defines an RoT Service API
  and implementation to encapsulate the use of the IPC protocol, and improve the
  usability of the service for client firmware.

Part of the RoT Service API have proposed specifications, such as the PSA
Cryptography API, PSA Storage API, and PSA Attestation API. It is suggested that
the service developer create documents of their RoT Service API and make them
publicly available.

The RoT Service API has a large amount and it is the main part of SPRTL. This
chapter describes the general implementation of the RoT Service API and the
reason for putting them into SPRTL.

In general, a client uses the PSA Client API to access a secure service.
For example:

.. code-block:: c

  /* Example, not a real implementation */
  caller_status_t psa_example_service(void)
  {
    ...
    handle = psa_connect(SERVICE_SID, SERVICE_VERSION);
    if (INVALID_HANDLE(handle)) {
      return INVALID_RETURN;
    }

    status = psa_call(handle, type, invecs, inlen, outvecs, outlen);

    psa_close(handle);

    return TO_CALLER_STATUS(status);
  }

This example encapsulates the PSA Client API, and can be provided as a simpler
and more generic API for clients to call. It is not possible to statically link
this API to each Secure Partition because of the limited storage space. The
ideal solution is to put it inside SPRTL and share it to all Secure Partitions.
This would simplify the caller logic into this:

.. code-block:: c

  if (psa_example_service() != STATUS_SUCCESS) {
    /* do something */
  }

This is the simplest case of encapsulating PSA Client API. If a RoT Service API
is connect heavy, then, the encapsulation can be changed to include a connection
handle inside a context data structure. This context data structure type is
defined in RoT Service headers and the instance is allocated by API caller since
API implementation does not have private data.

.. note::
  - Even the RoT Service APIs are provided in SPRTL for all clients, the SPM
    performs the access check eventually and decides if the access to service
    can be processed.
  - For those RoT Service APIs only get called by a specific client, they can be
    implemented inside the caller client, instead of putting it into SPRTL.

PSA Client and Service API
--------------------------
Most of the PSA APIs can be called directly with supervisor calls. The only
special function is ``psa_call``, because it has **6** parameters. This makes
the supervisor call handler complex because it has to extract the parameters
from the stack. The definition of psa_call is the following:

.. code-block:: c

  psa_status_t psa_call(psa_handle_t handle, int32_t type,
                        const psa_invec *in_vec, size_t in_len,
                        psa_outvec *out_vec, size_t out_len);

The parameters need to be packed to avoid passing parameters on the stack, and
the supervisor call needs to unpack the parameters back to **6** for subsequent
processing.

Privileged Access Supporting
============================
Due to specified API (printf, e.g.) need to access privileged resources, TF-M
Core needs to provide interface for the resources accessing. The permission
checking must happen in Core while caller is calling these interface.

Secure Partition Scratch Area
=============================
For the API needs partition specific private data, there needs to be a way to
pass the partition specific data for the API. Use C language preprocessor to
forward the existing prototype declaration can work, but it has the risks of
breaking the build since this method needs compilers support ('-include' e.g.).
Furthermore, no valid runtime tricks can work due to these limitations on
M-profile architecture:

.. note::
  - We cannot apply the aligned mask on a stack address to get stack bottom
    where the private data pointer stands. This is because aligned stack bottom
    is not supported.
  - We cannot read special registers such as 'PSPLIMIT' for retrieving the
    private data pointer while executing in unprivileged mode.
  - Furthermore, some earlier versions of the ARM architecture do not have
    certain special-purpose registers ('PSPLIMIT' etc.).

A system-provided scratch area is a precondition for implementing APIs that need
to access private data (such as 'malloc'). The requirements for implementing
such an area are:

.. important::
  - The area must be ``READ-ONLY`` for the running Secure Partition.
  - The SPM must put the running Secure Partition's metadata into this area
    while scheduling.

With these requirements, the passed parameters can be retrieved by SPRTL easily
with a read operation on the fixed memory address.

Tooling Support on Partition Entry
==================================
PSA FF requires each Secure Partition to have an entry point. For example:

.. code-block:: c

  /* The entry point function must not return. */
  void entry_point(void);

Each partition has its own dedicated metadata for heap tracking and other
runtime state. The metadata is designed to be saved at the read-write data area
of a partition with a specific name. A generic entry point needs to be available
to get partition metadata and do initialization before calling into the actual
partition entry. This generic entry point is defined as '__sprtmain':

.. code-block:: c

    void __sprtmain(void)
    {
      /* Get current SP private data from scratch area */
      struct sprt_meta_t *m = (struct sprt_meta_t *)tfm_sprt_scratch_data;

      /* Potential heap init - check later chapter */
      if (m->heap_size) {
        m->heap_instance = tfm_sprt_heap_init(m->heap_sa, m->heap_sz);
      }

      /* Call thread entry 'entry_point' */
      m->thread_entry();

      /* SVC back to tell Core end this thread */
      SVC(THREAD_EXIT);
    }

Since SPM is not aware of the '__sprtmain' in SPRTL, it just calls into the
entry point listed in partition runtime data structure. And the partition writer
may be not aware of running of '__sprtmain' as the generic wrapper entry,
tooling support needs to happen to support this magic. Here is an example of
partition manifest:

.. code-block:: sh

  {
    "name": "TFM_SP_SERVICE",
    "type": "PSA-ROT",
    "priority": "NORMAL",
    "entry_point": "tfm_service_entry",
    "stack_size": "0x1800",
    "heap_size": "0x1000",
    ...
  }

Tooling would do manipulation to tell SPM the partition entry as '__sprtmain',
and TF-M SPM would switch the activated metadata into the scratch area. Finally,
the partition entry point gets called and run, tooling helps on the decoupling
of SPM and SPRTL implementation. The pseudo code of a tooling result:

.. code-block:: c

  struct partition_t sp1 {
    .name = "TFM_SP_SERVICE",
    .type = PSA_ROT,
    .priority = NORMAL,
    .id = 0x00000100,
    .entry_point = __sprtmain, /* Tell SPM entry is '__sprtmain' */
    .metadata = { /* struct sprt_meta_t */
      .heap_sa = sp1_heap_buf,
      .heap_sz = sizeof(sp1_heap_buf),
      .thread_entry = sp1_entry, /* Actual Partition Entry */
      .heap_instance = NULL,
    },
  }

Implementation
==============
The SPRTL C Runtime sources are put under:
'$TFM_ROOT/secure_fw/partitions/lib/sprt/'

The output of this folder is a static library named as 'libtfm_sprt.a'. The code
of 'libtfm_sprt.a' is put into a dedicated section so that a hardware protected
region can be applied to contain it.

The RoT Service API are put under service interface folder. These APIs are
marked with the same section attribute where 'libtfm_sprt.a' is put.

The Formatting API - 'printf' and variants
------------------------------------------
The 'printf' and its variants need special parameters passing mechanism. To
implement these APIs, the toolchain provided builtin macro 'va_list', 'va_start'
and 'va_end' cannot be avoided. This is because of some scenarios such as when
'stack canaries' are enabled, only the compiler knows the format of the 'canary'
in order to extract the parameters correctly.

To provide a simple implementation, the following requirements are defined for
'printf':

- Format keyword 'xXduscp' needs to be supported.
- Take '%' as escape flag, '%%' shows a '%' in the formatted string.
- To save heap usage, 32 bytes buffer in the stack for collecting formatted
  string.
- Flush string outputting due to: a) buffer full b) function ends.

The interface for flushing can be a logging device.

Function with Implied Parameters
--------------------------------
Take 'malloc' as an example. There is only one parameter for 'malloc' in
the prototype. Heap management code is put in the SPRTL for sharing with caller
partitions. The heap instance belongs to each partition, which means this
instance needs to be passed into the heap management code as a parameter. For
allocation API in heap management, it needs two parameters - 'size' and
'instance', while for 'malloc' caller it needs a 'malloc' with one parameter
'size' only. As mentioned in the upper chapter, this instance can be retrieved
from the Secure Partition scratch area. The implementation can be:

.. code-block:: c

  void *malloc(size_t sz)
  {
      struct sprt_meta_t *m = (struct sprt_meta_t *)tfm_sprt_scratch_data;

      return tfm_sprt_alloc(m->heap_instance, sz);
  }

--------------

*Copyright (c) 2019-2020, Arm Limited. All rights reserved.*