CVE CVE-2021-35465 
Title
VLLDM instruction Security Vulnerability
Disclosure date

23rd August 2021

Affects
Arm Cortex-M33 r0p0 to r1p0
Arm Cortex-M35P r0
Arm Cortex-M55 r0p0 to r1p0
Arm China STAR-MC1 (STAR SE configuration)
On these processors, any Armv8-M Secure software running on that utilize FPU or Helium and call Non-secure functions might be affected.
Impacts
Secure information in floating-point or vector registers can be exposed to the Non-secure world.
CVSS Base Score
3.4 (Low) 
CVSS Vector string
CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:L/I:L/A:N
Credit
NA

Overview

On an Arm Cortex-M-based system, when the floating-point unit (FPU) or M-Profile vector extension (MVE) also known as Helium, is implemented, the processor contains thirty-two 32-bit ‘S’ registers, a floating-point status, a control register (FPSCR), and a vector predication register (VPR, only present with MVE). If the Security Extension (TrustZone) is implemented, Secure software can optionally utilize the FPU or MVE features, in this case these registers might contain Secure information.  
 
To reduce the software overhead of saving and restoring Secure context when calling a Non-secure function from the Secure world and prevent the inadvertent creation of a floating-point context, the Armv8-M architecture supports a pair of instructions called VLSTM and VLLDM. These instructions allow the Secure information to be saved and restored automatically only if the Non-secure function being called and also utilized by FPU or Helium feature. The insertion of VLSTM and VLLDM is normally handled by C/C++ compilers. 
 
Recently a vulnerability issue is found in the implementation of VLLDM instruction in the Arm Cortex-M33, Cortex-M35P, Cortex-M55, and Arm China STAR-MC1(STAR SE configuration) processors. If the VLLDM instruction is abandoned due to an exception (for example an interrupt) when it is partially completed, it is possible for subsequent Non-secure handler (which may be a tail chained handler from the original exception) to access and modify the partial restored register values. This vulnerability is identified as CVE-2021-35465. Software workaround details added in C/C++ compilers are provided in this document to mitigate this vulnerability issue.


Background

On an Armv8-M-based system with Security Extension implemented, on transition from Secure state to Non-secure state through BLXNS instruction, then the code sequence preceding BLXNS instructions must:

  • Save active non-banked registers by copying them to Secure memory (excluding arguments passing from Secure caller to a Non-secure function).
  • Clear all non-banked registers except:
    • The Link Register (LR)
    • The registers that hold the arguments of the call
    • Registers that do not hold secret information
  • Save and clear all contents in the Floating-point or MVE registers (S0-S31, FPSCR, and VPR), excluding Non-secure function arguments, if they contain Secure data.
  • The branch target address must have the LSB set to 0 and reside in Non-secure memory.

If the Secure software uses the FPU or MVE feature, before transitioning to Non-secure state, it is required that all the Floating-point or MVE registers (S0-S31, FPSCR, and VPR) are protected from the Non-secure function being called (for example by saving the data to secure memory and clearing the registers). Post completing the Non-secure function, on return to Secure state, the data that was in the Floating-point or MVE registers must be unprotected and made available to the secure code (for example by restoring the data from secure memory). To improve the process of protecting and unprotecting Floating-point or MVE registers the VLLDM and VLSTM instructions were introduced. These instructions save and clear the registers only when Floating-point or vector registers are used in Non-secure state.

On Armv8-M-based system when Floating point or MVE and Security Extensions are implemented, and if the Secure software used the FPU or MVE for Secure data processing, then the general programming practice followed is to: 

  • Use VLSTM instruction for code sequence just before transitioning to Non-secure code 
  • Use VLLDM instruction to restore the contents back in Secure code

If any of the Secure software uses FPU or MVE registers and calls a Non-secure function, the VLSTM and VLLDM instruction pair should be used for:

  • Compilations for software components without FPU or MVE usage. It is possible that such software components could link with other software that use FPU or MVE, and so the FP or vector registers could contain Secure information.
    Note: This does not prevent the software component being used on a processor without an FPU or MVE, because the VLSTM and VLLDM instructions behave as a NOP if the FPU or MVE is not present.
  • Compilations with soft-fp ABI (Application Binary Interface)
  • Compilations with hard-fp ABI, and the function call does not have FP or vector arguments or results.

An exception to this arrangement is when hard FP ABI is used, and the function call uses FPU or vector registers for function call argument or result passing.  


Vulnerability issue

When a VLLDM instruction restores the contents of Secure Floating-point registers that were protected by a VLSTM instruction, the VLLDM instructions mark the Floating-point context as active (through CONTROL.FPCA bit) only after restoring all the Secure Floating-point registers contents. The Secure Floating-point context includes FPSCR, VPR, and Floating-point ‘S’ register contents. Now, if a VLLDM instruction has started to restore the Secure data and it is abandoned due to an exception (for example an interrupt) mid-way, the partially restored Secure context will be present in Floating-point registers, when the context is not yet marked active by CONTROL.FPCA bit. Therefore, this effectively means that part of the Secure Floating-point or vector context can be exposed to, and potentially be modified by a subsequent Non-secure handler, resulting in a security vulnerability. 


