1################################
2Secure Partition Runtime Library
3################################
4
5:Organization: Arm Limited
6:Contact: tf-m@lists.trustedfirmware.org
7
8**********
9Background
10**********
11Trusted Firmware - M (TF-M) uses a toolchain provided runtime library and
12supervisor calls to easily implement the PSA Firmware Framework (PSA FF) API.
13This working model works well under isolation level 1 since there are no data
14isolation requirements. While TF-M is evolving, this model is not suitable
15because:
16
17  - The high-level isolation requires isolating data but some toolchain library
18    interfaces have their own global data which cannot be shared between the
19    Secure Partitions.
20  - The toolchain libraries are designed without taking security as a core
21    design principle.
22
23A TF-M specific runtime library is needed for the following reasons:
24
25  - Easier evaluation or certification by security standards.
26  - Source code transparency.
27  - Sharing code to save ROM and RAM space for TF-M.
28
29PSA FF specification also describes the requirements of C runtime API for Secure
30Partitions.
31
32This runtime library is named the ``Secure Partition Runtime Library``, and the
33abbreviation is ``SPRTL``.
34
35****************
36Design Principle
37****************
38The following requirements are mandatory for SPRTL implementation:
39
40.. important::
41  - **CODE ONLY** - No read-write data should be introduced into runtime library
42    implementation.
43  - **Thread safe** - All functions are designed with thread-safe consideration.
44    These APIs access caller stack and caller provided memory only.
45  - **Isolation** - Runtime API code is set as executable and read-only in
46    higher isolation levels.
47  - **Security first** - SPRTL is designed for security and it may come with
48    some performance loss.
49
50API Categories
51==============
52Several known types of functions are included in SPRTL:
53
54  - C runtime API.
55  - RoT Service API.
56  - PSA Client and Service API.
57  - [Future expansion, to be detailed later] other secure API.
58
59Security Implementation Requirements
60------------------------------------
61If ``malloc/realloc/free`` are provided, they must obey additional requirements
62compared to the C standard: newly allocated memory must be initialized to
63ZERO, and freed memory must be wiped immediately in case the block contains
64sensitive data.
65
66The comparison API ('memcmp' e.g.), they should not return immediately when the
67fault case is detected. The implementation should execute in linear time based
68on input to avoid execution timing side channel attack.
69
70The pointer validation needs to be considered. In general, at least the
71'non-NULL' checking is mandatory. A detection for invalid pointer leads to a
72``psa_panic()``.
73
74The following section describes the first 3 API types and the implementation
75requirements.
76
77C Runtime API
78-------------
79PSA FF describes a small set of the C standard library. Part of toolchain
80library API can be used as default if these APIs meet the `Design Principle`_
81and `Security Implementation Requirements`_. The toolchain 'header' and 'types'
82can be reused to simplify the implementation.
83
84These APIs can take the toolchain provided version, or separately implemented
85in case there are extra requirements:
86
87.. note::
88  - 'memcpy()/memmove()/memset()'
89  - String API
90
91These APIs are proposed to be implemented with the security consideration
92mentioned in `Security Implementation Requirements`_:
93
94.. note::
95  - 'memcmp()'
96  - Other comparison API if referenced ('strcmp' e.g.).
97
98The following functions are optional, but if present, they must conform to
99additional `Security Implementation Requirements`_:
100
101.. note::
102  - 'malloc()/free()/realloc()'
103  - 'assert()/printf()'
104
105The following APIs are coupled with toolchain library much so applying toolchain
106library implementation is recommended:
107
108.. note::
109  - Division and modulo - arithmetic operations.
110  - Other low level or compiler specific functions (such as 'va_list').
111
112Besides the APIs mentioned above, the following runtime APIs are required for
113runtime APIs with private runtime context ('malloc' e.g.):
114
115.. note::
116  - '__sprtmain()' - partition entry runtime wrapper.
117
118RoT Service API
119---------------
120The description of RoT Service API in PSA FF:
121
122.. note::
123  Arm recommends that the RoT Service developer also defines an RoT Service API
124  and implementation to encapsulate the use of the IPC protocol, and improve the
125  usability of the service for client firmware.
126
127Part of the RoT Service API have proposed specifications, such as the PSA
128Cryptography API, PSA Storage API, and PSA Attestation API. It is suggested that
129the service developer create documents of their RoT Service API and make them
130publicly available.
131
132The RoT Service API has a large amount and it is the main part of SPRTL. This
133chapter describes the general implementation of the RoT Service API and the
134reason for putting them into SPRTL.
135
136In general, a client uses the PSA Client API to access a secure service.
137For example:
138
139.. code-block:: c
140
141  /* Example, not a real implementation */
142  caller_status_t psa_example_service(void)
143  {
144    ...
145    handle = psa_connect(SERVICE_SID, SERVICE_VERSION);
146    if (INVALID_HANDLE(handle)) {
147      return INVALID_RETURN;
148    }
149
150    status = psa_call(handle, type, invecs, inlen, outvecs, outlen);
151
152    psa_close(handle);
153
154    return TO_CALLER_STATUS(status);
155  }
156
157This example encapsulates the PSA Client API, and can be provided as a simpler
158and more generic API for clients to call. It is not possible to statically link
159this API to each Secure Partition because of the limited storage space. The
160ideal solution is to put it inside SPRTL and share it to all Secure Partitions.
161This would simplify the caller logic into this:
162
163.. code-block:: c
164
165  if (psa_example_service() != STATUS_SUCCESS) {
166    /* do something */
167  }
168
169This is the simplest case of encapsulating PSA Client API. If a RoT Service API
170is connect heavy, then, the encapsulation can be changed to include a connection
171handle inside a context data structure. This context data structure type is
172defined in RoT Service headers and the instance is allocated by API caller since
173API implementation does not have private data.
174
175.. note::
176  - Even the RoT Service APIs are provided in SPRTL for all clients, the SPM
177    performs the access check eventually and decides if the access to service
178    can be processed.
179  - For those RoT Service APIs only get called by a specific client, they can be
180    implemented inside the caller client, instead of putting it into SPRTL.
181
182PSA Client and Service API
183--------------------------
184Most of the PSA APIs can be called directly with supervisor calls. The only
185special function is ``psa_call``, because it has **6** parameters. This makes
186the supervisor call handler complex because it has to extract the parameters
187from the stack. The definition of psa_call is the following:
188
189.. code-block:: c
190
191  psa_status_t psa_call(psa_handle_t handle, int32_t type,
192                        const psa_invec *in_vec, size_t in_len,
193                        psa_outvec *out_vec, size_t out_len);
194
195The parameters need to be packed to avoid passing parameters on the stack, and
196the supervisor call needs to unpack the parameters back to **6** for subsequent
197processing.
198
199Privileged Access Supporting
200============================
201Due to specified API (printf, e.g.) need to access privileged resources, TF-M
202Core needs to provide interface for the resources accessing. The permission
203checking must happen in Core while caller is calling these interface.
204
205Secure Partition Local Storage
206==============================
207There are APIs that need to reference specific partition private data ('malloc'
208references local heap, e.g.), and the APIs reference the data by mechanisms
209other than function parameters. The mechanism in TF-M is called
210'Secure Partition Local Storage'.
211
212A straight way for accessing the local storage is to put the local storage
213pointer in a known position in the stack, but there is a bit of difficulty in
214particular scenarios.
215
216.. note::
217  - The partition's stack is not fixed-size aligned, using stack address
218    aligning method can not work.
219  - It requires privileged permission to *access* special registers such
220    as `PSPLIMIT`. And Armv6-M and Armv7-M don't have `PSPLIMIT`.`
221
222Another common method is to put the pointer in one shared global variable, and
223the scheduler maintains the value of this variable to point to the running
224partition's local storage in runtime. It does not fully align with SPRTL design
225prerequisites listed above, hence extra settings are required to guarantee the
226isolation boundaries are not broken.
227
228.. important::
229  - This variable is put inside a dedicated shared region and it can not hold
230    information not belonging to the owner.
231
232And this mechanism has disadvantages:
233
234  - It needs extra maintenance effort from the scheduler and extra resources
235    for containing the variable.
236
237TF-M chooses this common way as the default option for local storage and can
238be expanded to support more methods.
239
240Tooling Support on Partition Entry
241==================================
242PSA FF requires each Secure Partition to have an entry point. For example:
243
244.. code-block:: c
245
246  /* The entry point function must not return. */
247  void entry_point(void);
248
249Each partition has its own dedicated local_storage for heap tracking and other
250runtime state. The local_storage is designed to be saved at the read-write data area
251of a partition with a specific name. A generic entry point needs to be available
252to get partition local_storage and do initialization before calling into the actual
253partition entry. This generic entry point is defined as '__sprtmain':
254
255.. code-block:: c
256
257    void __sprtmain(void)
258    {
259      /* Get current SP private data from local storage */
260      struct p_sp_local_storage_t *m =
261              (struct p_sp_local_storage_t *)tfm_sprt_local_storage;
262
263      /* Potential heap init - check later chapter */
264      if (m->heap_size) {
265        m->heap_instance = tfm_sprt_heap_init(m->heap_sa, m->heap_sz);
266      }
267
268      /* Call thread entry 'entry_point' */
269      m->thread_entry();
270
271      /* Back to tell Core end this thread */
272      SVC(THREAD_EXIT);
273    }
274
275Since SPM is not aware of the '__sprtmain' in SPRTL, it just calls into the
276entry point listed in partition runtime data structure. And the partition writer
277may be not aware of running of '__sprtmain' as the generic wrapper entry,
278tooling support needs to happen to support this magic. Here is an example of
279partition manifest:
280
281.. code-block:: sh
282
283  {
284    "name": "TFM_SP_SERVICE",
285    "type": "PSA-ROT",
286    "priority": "NORMAL",
287    "entry_point": "tfm_service_entry",
288    "stack_size": "0x1800",
289    "heap_size": "0x1000",
290    ...
291  }
292
293Tooling would do manipulation to tell SPM the partition entry as '__sprtmain',
294and TF-M SPM would maintain the local storage at run time. Finally, the
295partition entry point gets called and run, tooling helps on the decoupling
296of SPM and SPRTL implementation. The pseudo code of a tooling result:
297
298.. code-block:: c
299
300  struct partition_t sp1 {
301    .name = "TFM_SP_SERVICE",
302    .type = PSA_ROT,
303    .priority = NORMAL,
304    .id = 0x00000100,
305    .entry_point = __sprtmain, /* Tell SPM entry is '__sprtmain' */
306    .local_storage = { /* struct sprt_local_storage_t */
307      .heap_sa = sp1_heap_buf,
308      .heap_sz = sizeof(sp1_heap_buf),
309      .thread_entry = sp1_entry, /* Actual Partition Entry */
310      .heap_instance = NULL,
311    },
312  }
313
314Implementation
315==============
316The SPRTL C Runtime sources are put under:
317'$TFM_ROOT/secure_fw/partitions/lib/runtime/'
318
319The output of this folder is a static library named as 'libtfm_sprt.a'. The code
320of 'libtfm_sprt.a' is put into a dedicated section so that a hardware protected
321region can be applied to contain it.
322
323The RoT Service API are put under service interface folder. These APIs are
324marked with the same section attribute where 'libtfm_sprt.a' is put.
325
326The Formatting API - 'printf' and variants
327------------------------------------------
328The 'printf' and its variants need special parameters passing mechanism. To
329implement these APIs, the toolchain provided builtin macro 'va_list', 'va_start'
330and 'va_end' cannot be avoided. This is because of some scenarios such as when
331'stack canaries' are enabled, only the compiler knows the format of the 'canary'
332in order to extract the parameters correctly.
333
334To provide a simple implementation, the following requirements are defined for
335'printf':
336
337- Format keyword 'xXduscp' needs to be supported.
338- Take '%' as escape flag, '%%' shows a '%' in the formatted string.
339- To save heap usage, 32 bytes buffer in the stack for collecting formatted
340  string.
341- Flush string outputting due to: a) buffer full b) function ends.
342
343The interface for flushing can be a logging device.
344
345Function needs implied inputs
346-----------------------------
347Take 'malloc' as an example. There is only one parameter for 'malloc' in
348the prototype. Heap management code is put in the SPRTL for sharing with caller
349partitions. The heap instance belongs to each partition, which means this
350instance needs to be passed into the heap management code as a parameter. For
351allocation API in heap management, it needs two parameters - 'size' and
352'instance', while for 'malloc' caller it needs a 'malloc' with one parameter
353'size' only. As mentioned in the upper chapter, this instance can be retrieved
354from the Secure Partition Local Storage. The implementation can be:
355
356.. code-block:: c
357
358  void *malloc(size_t sz)
359  {
360      struct p_sp_local_storage_t *m =
361          (struct p_sp_local_storage_t *)tfm_sprt_local_storage;
362
363      return tfm_sprt_alloc(m->heap_instance, sz);
364  }
365
366--------------
367
368*Copyright (c) 2019-2024, Arm Limited. All rights reserved.*
369