Summary

In this advisory we are disclosing a heap overflow vulnerability in the MediaTek baseband. The vulnerability can be exploited to gain arbitrary code execution in the context of the baseband runtime. The vulnerability was fixed in 2020 in some models, and received a CVE and more widely deployed fix in 2021.

Vulnerability Details

When processing the GSM Radio Resource Management Channel Release message, the CSN.1 decoding of the “Cell selection indicator after release of all TCH and SDCCH” information element contains a heap buffer overflow in the function FDD_csrr_decode_redirection_ie. The “Cell selection indicator after release of all TCH and SDCCH” is a type 4 information element with a minimum length of 4 octets. No upper length limit is specified except for that given by the maximum number of octets in a L3 message (see 3GPP TS 44.006). Its format is the following:

<Cell Selection Indicator after release of all TCH and SDCCH value part> ::= 
{  000 { 1 <GSM Description : <GSM Description struct >} ** 0
 | 001 { 1 <UTRAN FDD Description : < UTRAN FDD Description struct >> } ** 0
 | 010 { 1 <UTRAN TDD Description : < UTRAN TDD Description struct >> } ** 0
 | 011 { 1 <E-UTRAN Description : < E-UTRAN Description struct >> } ** 0
}; 

This means that it will use 3 bits to signal which type of repeated descriptions it will contain. As we can see at 44.018 10.5.2.1e., there is simply no explanation of what is the maximum allowed repetition count.

The Cell selection field can contain GSM, UTRAN-FDD/TDD or EUTRAN values (only one RAT per IE). The vulnerable path is the GSM one. The grammar definition of the GSM Description is:

< GSM Description struct > ::=
  < Band_Indicator : bit >
  < ARFCN : bit (10) >
  < BSIC : bit (6) > ;

The GSM handling code (function FDD_csrr_decode_redirection_ie) makes a fixed allocation of 384 bytes, which can accomodate 96 elements maximum (4byte/element: arfcn and bsic are stored on uint16_t fields). However, although the allocation is fixed, the iteration is not limited to 96 elements, thus it is possible to overwrite the allocated memory chunk.

For the representation of a single repetitive element of “GSM description”, 18 bits are needed, thus it is possible to pack more than 96 elements into a single L3 RR message, as 96 elements would only consume 216 bytes. Those circumstances lead to a heap buffer overflow.

uint FDD_csrr_decode_redirection_ie(byte **bs,short length)

