Last edited 3 years ago

How to protect the coprocessor firmware

1. Article purpose[edit source]

This article explains how to protect the software loaded by the main processor into the coprocessor, and ensure the authentication of the loaded firmware.

2. Introduction[edit source]

The Linux® OS, through the remoteproc framework, enables the loading of firmware into control remote processors. Thanks to a specific OP-TEE trusted application (TA) running on the Arm® Trustzone and to the ETZPC peripheral, it is possible to authenticate a Cortex®-M4 firmware and install it on isolated RAMs to ensure its integrity during the execution .

From user's point of view the management of a non-authenticated or authenticated firmware does not differ, whether the firmware is managed by the Linux or by the U-Boot. The difference is that the management of the authenticated firmware is delegated to the OP-TEE firmware running in the Arm® Trustzone.

3. Principles[edit source]

The protection of the coprocessor firmware relies on hash computation and on the encryption key to authenticate a firmware, as does the isolation of the execution memories to ensure execution integrity.

  • The firmware is signed using a OP-TEE signature script. This script adds to the ELF binary a header that contains elements for the authentication among other firmware information:
  • a hash table, which contains the hash of the segment to load
  • a signature computed with the private key.
  • During the execution phase:
  • the firmware is read from the file system by the remoteproc framework and the image is provided to the OP-TEE trusted application.
  • The firmware is authenticated and installed by the OP-TEE trusted application in the protected Cortex-M memories.

Authenticate rproc fw.png

3.1. Firmware signature[edit source]

A common strategy to sign a firmware consists in adding a header that contains the signature required for authentication. The signature is generated by computing a hash on the whole binary including the header, and encrypting this hash using a private key.
On the STM32MP1 microprocessor, the signature procedure is similar except that it is optimized to minimize the use of memory and speed up the authentication process. The principle is to compute the hash of the segments to load, instead of computing the hash of the full image.


STM32CubeMP1 firmware generationKeys generationSign the STM32CubeMP1_firmwarebuild OPTEE with public KeySignature procedure
  • The signature consists in:
  1. parsing the program table in the ELF file to identify the segments to load.
  2. computing the hash of each segment and save the result in the hash table chunk of the header.
  3. computing the hash of the header and sign the hash with the private key.
  • The public key is embedded in the OPTEE-OS firmware that is in charge of the Cortex-M4 firmware authentication.

3.2. Memory management[edit source]

The MCU SRAM and the RETRAM memories can be dedicated to Cortex-M usage (isolated), shared with the Cortex-A non-secure context, or protected for Cortex-A secure access only.
The ETZPC mechanism is used:

  • during the load and authentication phase: to lock the memories for Cortex-A secure access only,
  • during the execution phase: to isolate the memories for MCU usage (such as code execution and data) or share them with the Cortex-A non-secure context (such as RPMsg shared buffers).
Info white.png Information
When creating a Cortex-M firmware build, developers must be careful to pack in a same memory bank all the data shared with the Cortex-A. This enables the isolation of the rest of the memory banks for the Cortex-M context.

The information provided in this article respect the mapping described in memory mapping section:

  • RETRAM, MCU SRAM1 and MCU SRAM2 are used for code execution and data. They are only accessible by the Cortex-A secure context when no firmware is running, and assigned to Cortex-M context during authenticated firmware execution.
  • MCU SRAM3 (IPC buffers) is shared between the Cortex-A non-secure context and the Cortex-M. No protection is applied to this memory bank.
  • MCU SRAM4 is reserved for the Cortex-A non-secure context. It is thus free for other usage.

This mapping can be customized depending on project requirements. In this case, the code has to be updated in a synchronized manner in STM32CubeMP1 firmware, Linux Kernel device-tree and the OP-TEE rproc PTA code.

3.3. Authentication[edit source]

The authentication is executed in the secure context by an OP-TEE trusted application.

Authentication Fw load phase1.png

1. The firmware is copied by the Linux Kernel (or U-boot) to non-secure DDR memory.
2. The firmware header is copied to a secure memory to ensure its integrity during the authentication steps.
3. The header hash is computed.
4. The signature is decrypted using the public key. The result is the hash of the header computed by the signing tools. Both hashes are compared to authenticate the firmware header.
5. At this step the firmware header is valid. The next step, the loading of the firmware in MCU memories, can start:

Authentication Fw load phase2.png

For each segment to load:
6. The segment is copied to the MCU RAMs that can be accessed only by the secure context to ensure their integrity.
7. The hash of the copied segment is computed and compared to the hash stored in the firmware header.
8. At this step all segments have been copied and authenticated. The firmware is loaded and read for execution.

3.4. Authenticated firmware live-cycle[edit source]

Authentication software overview.png

From user's point of view, the management of the firmware live-cycle does not change. It is described in the remoteproc article.

