Looking for vulnerabilities in MediaTek audio DSP

November 24, 2021

Research By: Slava Makkaveev

Introduction

Taiwan’s MediaTek has been the global smartphone chip leader since Q3 2020. MediaTek Systems on a chip (SoCs) are embedded in approximately 37% of all smartphones and IoT devices in the world, including high-end phones from Xiaomi, Oppo, Realme, Vivo and more.

Modern MediaTek SoCs, including the latest Dimensity series, contain a special AI processing unit (APU) and audio Digital signal processor (DSP) to improve media performance and reduce CPU usage. Both the APU and the audio DSP have custom Tensilica Xtensa microprocessor architecture. The Tensilica processor platform allows chip manufacturers to extend the base Xtensa instruction set with custom instructions to optimize particular algorithms and prevent them from being copied. This fact makes MediaTek DSP a unique and challenging target for security research.

In this study, we reverse-engineered the MediaTek audio DSP firmware despite the unique opcodes and processor registers, and discovered several vulnerabilities that are accessible from the Android user space.

By chaining with vulnerabilities in Original equipment manufacturer (OEM) partner’s libraries, the MediaTek security issues we found could lead to local privilege escalation from an Android application. A successful exploitation of the DSP vulnerabilities could potentially allow an attacker to listen to user conversations and/or hide malicious code.

From Android to the audio DSP

The goal of our research is to find a way to attack the audio DSP from Android. First, we need to understand how Android running on the Application processor (AP) communicates with the audio processor. Obviously, there must be a driver that waits for requests from the Android user space, and then, using some kind of Inter-processor communication (IPC), forwards these requests to the DSP for processing.

We used a rooted Xiaomi Redmi Note 9 5G smartphone based on MT6853 (Dimensity 800U) chipset as the testing device. The OS is MIUI Global 12.5.2.0 (Android 11 RP1A.200720.011).

As there are only a few media related drivers presented on the device, it was not difficult to find the driver that is responsible for communication between the AP and the DSP.

Figure 1: Media drivers

We are interested in the /dev/audio_ipi driver.

A simple search for the driver name in the vendor partition allowed us to find the MediaTek API library /vendor/lib/hw/audio.primary.mt6853.so. The library exports the AudioMessengerIPI singleton, which contains the sendIpiMsg method that can be used to send Inter-processor interrupt (IPI) messages to the audio DSP. We used this library to explore the communication flow between the Android user space and the kernel. In our PoC code, we deal with the driver ioctls directly without additional wrapping.

The following ioctls are defined in the /dev/audio_ipi driver:

Let’s take a look at their purpose.

The DSP firmware must be initialized using the AUDIO_IPI_INIT_DSP ioctl before sending messages.

We can use the following simple function to open and init the driver:

As we will show later, on the DSP side, there are several independent message handlers called task scenes. Each task scene has its own unique area of responsibility. For example, the phone call task controls speech enhancement. The AUDIO_IPI_LOAD_SCENE ioctl is used to load a task scene on the DSP. The task scene ID is a mandatory parameter of the IPI message.

There are three different ioctls for sending an IPI message to the audio DSP. The difference lies in the way the payload data associated with the message is transmitted. The possible options are:

  • Transmit the payload as part of the message (AUDIO_IPI_SEND_PAYLOAD). The payload size is limited to 0xE0 bytes.
  • Transmit the payload through a shared memory registered to communicate between the AP and the DSP (AUDIO_IPI_SEND_DRAM). The payload size is limited by the shared region size.
  • Do not transmit the payload (AUDIO_IPI_SEND_MSG_ONLY).

The IPI message has this structure:

The significant fields are:

  • task_scene – The DSP task scene ID.
  • data_type – The payload type. Set to 1 if the payload field contains data associated with the message. Set to 2 if the payload field contains information about the shared region.
  • msg_id – The message ID.
  • param1 and param2 – The message parameters. Usually the param1 contains the payload size.

We therefore have full control over the transmitted message right from the Android user space. We target the DSP handler through task_scene and msg_id fields, and provide it with our data through param1, param2 and payload fields.

Now let’s deal with shared memory. The AUDIO_IPI_REG_DMA ioctl can be used to request the DSP driver to allocate a region in a dedicated Direct access memory (DMA) that is shared between the AP and the DSP. In fact, two memory regions are allocated: one is for transferring data from the AP to a DSP task scene, and the other is for transferring data in the opposite direction. The DSP driver uses these regions to transmit the message payload when calling the AUDIO_IPI_SEND_DRAM ioctl and to receive the results.