{
  byte description_type_;
  ushort arfcn;
  uint uVar5;
  void *dst;
  undefined bsic;
  ushort uVar1;
  int still_has_repetition;
  uint band_indicator;
  void *pvVar2;
  int local_v0_364;
  ushort *gsm_desc_struct_iterator;
  undefined *puVar3;
  uint uVar7;
  uint gsm_desc_counter;
  undefined *puVar1;
  byte description_type;
  
  puVar1 = PTR_global_state_906094b4;
  uVar7 = *(uint *)(PTR_global_state_906094b4 + 8);
  **(short **)PTR_global_state_906094b4 = **(short **)PTR_global_state_906094b4 + -3;
                    /* RAT type */
  description_type_ = bit_stream_read(bs,3);
  *(byte *)(uVar7 + 0x8b9) = description_type_;
  dhl_brief_trace(5,0,(byte *)0xf00009e,PTR_DAT_906094b8);
  if (*(char *)(uVar7 + 0x8b9) != '\x03') {
    ppbVar6 = (byte **)FDD_rr_get_current_rr_ptr();
    mrs_set_as_reason_not_moving_to_lte((uint)ppbVar6,3);
  }
  description_type = *(byte *)(uVar7 + 0x8b9);
  if (description_type == 1) {
                    /* UTRAN FDD Description */
    if (*(int *)(uVar7 + 0x8c0) == 0) {
      pvVar2 = get_ctrl_buffer_ext(0x3c0,PTR_s_pcore/modem/gas/rrm/rmc/src/csrr_906094bc,0x136e);
      *(void **)(uVar7 + 0x8c0) = pvVar2;
    }
    __wrap_memset(*(void **)(uVar7 + 0x8c0),0,0x3c0);
    uVar5 = FDD_csrr_decode_utran_fdd_descriptions(bs,0,6,4);
  }
  else {
                    /* GSM Description */
    if (description_type == 0) {
                    /* separately handles the IEI 77 (Cell selection) length */
      if ((int)length << 3 < 0x16) {
        return 0;
      }
        /*
         * VULNERABILITY BASIS:
         *   the allocation is to a fixed size!
         */
      dst = get_ctrl_buffer_ext(0x180,PTR_s_pcore/modem/gas/rrm/rmc/src/csrr_906094bc,0x1328);

      *(void **)(uVar7 + 0x8bc) = dst;
                    /* initially allocated for 0x180 / 4 = 96 entries */
      __wrap_memset(dst,0,0x180);
      gsm_desc_struct_iterator = (ushort *)(*(int *)(uVar7 + 0x8bc) + 2);
      gsm_desc_counter = 0;
      while( true ) {
        still_has_repetition = bit_stream_read(bs,1);
        **(short **)puVar1 = **(short **)puVar1 + -1;
        
        /*
         * VULNERABILITY:
         *   the recursion only stops with the 0 bit
         *   never because it is too long!
         */
        if (still_has_repetition != 1) break;

        band_indicator = bit_stream_read(bs,1);
        arfcn = bit_stream_read(bs,10);
                    /* Write ARFCN and BSIC directly to the allocated memory */
        /*
         * MEMORY CORRUPTION HAPPENS
         */
        *gsm_desc_struct_iterator = arfcn;
        bsic = bit_stream_read(bs,6);
        *(undefined *)(gsm_desc_struct_iterator + -1) = bsic;

        **(short **)PTR_RR_GLOBAL_PTR_90601f1c = **(short **)PTR_RR_GLOBAL_PTR_90601f1c + -0x11;
        if ((band_indicator & 0xff) == 1) {
          uVar1 = FDD_rr_arfcn_PCS_band_tag((uint)*gsm_desc_struct_iterator);
          *gsm_desc_struct_iterator = uVar1;
        }
        gsm_desc_struct_iterator = gsm_desc_struct_iterator + 2;
        gsm_desc_counter = gsm_desc_counter + 1;
      }
      if (gsm_desc_counter != 0) {
        FDD_rrm_store_gsm_redirection_list(gsm_desc_counter & 0xff,0,*(void **)(uVar7 + 0x8bc),puVar3);
      }
      else {
        dhl_brief_trace(5,0,0xe60009f,0);
      }
      uVar5 = (uint)(gsm_desc_counter != 0);
                    /* free the buffer allocated in the begining of this block */
      free_ctrl_buffer_ext(*(void **)(uVar7 + 0x8bc),PTR_s_pcore/modem/gas/rrm/rmc/src/csrr_906094bc,0x1363);
      *(undefined4 *)(uVar7 + 0x8bc) = 0;
    }
    else {
      if (description_type == 3) {
                    /* E-UTRAN Description */
        uVar5 = FDD_csrr_decode_eutran_redirection_list(bs);
      }
      else {
        uVar5 = 0;
      }
    }
  }
  local_v0_364 = FDD_rr_is_GSM_ChannelLockEnable();
  if (local_v0_364 == 1) {
    uVar5 = uVar7;
  }
  return uVar5;
}

Brief Impact Analysis

To read more about the exploitation of this heap overflow, check out our companion blog post which also provides details on the reverse engineering of the MediaTek baseband firmware to find the vulnerability.

Mitigation

MediaTek security update from September 2021 provides the fixes for this issue.

Timeline

  • 2019.12.11. Vulnerability reported to Huawei PSIRT (Huawei uses Helio chipsets in some devices)
  • 2019.12.16. Huawei PSIRT sends the report to MediaTek
  • 2020.02.26. MediaTek completes analysis and provides fix for their affected devices
  • 2021.04.23. We request CVE for publication, Huawei PSIRT indicates it will be handled by MediaTek and should be requested directly
  • 2021.05.06. Vulnerability reported directly to the MediaTek product security team
  • 2021.05.19. MediaTek assigns CVE-2021-32484, embargo until August 2021 agreed
  • 2021.09.01. MediaTek releases advisory, vulnerability rated Medium/DoS
  • 2022.01.10. Blog draft shared with MediaTek and Huawei
  • 2022.01.12. MediaTek upgraded vulnerability rating to High/RCE, vendors ask for a small delay to notify customers and update advisory
  • 2022.01.21. Delay requested by MediaTek ends
  • 2022.02.28. Delay requested by Huawei ends
  • 2022.03.10. Report released publicly