The main difference compared to a non-authenticated firmware is that the remote processor framework delegates the coprocessor management to the OP-TEE.

Info white.png Information
The authenticated firmware can also be pre-loaded and started by the U-boot.

3.4.1. Firmware loading and authentication[edit source]

  • As for a non authenticated firmware, the firmware is read from the file system by the remoteproc framework. The image is then sent to the OP-TTE trusted application.
  • The authentication and the MCU memory isolation are managed in the secure context by the OP-TEE trusted application.

3.4.2. Firmware start and stop[edit source]

  • As for a non-authenticated firmware, the firmware start and stop requests are handled by the remoteproc framework, but they are delegated to the Arm® Trustzone.
  • The firmware start and stop are executed by the OP-TEE. The reset and RAM access rights are only accessible by the secure context.
  • Before starting the firmware, the MCU RAM access rights are updated to isolate execution RAMs for the Cortex-M.
  • After stopping the firmware, the MCU RAMs are cleaned and access rights are updated to Cortex-A secure context.

3.4.3. RPMsg management[edit source]

As the resource tables and RPMsg vrings and buffers are in a MCU shared memory and accessible by both Cortexes, it is possible to communicate with the authenticated firmware.

3.4.4. Firmware core dump[edit source]

Since the memories are protected, it is not possible to dump the firmware when a crash occurs.

4. Implementation[edit source]

The authentication mechanisms require STM32MPU Embedded Software distribution with OP-TEE running in the Arm® Trustzone context.

4.1. Updating the built image to enable the authentication feature[edit source]

4.1.1. OP-TEE image installation[edit source]

Refer to How to populate and boot a board with OP-TEE for details.

4.1.2. OP-TEE remoteproc TA generation[edit source]

First, install the OP-TEE OS build environment.

The OPTEE OS firmware has to be generated again to include (see here for how to information):

  • the remote processor trusted application (TA),
  • the remote processor platform pseudo trusted application (PTA),
  • the public key.
4.1.2.1. Configuration[edit source]

Enable the CFG_RPROC_PTA in core/arch/arm/plat-stm32mp1/conf.mk

CFG_RPROC_PTA ?= y
4.1.2.2. Building the OP-TEE OS[edit source]

Below additional directives to build OP-TEE OS:

  • CFG_IN_TREE_EARLY_TAS=remoteproc/80a4c275-0a47-4905-8285-1486a9771a08 : adds remoteproc early TA.
  • CFG_RPROC_SIGN_KEY= <key file path name>: specifies the public key file to authenticate the remote processor firmware. This directive is optional. If it is not defined, the keys/default_rproc.pem default key is used.

Example of build command (<other directives> has to be replaced by usual directives to build OP-TEE OS):

  • Use the default key available in the OP-TEE repository to build the OP-TEE OS (that can be updated): keys/default_rproc.pem
 make <other directives> CFG_IN_TREE_EARLY_TAS=remoteproc/80a4c275-0a47-4905-8285-1486a9771a08
  • Or specify the key to use by defining CFG_RPROC_SIGN_KEY:
 make <other directives> CFG_IN_TREE_EARLY_TAS=remoteproc/80a4c275-0a47-4905-8285-1486a9771a08 CFG_RPROC_SIGN_KEY=my_public_key.pem
Info white.png Information
The remoteproc early trusted application and the public key are part of the OP-TEE OS binary.
4.1.2.3. Updating the software on board[edit source]

Refer to update OP-TEE software on board for details.

4.1.3. Updating the Linux kernel[edit source]

4.1.3.1. Kernel configuration[edit source]

Activate the remoteproc tee in the kernel configuration, using the Linux Menuconfig tool: Menuconfig or how to configure kernel.

Device drivers --->
   Remoteproc drivers --->
     <*> Support for Remote Processor subsystem
     <*> STM32 remoteproc support
     <*> Trusted firmware support by a trusted application 
4.1.3.2. Device tree configuration[edit source]

To be compliant with the memory mapping and protection, the memory region properties described in the Linux remoteproc framework overview have to be updated.
Example:

  • Declare a memory region for the resource table and remoteproc trace memory region. The address and size must be aligned with STM32Cube linker script m_rsc_table definition:
reserved-memory {
 	...
	mcu_rsc_table: mcu_rsc_table@10048000 {
		compatible = "shared-dma-pool";
		reg = <0x10048000 0x8000>;
		no-map;
	};
};