The AUDIO_IPI_REG_DMA ioctl expects an object with the following structure for the argument:

As you can see, we control the size of the allocated regions through the a2d_size and d2a_size fields.

The Android kernel log kindly provides us with the following information about the reserved DMA:

  • The base virtual address – 0xffffff800b000000.
  • The base physical address – 0x7d940000.
  • The size – 0x200000.

When we allocate the shared regions for a task scene, the corresponding offsets in the DMA are also logged.

Figure 2: Android kernel log

We should note that the physical address of the task scene’s shared region, calculated as the base physical address of the DMA + the offset of the shared region, is persistent on the device.

The following function can be used to send an IPI message with data transfer over the DMA:

The /dev/audio_ipi driver does not communicate with the audio DSP directly. Instead, it forwards an IPI message to the System control processor (SCP) by adding the message to the SCP queue. The audio DSP firmware registers the SCP dispatcher to receive audio IPI messages from the SCP.

Reverse engineering of the audio DSP

The firmware image

We know how to send an IPI message to the audio DSP. The next step is to find the handlers for such messages in DSP firmware.

The audio DSP is presented in the Xiaomi factory update by a separate audio_dsp.img image file. Another way to get the image is to dump the /dev/block/platform/bootdevice/by-name/audio_dsp partition from a rooted device.

The image file has a proprietary structure but it can be easily reconstructed. On our test device, the DSP image contains nine partitions.

Figure 3: The audio_dsp.img structure

The cert1 and cert2 partitions are certificates in DER format that are used to verify the integrity of the hifi3 partitions. The hifi3_a_dram partition is the dynamic memory used by the audio firmware. In the initial state, it is almost empty. The hifi3_a_iram and hifi3_a_sram partitions are the code and data of the customized FreeRTOS.

Each partition has a header that stores the size and name of that partition. The header starts with the magic 0x88168858, which can be used to quickly locate the beginning of the partition in the file. Figure 4 shows the hifi3_a_dram header.

Figure 4: The hifi3_a_dram header

The header and data sizes of the hifi3_a_dram partition are 0x200 and 0x8000, respectively. We can cut the hifi3 content easily.

Let’s take a closer look at the hifi3_a_sram (we skip the header from now on). The partition starts with the 0x400 zero bytes. So there is no special file format here. We are dealing with raw data. The next 0x37F8 bytes appear to be pointers to memory, mostly located after the 0x56000000 address. Starting from byte 0x3BF8 is the Xtensa code.

The IDA Pro 7.6 supports the Tensilica Xtensa architecture. Let’s open the hifi3_a_sram partition in the IDA with 0x56000000 as the base address.

We used this simple script to recognize the leading raw bytes as pointers (double words):

Now we have thousands of pointers to code and data. But how do we deal with the code? Xtensa opcodes have variable length and IDA has no idea how to proceed.

We first tried to write a script that would find the beginning of functions and try to disassemble. This is possible because most functions start with the entry opcode that allocates the stack. But it does not work well here because there are too many custom opcodes that IDA is not aware of. Disassembly gets stuck when it reaches an unknown opcode. All we got are snippets like the following:

Eventually, we found another good solution. We used the Xtensa SDK to help IDA.

The HiFi DSP software development toolchain can be freely downloaded from the tensilicatools.com web site. The XtDevTools is part of the installation packet. We used the ~/xtensa/XtDevTools/install/tools/RI-2020.5-linux/XtensaTools/bin/xt-objdump tool to create object dumps of hifi3 partitions. This way we dumped the hifi3_a_sram:

The object dump contains disassembled Xtensa code. Let’s take a look at the instruction where IDA got stuck:

As you can see, the hifi3_ss_spfpu_7 core of the xt-objdump tool knows more Xtensa opcodes than the IDA plugin. Apparently, MediaTek used the standard audio DSP template prepared by Tensilica as a basis for its processor. MediaTek added several particular instructions, but their number is small compared to those offered by Tensilica for audio DSPs.

The object dump contains many errors and cannot be used as the main source for the research. But it can help IDA disassemble the hifi3 partitions more easily.

The Xtensa plugin is represented in the IDA by the xtensa.so library. It is not easy to patch because there are too many instructions to add. The best solution is to use the object dump to find all basic Xtensa instructions and add the disassembly as a comment to any unrecognized instructions. A simple IDA script can do this job. In Figure 5, you can see what the IDA navigation bar looks like after applying the dump. Almost all code chunks were recognized.