Products affected

  • Armv8-M architecture
  • Processor hardware: Arm Cortex-M33, Cortex-M55, Cortex-M35P processors. Arm China STAR-MC1 processor (STAR SE configuration) 
  • Software toolchain: Arm Compiler 6 (and LLVM codebase), GCC embedded toolchain, Fast Model, Cycle Models

Products not affected

  • Example code in DS-5/Arm Development Studio
  • CMSIS Packs
  • System that does not use Armv8-M TrustZone technology
  • Secure software that does not use FPU/MVE technology
  • Secure software that does not call Non-secure functions
  • Secure software that only stores Non-secure values in the Floating-point or MVE registers (S0-S31, FPSCR, and VPR)

Mitigation

To mitigate this security vulnerability, you are required to recompile the Secure image with C/C++ compilers that has the software workaround implemented. In the updated C/C++ compilers, instead of executing just the VLLDM instruction after the Non-secure function call, the mitigation code sequence given in Table 1 should be used:

Table 1 - Mitigation Code Sequence

Architecture
Code sequence after the Non-secure function call
armv8-m.main
MRS        R5, CONTROL
TST        R5, #8   /* CONTROL_S.SFPA */
IT         NE
VMOVNE S0, S0       /* Note 1 */
VLLDM      SP       /* Lazy restore of d0-d15 and FPSCR. */
armv8.1-m.main
vscclrm   {VPR}
vlldm

Note 1: If the toolchain environment does not permit the use of “vmovne  s0, s0” in a software-float only environment, it might be necessary to insert the binary value for the instruction (0xeeb00a40) instead. In GNU assembly syntax this can be written as:

    .inst.w    0xeeb00a40

The instruction sequences for the fix for this security issue have also been chosen so they are safe regardless of whether the FPU and MVE are implemented.

The example codes for calling a Non-secure function with the software workaround are given in Table 2 and Table 3, with the workaround code sequence highlighted in bold.

Table 2 - Cortex-M55 (Workaround Code Sequence)

Cortex-M55 (Example workaround code sequence)

-mcpu=cortex-m55

     2fc:       e92d 0ff0       stmdb   sp!,{r4,r5,r6,r7,r8,r9,sl,fp}
     300:       b0a2            sub     sp,#136;0x88
     302:       ec2d 0a00       vlstm   sp
     306:       e89f 9ff7       clrm    {r0,r1,r2,r4,r5,r6,r7,r8,r9,sl,fp,ip,APSR}
     30a:       479c            blxns   r3
     30c:       ec9f 0b00       vscclrm {VPR}
     310:       ec3d 0a00       vlldm   sp
     314:       b022            add     sp,#136;0x88
     316:       e8bd 0ff0       ldmia.w sp!,{r4,r5,r6,r7,r8,r9,sl,fp}
     31a:       bf00            nop
     31c:       b003            add     sp,#12  
     31e:       f85d fb04       ldr.w   pc,[sp],#4

Table 3 - Cortex-M33, Cortex-M35P, STAR (Workaround Code Sequence)

Cortex-M33, Cortex-M35P, STAR (Example workaround code sequence)

-mcpu=cortex-m33

00002680 <__gnu_cmse_nonsecure_call>:

    2680:       e92d 4fe0       stmdb   sp!,{r5,r6,r7,r8,r9,sl,fp,lr}
    2684:       4627            mov     r7,r4
    2686:       46a0            mov     r8,r4
    2688:       46a1            mov     r9,r4
    268a:       46a2            mov     sl,r4
    268c:       46a3            mov     fp,r4
    268e:       46a4            mov     ip,r4
    2690:       b0a2            sub     sp,#136;0x88
    2692:       ec2d 0a00       vlstm   sp
    2696:       f384 8800       msr     CPSR_f,r4
    269a:       4625            mov     r5,r4
    269c:       4626            mov     r6,r4
    269e:       47a4            blxns   r4
    26a0:       f3ef 8514       mrs     r5,CONTROL
    26a4:       f015 0f08       tst.w   r5,#8
    26a8:       bf18            it      ne
    26aa:       eeb0 0a40       vmovne.f32 s0,s0
    26ae:       ec3d 0a00       vlldm   sp
    26b2:       b022            add     sp,#136;0x88
    26b4:       e8bd 8fe0       ldmia.w sp!,{r5,r6,r7,r8,r9,sl,fp,pc}

In the updated versions of Arm Compiler 6 and GCC, if your program is compiled with a command-line option -march=armv8-m.main, then the workaround code sequence is enabled by default and the code sequence is inbuilt within the support code of the compiler library.

In the updated versions of Arm Compiler 6 and GCC, if your program is compiled with a command-line option -mcpu=cortex-m55, then the workaround code sequence for Armv8.1-M will be enabled by default and this code sequence will be inbuilt within the support code of the compiler library. However, if your program is compiled with command-line option -march=armv8-1m.main, then the option -mfix-cmse-cve-2021-35465 should be added manually in your command-line option.

If software is being developed solely for use on devices where the erratum has been fixed in hardware, then the option -mno-fix-cmse-cve-2021-35465 can be applied disable generation of the mitigation sequence. Note that this might not be possible in situations where the mitigation is built into the support libraries of the compiler.