Info white.png Information
The U-Boot limits the number of memory regions that can be reserved. Adding this region can lead to a U-boot crash. A simple way to solve the issue is to increase the value of the MAX_LMB_REGIONS defined in include/lmb.h .
  • In the remoteproc DT node:
  • Clean up retram, mcusram and mcusram2 regions that are now handled by OP-TEE.
  • Add the mcu_rsc_table region.
  • Update the compatibility field.
 /* stm32 M4 remoteproc device */
 m4_rproc: m4@0 {
	...
       compatible = "st,stm32mp1-m4_optee";
       memory-region = <&vdev0vring0>, <&vdev0vring1>, <&vdev0buffer>, <&mcu_rsc_table>;
	...
};
Info white.png Information
The firmware memory mapping, defined in the STM32Cube firmware linker script, must be set according to the values defined in the STM32Cube firmware linker script.
Info white.png Information
If the coprocessor firmware does not embed any resource table, the mcu_rsc_table memory region does not need to be declared

4.1.4. U-Boot update[edit source]

This chapter is relevant only for coprocessor firmware preloaded by the bootloader.

To enable the management of a preloaded authenticated firmware, the compatible field of the remote proc node has to be updated in the device tree configuration. In arch/arm/dts/stm32mp151.dtsi :

m4_rproc: m4@10000000 {
      compatible = "st,stm32mp1-m4_optee";
};

4.2. STM32CubeMP1 firmware generation[edit source]

Most of the STM32CubeMP1 firmware can be isolated. A developer has to pay attention to the memories shared with the Cortex-A7 non-secure context:

  • the resource table,
  • the log buffer shared through the resource table,
  • the shared buffers for RPMsg communication,
  • other shared buffers defined by the developer for its project.

4.2.1. Resource table[edit source]

In the STM32CubeMP1 examples, the resource table is linked by default to other firmware data. A specific memory region has to be defined in the linker script to isolate it in a new region. In the following example, a new memory region is created at the end of the MCU_SRAM3 to host for the resource table:

/* Memory definition */
MEMORY
{
 m_interrupts (RX)  : ORIGIN = 0x00000000, LENGTH = 0x00000298
 m_text       (RX)  : ORIGIN = 0x10000000, LENGTH = 0x00020000
 m_data       (RW)  : ORIGIN = 0x10020000, LENGTH = 0x00020000
 m_ipc_shm    (RW)  : ORIGIN = 0x10040000, LENGTH = 0x00008000
 m_rsc_table  (RW)  : ORIGIN = 0x10048000, LENGTH = 0x00008000
}

The resource table and log buffer are then mapped in this region:

.resource_table :
{
  . = ALIGN(4);
  KEEP (*(.resource_table*))
  . = ALIGN(4);
} > m_rsc_table

4.2.2. Log buffer[edit source]

The log buffer can be enabled in the resource table to get Cortex-M log from the Cortex-A non-secure context. To define it, proceed as follows:

  • Add an attribute to the system_log_buf buffer:
#if defined(__GNUC__) 
#define __section_t(S)          __attribute__((__section__(#S)))
#define __resource              __section_t(.sys_logs)
#endif

char  __resource __attribute__((used)) system_log_buf[SYSTEM_TRACE_BUF_SZ];
  • Update the linker script to add the trace buffer in the m_rsc_table memory region:
.sys_logs :
{
  . = ALIGN(4);
  KEEP (*(.sys_logs*))
  . = ALIGN(4);
} > m_rsc_table

4.2.3. RPMsg shared buffers[edit source]

The m_ipc_shm memory region is already reserved for the RPMsg Virtio rings and buffers.

4.3. Cortex-M4 firmware signature[edit source]

4.3.1. Generate private and public keys[edit source]

An RSA key has to be generated to sign and authenticate the Cortex-M firmware. For instance, the ssh-keygen command can be used.

 ssh-keygen

Two files are created:

  • id_rsa: the private key to sign the firmware.
  • id_rsa.pub: the public key to authenticate the firmware.

4.3.2. Firmware signature[edit source]

The OP-TEE ST-Microelectronics distribution integrates the sign_rproc_fw.py that can sign the firmware binary ELF file.
This script

  • adds a header on top of the ELF binary,
  • computes the hash of the program segments that is loaded in the Cortex-M memories and fill the hash table included in the header,
  • computes the hash of the header and signs it using the private key.

To sign the firmware:

 ./scripts/sign_rproc_fw.py sign --in <firmware name>
  • or specify the key to use as parameter:
 ./scripts/sign_rproc_fw.py sign --in <firmware name> --key <id_rsa private key>
  • For more script options:
 ./scripts/sign_rproc_fw.py sign --help

The signed firmware is available in the ELF file directory with a .sign extension.

5. Code source[edit source]

5.1. OP-TEE[edit source]

 ta/remoteproc/remoteproc_core.c  
 core/arch/arm/plat-stm32mp1/remoteproc_pta.c  

5.2. Linux Kernel[edit source]

drivers/remoteproc/tee_remoteproc.c  
drivers/remoteproc/stm32_rproc.c  

5.3. U-boot[edit source]

drivers/remoteproc/rproc-optee.c