We have identified a new out-of-bound read vulnerability in Mediatek’s Linux Kernel driver implementation of cellular-to-application processor communication interface (CCCI). The vulnerability can be exploited by a malicious (compromised) baseband runtime to leak information from the kernel runtime and break the kernel’s entropy-based mitigations such as KASLR and stack smashing protection.

The vulnerability we are disclosing in this advisory affected a wide range of Mediatek devices, including phones on the newest chipsets (Dimensity 700, 1000, etc). The July 2022 issue of the Mediatek Security Bulletin contains this vulnerability as CVE-2022-21769.

Vulnerability Details

There is a vmalloc out-of-bound read vulnerability in the kernel implementation of the modem-kernel communication interface. The out-of-bound read can be used to disclose sensitive kernel data from the modem.

The modem and the kernel exchange messages through a series of ring-buffers that reside in a shared memory region. The meta data of these ring-buffers are also stored in-line within the shared memory, thus both the kernel and modem can freely modify them. The vulnerability is the result of the kernel inherently trusting these values and failing to verify them.

When the kernel receives a message from the modem, ultimately the ccci_ringbuf_read() function is called (defined in drivers/misc/mediatek/eccci/hif/ccci_ringbuf.c) to read the content of the message from the shared memory. The offsets and length fields are taken from the ring-buffer header (struct ccci_ringbuf), which is also stored in the shared memory, and they are used without further verification.

The message is read from the ringbuf->buffer + ringbuf->rx_control.read address, where the buffer contains the kernel virtual address of the shared memory buffer while the rx-read field can be an arbitrary 32 bit value, controlled by the modem. The rx-read value is only checked against the rx-length field which is also read from the shared memory. The extract of the vulnerable function is presented below.

int ccci_ringbuf_read(int md_id, struct ccci_ringbuf *ringbuf,
  unsigned char *buf, int read_size)
{
  unsigned int read, write, length;

  if (ringbuf == NULL || read_size == 0 || buf == NULL)
    return -CCCI_RINGBUF_PARAM_ERR;

  // [0] Offsets and lengths are retrieved from SHMEM
  read = (unsigned int)(ringbuf->rx_control.read);
  write = (unsigned int)(ringbuf->rx_control.write);
  length = (unsigned int)(ringbuf->rx_control.length);
  /* skip header */
  read += CCIF_HEADER_LEN;

  // [1] The length field is also controlled
  if (read >= length)
    read -= length;
  // [2] The message is read from a controlled offset outside of the shared memory buffer
  CCIF_RBF_READ(ringbuf->buffer, buf, read_size, read, length);

  return read_size;
}

#define CCIF_RBF_READ(bufaddr, output_addr, read_size, read_pos, buflen)\
do {\
  // [3] Since both buflen and read_pos is controlled always the first branch would execute
  if (read_pos + read_size < buflen) {\
    rbf_memcpy((unsigned char *)output_addr,\
      (unsigned char *)(bufaddr) + read_pos, read_size);\
  } else {\
    rbf_memcpy((unsigned char *)output_addr,\
    (unsigned char *)(bufaddr) + read_pos, buflen - read_pos);\
    output_addr = (unsigned char *)output_addr + buflen - read_pos;\
    rbf_memcpy((unsigned char *)output_addr, \
      (unsigned char *)(bufaddr),\
      read_size - (buflen - read_pos));\
  } \
} while (0)

As a result a compromised modem can force the kernel to read and interpret a CCCI message outside of the original shared memory region. The buffer pointer, points into the md-ap shared memory, which is iomapped into the kernel address space. Iomap allocates kernel virtual addresses from the vmalloc address space, just like vmap and vmalloc family functions. The vmalloc allocations are not affected by KASLR, their address only depends the order of allocations. The shared memory is mapped during kernel boot, when the allocations are still deterministic, as a result it has a fixed kernel virtual address. The maximum offset that can be addressed is UINT_MAX distance, from the ringbuf->buffer kernel virtual address. This is sufficient to cover the entire vmalloc address space that is beyond the shared memory mapping.

Triggering this vulnerability is not as straight forward as on the write side. When the kernel receives a CCCI message it calls ccif_rx_collect() to read it from shared memory and translate it to an SKB. Before calling the vulnerable ccci_ringbuf_read() function, to retrieve the actual message data, ccif_rx_collect() first executes ccci_ringbuf_readable(), in order to establish the message size. The ccci_ringbuf_readable() function checks the message header and footer and, if they are both valid, extracts the message length. Both the ccci_ringbuf_readable() and ccci_ringbuf_read() retrieves the rx-read and rx-length values directly from shared memory, thus they can be changed between the calls. It is possible to create a scenario where the ccci_ringbuf_readable() functions parses the original, well-formed message in the shared memory, however ccci_ringbuf_read() sees the corrupted rx-read field, and reads the message data from an out-of-bound location.

Affected Devices

All Mediatek chipsets containing the CCCI kernel driver, namely: MT6580, MT6735, MT6737, MT6739, MT6753, MT6761, MT6765, MT6768, MT6771, MT6779, MT6781, MT6785, MT6833, MT6853, MT6873, MT6877, MT6879, MT6883, MT6885, MT6889, MT6893, MT6895, MT6983, MT8321, MT8666, MT8667, MT8675, MT8765, MT8766, MT8768, MT8786, MT8788, MT8789, MT8791, MT8797

Fix

Mediatek OTA images, released after July 2022, contain the fix for the vulnerability.

Timeline

  • 2022.01.31. Bug reported to Mediatek PSIRT
  • 2022.04.12. Mediatek confirms vulnerability
  • 2022.05.03. Mediatek confirms CVE
  • 2022.07.04. Mediatek releases security bulletin
  • 2023.08.26. TASZK informs Mediatek of advisory disclosure plan
  • 2023.08.26. Mediatek requests two more months of delay
  • 2023.11.03. Vulnerability released at Hardwear.io
  • 2023.11.26. Advisory release