We have identified a new stack buffer overflow vulnerability in Samsung’s Android Radio Interface Layer implementation. The vulnerability can be exploited by a malicious (compromised) baseband runtime to achieve denial of service in Android in the radio context.

The vulnerability we are disclosing in this advisory affected a wide range of Samsung devices, including phones on the newest Exynos chipsets. The July 2023 issue of the Samsung Mobile Security Bulletin contains this vulnerability as CVE-2023-30648.

Vulnerability Details

The Exynos vendor RIL implementation, provided by the libsec-ril.so library, exposes an Inter Process Call (IPC) interface to the baseband processor. The baseband processor can use this API through dedicated IPC messages.

When the IpcProtocol41Imei::IpcRxImeiUpdateImeiNoti IPC handler is triggered, it converts the supplied error codes into error messages and concatenates them into a stack buffer. The error codes are stored in a variable length array in the IPC message, the size of which is never verified.

The vulnerability is in the IpcProtocol41Imei::IpcRxImeiUpdateImeiNoti function, which simply concatenates the fail reason descriptions into the fixed size stack buffer, without verifying the overall length. Each fail description is added to the stack buffer with sprintf and an offset variable is maintained to track the current position within the buffer. This offset variable is never verified and it can point out of the reserved buffer. The stack buffer is 240 bytes from the bottom of the stack frame, each fail reason description can take up to 25-30 bytes and the maximum array size is 255 elements. As a result the stack buffer can be overflown by ~6000 bytes.

CscCompareResult * __thiscall
IpcProtocol41Imei::IpcRxImeiUpdateImeiNoti
          (IpcProtocol41Imei *this,sipc_ipc_msg *ipc_msg,int param_2,int *param_3,
          RegistrantType *param_4)

{
  [...] 
  if (ipc_msg->data[1] == (Data)0x0) {
    [...] 
  }
  else if (ipc_msg->data[1] == (Data)0x1) {
    local_f0 = 0;

    if (ipc_msg->data[2] == (Data)0x0) {
      // [1] Count is retrieved from the message
      idx = ipc_msg->data[4];

      if (idx != (Data)0x0) {
        offset = 0;
        data_ptr = ipc_msg->data + 5;
        do {
          fmt = "%s";
          if (offset != 0) {
            fmt = "/%s";
          }
          uVar5 = ConvertToFailName(*(uint *)data_ptr);

          // [2] Fail reason is concatenated, the offset is never verified
          cnt = sprintf((char *)((long)&local_f0 + offset),(char *)0xffffffffffffffff,fmt,uVar5);
          pDVar6 = data_ptr + 4;
          idx = (Data)((char)idx + -1);
          offset = offset + cnt;
          data_ptr = pDVar6 + ((ulong)(*(uint *)pDVar6 << 1) & 0x1fe | 1);
          if (*(uint *)pDVar6 - 1 < 0x93) {
            data_ptr = pDVar6;
          }
        // [2] The FailName is printed into the stack buffer Count times
        } while (idx != (Data)0x0);

The vulnerable function retrieves the specified count value at [1] from the IPC message. Without further verification, it retrieves, converts, and prints the failure reason into the provided stack buffer at [2]. The sprintf eventually overwrites the stack frame beyond the designated buffer.

It must be noted that overflown value is the result of the ConvertToFailName() and sprintf conversion, giving very limited control over it to the attacker.

The baseband firmware has dedicated API functions for sending and receiving IPC messages. These functions serialize the IPC content into the dedicated shared memory ring-buffers and signal the kernel through an interrupt. On the AP processor side the CPIF Android kernel driver retrieves the IPC message content from the shared memory and exposes it to the user-space through the /dev/umts_ipc0 and/or /dev/umts_ipc1 character devices.

Within the vendor rild implementation (libsec-ril.so) there are dedicated threads for continuously polling these devices. The entry point of the threads is IoChannelReader::Run and they block on the device read in the IoChannel::Read function. Once an IPC message is read from the character device it is passed to IpcModem::ReceiveMessage and subsequently to the IpcModem::DoIoChannelRouting and IpcModem::DoIoChannelRoutingRx functions. The IpcModem::DoIoChannelRoutingRx function receives the pointer to the appropriate IoChannel object where the IPC message was retrieved.

IPC messages are passed to IpcModem::GetIpcMessage and then forwarded to IpcModem::ProcessSingleIpcMessageReceived where the appropriate IpcProtocol41* implementation is retrieved based on the IPC command value. Finally, the IpcProtocol41*::GetRxData function is called which is responsible for calling the requested IPC API endpoint, based on the subcommand of the IPC message.

Affected Devices

All Samsung chipsets containing Samsung’s baseband implementation, including all Exynos chipsets.

Fix

Samsung OTA images, released after July 2023, contain the fix for the vulnerability.

Timeline

  • 2023.04.01. Bug reported to Samsung PSIRT
  • 2023.05.12. Samsung confirms vulnerability
  • 2023.06.26. Samsung confirms fix will be released in the July security bulletin
  • 2023.07.11. Samsung releases security bulletin
  • 2023.09.26. TASZK informs Samsung of disclosure plan, Samsung confirms
  • 2023.11.03. Vulnerability released at Hardwear.io
  • 2023.11.26. Advisory release