Figure 5: IDA navigation bar

The disassembled code looks like this:

This representation is quite convenient for manual research.

Note that most of the firmware functions contain code to log debug information. A log message includes the name of the current function. MediaTek gave us self-describing function names and the ability to quickly search for functions in the code.

We disassembled the hifi3_a_iram partition in the same way as the hifi3_a_sram. The base addresses of the hifi3_a_dram and the hifi3_a_iram are 0x4FFB0000 and 0x4FFE0000, respectively.

FreeRTOS

Now that we found a way to research the audio DSP firmware, let’s take a look at its content.

The MediaTek audio DSP OS is an adapted version of FreeRTOS. MediaTek used the third-party kernel and implemented audio and messaging logic on top of it.

The OS creates a number of audio tasks at startup and associates them with scene IDs. The create_all_audio_task function is a factory where we can find all supported tasks and scene IDs. The following tasks run on our test device:

  • phone call (0)
  • offload (2)
  • voip (4)
  • primary (7)
  • deep buffer (8)
  • aud playback (9)
  • capture ul1 (0xA)
  • music (0xD)
  • call final (0xE)
  • fast (0xF)
  • ktv (0x10)
  • capture raw (0x11)
  • controller (0x12)
  • daemon (0x14)

Each audio task is represented by a task object that contains a pointer to a recv_message function. The SCP message dispatcher calls this function when a new IPI message arrives. The IPI message is passed to the function as the second argument.

The recv_message functions are what we are looking for. This is where audio tasks begin to handle IPI messages sent from the Android side. After a quick look at the code, we see that most tasks, other than the phone call, offload, controller and daemon, use the same task_common_recv_message function. After all, only the next five functions parse IPI messages, and this is where we can search for vulnerabilities:

  • task_phone_call_parsing_message
  • do_offload_actions_post
  • task_common_task_loop
  • task_controller_parsing_message
  • task_auddaemon_task_loop

We manually reviewed these functions and discovered several vulnerabilities that can be exploited to attack the DSP from Android.

CVE-2021-0661, CVE-2021-0662 and CVE-2021-0663

Classic heap overflow in the AUDIO_DSP_TASK_MSGA2DSHAREMEM message handler

This issue is related to all common audio DSP tasks. When processing an IPI message with ID 6 (AUDIO_DSP_TASK_MSGA2DSHAREMEM), the task_common_task_loop function copies the message payload into the atod_share field of the common task object. The message param1 is used as the number of bytes to copy. The check that param1 is not larger than the atod_share field size is omitted. Therefore, the payload overwrites the memory after atod_share when the payload size is greater than 0x20 bytes.

The following call to the send_ipi_dma on the Android side overwrites the DSP memory with garbage and causes a crash:

The Android kernel log confirms the issue:

Classic heap overflow in the init_share_mem_core function

The task_auddaemon_task_loop function of the daemon task, upon receiving an IPI message with ID 7, calls the init_share_mem_core function. The init_share_mem_core copies the message payload to an internal audio_dsp_dram buffer using the param1 as the number of bytes to copy. The function has a check that param1 is less than 0xE0 bytes, but the audio_dsp_dram size is 0x20 bytes. 0xC0 bytes can be overwritten.

To fix the DSP heap with controlled values, we can send an IPI message carrying the payload as part of the message:

Improper validation of array index in the audio_dsp_hw_open_op function

When processing an IPI message with ID 0x203 (AUDIO_DSP_TASK_PCM_PREPARE), the task_common_task_loop function calls the get_audiobuf_from_msg to extract an audio buffer from the physical memory addressed by the param2. Next, this buffer is passed as an argument to the audio_dsp_hw_open function that is a wrapper over the audio_dsp_hw_open_op. The audio_dsp_hw_open_op function copies this audio buffer to a static array. The field at offset 0x54 in the audio buffer is used as the array index. There is no overflow check of the index value. Therefore, we can provide an arbitrary index to overwrite a portion of memory after the array with controlled values.

To own the audio buffer, we can send the IPI message to the DSP through the shared DMA region and point the param2 to the memory where the payload is located. As we showed earlier, the physical address of the shared DMA region is permanently on the device.

The following PoC code reboots our test device:

