Summary

There is a vulnerability in the Huawei Kirin SoC’s DDR Controller (DMSS) Access Permission system which allows the baseband to bypass the Baseband’s MPU memory protections and circumvent RO and NX protections. The vulnerability was fixed in February 2022.

Vulnerability Details

CVE-2021-22430 is a vulnerability in the Huawei Kirin SoC’s basebands which allowed to circumvent MPU restrictions. The vulnerability in CVE-2021-22430 was that MPU configuration was restored from a writable table for sleep cycles and therefore overwriting the cached entries resulted in new settings taking effect. This worked because the implementation normally only wrote the table once (not every time the core went to sleep) but restored the MPU configuration from it every time it was woken up.

The fix for CVE-2021-22430 split the saved MPU configuration into two parts.

There is a read-only config table, which is used by the restore_mpu_cfg function. This function is called after each sleep cycle, so if a modification succeeds, the MPU configuration is persistenly altered.

restore_mpu_cfg
     20025558   17 00 a0 e3    mov       r0,#0x17
     2002555c   7c 10 9f e5    ldr       r1,[->mpu_config_read_only]                 = 21ece680
     20025560   00 10 91 e5    ldr       r1,[r1,#0x0]=>mpu_config_read_only
 LAB_20025564                  XREF[1]:   200255ac(j)  
     20025564   fc 00 31 e9    ldmdb     r1!,{ r2 r3 r4 r5 r6 r7 }
     20025568   12 0f 06 ee    mcr       p15,0x0,r0,cr6,cr2,0x0
     2002556c   6f f0 7f f5    isb       SY
     20025570   11 2f 06 ee    mcr       p15,0x0,r2,cr6,cr1,0x0
     20025574   6f f0 7f f5    isb       SY
     20025578   31 3f 06 ee    mcr       p15,0x0,r3,cr6,cr1,0x1
     2002557c   6f f0 7f f5    isb       SY
     20025580   51 4f 06 ee    mcr       p15,0x0,r4,cr6,cr1,0x2
     20025584   6f f0 7f f5    isb       SY
     20025588   71 5f 06 ee    mcr       p15,0x0,r5,cr6,cr1,0x3
     2002558c   6f f0 7f f5    isb       SY
     20025590   91 6f 06 ee    mcr       p15,0x0,r6,cr6,cr1,0x4
     20025594   6f f0 7f f5    isb       SY
     20025598   b1 7f 06 ee    mcr       p15,0x0,r7,cr6,cr1,0x5
     2002559c   6f f0 7f f5    isb       SY
     200255a0   4f f0 7f f5    dsb       SY
     200255a4   01 00 50 e2    subs      r0,r0,#0x1
     200255a8   00 00 50 e3    cmp       r0,#0x0
     200255ac   ec ff ff aa    bge       LAB_20025564
     200255b0   0e f0 a0 e1    mov       pc,lr

On the other hand, the save_mpu_cfg function now works on a writable config table.

 save_mpu_cfg
     200254e0   ff 5f 2d e9    stmdb     sp!,{r0 r1 r2 r3 r4 r5 r6 r7 r8 r9 r10 r11
     200254e4   00 00 a0 e3    mov       r0,#0x0
     200254e8   ec 90 9f e5    ldr       r9,[->mpu_config_writeable]                 = 2276e444
     200254ec   ec 80 9f e5    ldr       r8,[->mpu_config_read_only]                 = 21ece680
     200254f0   09 10 a0 e1    cpy       r1,r9
     200254f4   04 10 81 e2    add       r1,r1,#0x4
 LAB_200254f8                  XREF[1]:   20025540(j)  
     200254f8   12 0f 06 ee    mcr       p15,0x0,r0,cr6,cr2,0x0
     200254fc   6f f0 7f f5    isb       SY
     20025500   11 2f 16 ee    mrc       p15,0x0,r2,cr6,cr1,0x0
     20025504   6f f0 7f f5    isb       SY
     20025508   31 3f 16 ee    mrc       p15,0x0,r3,cr6,cr1,0x1
     2002550c   6f f0 7f f5    isb       SY
     20025510   51 4f 16 ee    mrc       p15,0x0,r4,cr6,cr1,0x2
     20025514   6f f0 7f f5    isb       SY
     20025518   71 5f 16 ee    mrc       p15,0x0,r5,cr6,cr1,0x3
     2002551c   6f f0 7f f5    isb       SY
     20025520   91 6f 16 ee    mrc       p15,0x0,r6,cr6,cr1,0x4
     20025524   6f f0 7f f5    isb       SY
     20025528   b1 7f 16 ee    mrc       p15,0x0,r7,cr6,cr1,0x5
     2002552c   6f f0 7f f5    isb       SY
     20025530   4f f0 7f f5    dsb       SY
     20025534   fc 00 a1 e8    stmia     r1!,{ r2 r3 r4 r5 r6 r7 }=>mpu_config       = null
     20025538   01 00 80 e2    add       r0,r0,#0x1
     2002553c   17 00 50 e3    cmp       r0,#0x17
     20025540   ec ff ff 9a    bls       LAB_200254f8
     20025544   09 10 41 e0    sub       r1,r1,r9
     20025548   08 10 81 e0    add       r1,r1,r8
     2002554c   00 10 89 e5    str       r1=>DAT_21ece69c,[r9,#0x0]=>mpu_config_wriable
     20025550   ff 5f bd e8    ldmia     sp!,{r0 r1 r2 r3 r4 r5 r6 r7 r8 r9 r10 r11
     20025554   0e f0 a0 e1    mov       pc,lr

Finally, the fix added a new function, probably called mpu_bak_init, which interestingly handles the synchronization between the two tables.

undefined4 mpu_bak_init(void) {
  uint chan_id;
  int iVar1;
  int iVar2;
  undefined4 uVar3;
  
  chan_id = bsp_edma_channel_init(0x16,0,0);
  if (-1 < (int)chan_id) {
    iVar1 = bsp_edma_channel_set_config(chan_id,3,3,0xf);
    iVar2 = edma_channel_async_start(
      chan_id,
      mpu_config_writeable, // src
      mpu_config_read_only, // dst
      600                   // size
    );
    if (iVar1 + iVar2 == 0) {
      return 0;
    }
  }
  bsp_print(0x40,2);
  uVar3 = FUN_200379c4();
  system_error(0x1ffe,0,uVar3,0,0);
  return 0xffffffff;
}

The synchronization happens from the writable into the read-only table. So at first it is counter-intuitive how that is possible. In terms of intended functionality, the new implementation behaves correctly, as the final MPU configuration generated by the initialization functions ends up in the writable table, then it is synchronized into the read-only table, and after that the wake up restores from the read-only table alone, meaning that (wild) writes directly into the writable table no longer result in MPU reconfiguration.

The actual synchronization is done via the modem’s DMA controller, which is referred to as EDMA in strings taken from the published kernel sources and the modem log messages. Because the employed ARM Cortex-R8 is in PMSA configuration (that’s why there is an MPU and not an MMU), the MPU settings are only enforced by the ARM core to itself. So that’s how writing the read-only memory is possible, because the DMA is not affected by the MPU rules, i.e. an EDMA transaction can still write into the memory that to the modem runtime itself is read-only because of the MPU configuration.

However, the problem is that programming the EDMA to execute transaction is also feasible with write only, no code execution required. This defeats the purpose of the MPU because it once again allows an attacker with a write primitive to remove the MPU protections. In other words, The EDMA can be utilized as a way to write read-only data, without code execution in the context of the modem.

In effect, we have evaded the intended fix and recreated the same vulnerability primitive as CVE-2021-22430.

Affected Devices (Verified)

  • Kirin 990

    • Huawei Mate 30 Pro (LIO)
    • Huawei P40 Pro (ELS)
    • Huawei P40 (ANA)
  • Kirin 9000

    • Huawei Mate40 Pro (NOH)

Fix

Huawei OTA images, released after February 2022, contain the fix for the vulnerability.

Timeline

  • 2021.08.05. Bug reported to Huawei PSIRT
  • 2021.09.08. Huawei PSIRT confirms vulnerability, does not provide severity rating
  • 2021.09.21. Additional reporting to Huawei PSIRT shows Kirin 9000 is vulnerable
  • 2021.10.19. Update Requested
  • 2021.10.20. Huawei confirms final assessment and High severity rating
  • 2022.02.03. Huawei promises a later response
  • 2022.02.25. Huawei confirms CVE released in security bulletin, confirms disclosure allowed in May