Note that the get_audiobuf_from_msg function also does not validate the param2. Using any unsuitable or null address in param2 will crash the DSP in memcpy function:

Looking for a way to attack the Android HAL from an unprivileged app

Now we know how the audio DSP can be attacked from Android through the /dev/audio_ipi driver. Unfortunately, an unprivileged Android application as well as the adb shell have no permissions to communicate with this driver. SELinux allows access to the audio_ipi_device object from the factory, meta_tst, and mtk_hal_audio contexts only. An attacker needs to find a way to exploit the MediaTek Hardware abstraction layer (HAL) to access the DSP driver from under the mtk_hal_audio context.

While looking for a way to attack the Android HAL, we found several dangerous audio settings implemented by MediaTek for debugging purposes. A third-party Android application can abuse these settings to attack MediaTek Aurisys HAL libraries.

Audio hardware parameters

Android documentation states that the AudioManager provides access to volume and ringer mode control. An Android application can bind the audio service and then use the setParameters method of the AudioManager to configure the hardware.

A device manufacturer can add their own audio settings and keep track of their changes. MediaTek provides proprietary parameters to configure the Aurisys libraries. On our test device, the /vendor/lib/hw/audio.primary.mt6853.so library is responsible for handling audio parameters added by MediaTek. In Figure 6 you can see the accepted format of the setParameters string argument.

Figure 6: MediaTek audio parameter

The parameter string contains the following information:

  • The target subsystem to handle the command. It can be HAL or DSP.
  • The aurisys scenario.
  • The command key that identifies the affected HAL library.
  • The command string. We found eight supported commands.
  • The value that is actually the argument to the command.

The /vendor/etc/aurisys_config.xml and aurisys_config_hifi3.xml files define all supported aurisys scenarios and command keys.

For example, the following parameter can be used to enable logging of speech processing information:

Most of the supported commands are interesting for us in terms of information leak. But we want to pay attention only to the PARAM_FILE command that allows us to set the location of the configuration file related to a particular Aurisys HAL library.

For example, an unprivileged Android app can customize the libfvaudio.so HAL library provided by an OEM by setting the following parameter:

The Aurisys library parses the configuration file when provided.

Note that generally, device manufacturers do not care about validating configuration files properly because they are not available to unprivileged users. But in our case, we are in control of the configuration files. The HAL configuration becomes an attack vector. A malformed config file could be used to crash an Aurisys library which could lead to LPE.

We have prepared an example of the attack against the libfvaudio.so HAL library on the Xiaomi device but we cannot share the details for ethical reasons.

To mitigate the described audio configuration issues, MediaTek decided to remove the ability to use the PARAM_FILE command via the AudioManager in the release build of Android. CVE-2021-0673 was assigned to the issue.

Summary

In our research, we looked at MediaTek audio DSP as an attack target. We reverse engineered the Android API that is responsible for communication with the audio processor, as well as the firmware that runs on the audio DSP.

We show how an unprivileged Android application could abuse the AudioManager API by setting a crafted parameter value in order to attack the Android Aurisys HAL (CVE-2021-0673). By chaining CVE-2021-0673 with the vulnerabilities in original equipment manufacturer (OEM) partner’s libraries, the MediaTek security issue we found could lead to local privilege escalation from an Android application.

With the above local privilege escalation, an Android application may be able to send messages to the audio DSP firmware.

CVE-2021-0661, CVE-2021-0662 and CVE-2021-0663 which present vulnerabilities in the audio DSP itself, may further allow to preform malicious actions such as, for example, execute and hide malicious code within the audio DSP chip itself.

Since the DSP firmware has access to the audio data flow, a malformed IPI message could potentially be used by a local attacker to do privilege escalation, and theoretically eavesdrop on the mobile phone’s user.

The discovered vulnerabilities in the DSP firmware (CVE-2021-0661, CVE-2021-0662, CVE-2021-0663) have already been fixed and published in the October 2021 MediaTek Security Bulletin. The security issue in the MediaTek audio HAL (CVE-2021-0673) was fixed in October and will be published in the December 2021 MediaTek Security Bulletin.

 

Check Point’s customer remain fully protected against such threats with Harmony Mobile Security that Prevents malware from infiltrating  devices by detecting and blocking the download of malicious apps in real-time.
By extending Check Point’s industry-leading network security technologies to mobile devices, Harmony Mobile offers a broad range of network security capabilities, ensuring devices are not exposed to compromise with real-time risk assessments
Protection name: VULN__CVE20210673