Talos Vulnerability Report

TALOS-2025-2153

Dell ControlVault3 cvhDecapsulateCmd improper input validation vulnerability

August 9, 2025
CVE Number

CVE-2025-24919

SUMMARY

A deserialization of untrusted input vulnerability exists in the cvhDecapsulateCmd functionality of Dell ControlVault3 prior to 5.15.10.14 and ControlVault3 Plus prior to 6.2.26.36. A specially crafted ControlVault response to a command can lead to arbitrary code execution. An attacker can compromise a ControlVault firmware and have it craft a malicious response to trigger this vulnerability.

CONFIRMED VULNERABLE VERSIONS

The versions below were either tested or verified to be vulnerable by Talos or confirmed to be vulnerable by the vendor.

Broadcom BCM5820X
Dell ControlVault3 5.14.3.0
Dell ControlVault3 Driver and Firmware prior to 5.15.10.14
Dell ControlVault3 Plus Driver and Firmware prior to 6.2.26.36

PRODUCT URLS

ControlVault3 - https://dell.com/ BCM5820X - https://www.broadcom.com/products/embedded-and-networking-processors/secure/bcm5820x

CVSSv3 SCORE

8.1 - CVSS:3.1/AV:L/AC:H/PR:N/UI:N/S:C/C:H/I:H/A:H

CWE

CWE-502 - Deserialization of Untrusted Data

DETAILS

Dell ControlVault is a hardware based solution that can securely store passwords, biometric templates and security codes. It can interface with smart cards, Near-field Communication (NFC) devices and fingerprint readers. The hardware solution is based on the Broadcom BCM5820X chip series.

On Windows, any low privilege user can interface with the ControlVault3 hardware. In order to do so, the userland dll bcmbipdll.dll can be used to talk with the device driver cvusbdrv.sys which in turn talks over USB to the ARM firmware running on the BCM5820X chip.

This vulnerability is located in the cvhDecapsulateCmd function of bcmbipdll.dll that is called whenever a response from the firmware is received.

When sending a command to the firmware, two lists of parameters are created using the InitParam_List function. One list is meant as outgoing parameters to the firmware, and one list is meant as an incoming list of parameters that are expected as a reply from the firmware. Each parameter will have a 8-byte header (type, length) followed by any necessary data content.

The outgoing data is then serialized using the ConstructBlob function and encapsulated using the cvhEncapsulateCmd function; the data is eventually sent to the firmware using the ProcessTransmitCmd and upon return of this function, a buffer is obtained that contains the data returned from the firmware. This data goes through the reverse operations, first decapsulated (cvhDecapsulateCmd) then the data may be split further into relevant pointers using cvhSaveReturnValue. At this point the user provided parameters should have been populated with the data requested for a given command.

The problem here is how cvhDecapsulateCmd handles the data retrieved from the firmware (See code below). Instead of unpacking the data in a way that matches the specification from the incoming list of parameters created using the InitParam_List function (at [0]), the function deserializes instead the data provided by the firmware at [1] and overwrites the (type,length) header while copying the data at [2]. Furthermore, it appears that if too many parameters are provided, there are no checks in place (see [3]) that would prevent from writing too many parameters into the dst_params array, thus leading to further memory corruption (dereferencing out-of-bound the content of the memory located after the last dst_params entry and trying to memmove the data from the parameter into it)

This situation can become problematic if a malicious firmware is interacting with the driver; this can happen if the ControlVault firmware has been compromised (as seen in TALOS-2025-2130) or if a malicious USB device is masquarading as a legitimate Control Vault device.

To delve further into the details, we can observe the vulnerability in the following code (function names are based on strings found in the firmware, variable names are mostly arbitrary and based on reversing work):

__int64 __fastcall cvhDecapsulateCmd(
        cv_out_buffer *encapsulated_cmd, //data coming from the firmware
        cv_session_info *session,
        unsigned int *pNumParams,  // ouput of the function, total number of parameters unpacked
        cv_param_list_entry **dst_params)  //[0]
{
    (...) // skipped variable definition

  dst_params_ = dst_params;
  pNumParams_ = pNumParams;
  cur_param_num = 0i64;

  if ( (encapsulated_cmd->header.field_A & 1) == 0 || (decryptedCmdSize = 0, encapsulated_cmd->header.field_8 == 4) )
  {
    cur_pos_buffer = &encapsulated_cmd->first_byte;
    v25 = &encapsulated_cmd->first_byte + encapsulated_cmd->header.cmd_size;
    v39 = v25;
    goto SKIP_DECRYPTION;
  }
    (...) // skipped case where data is encrypted

SKIP_DECRYPTION:
      if ( cur_pos_buffer >= v25 )
      {
        *pNumParams_ = cur_param_num;
        goto DONE;
      }
      param_type = cur_pos_buffer->paramType;
      if ( cur_pos_buffer->paramType >= 2 && cur_pos_buffer->paramType - 2 >= 2 )   // ParamType is between 0 and 3
      {
        if ( bLoggingStuff )
        {
          log_more_stuff(
            "d:\\BuildAutomation\\BCMSecurity_int_cs_5.14\\sw\\phoenixII\\host\\windows\\BCMSecurity\\BCMSecurity\\src\\B"
            "CMIDll\\BCMILib_Src\\HelperFunctions.c");
          log_stuff("%s %s%s:%d %s\n", "Invalid Status Type", v51, &pNumParams_, 888i64, "is_valid_param_type");
        }
        status_ = INVALID_PARAM_TYPE;
        goto DONE;
      }
      status_ = 0;
      status = encapsulated_cmd->header.status;
      if ( !status || status == 143 || status == 41 || status == 154 && param_type != CV_PACK_MAGIC_BUFFER )
      {
COPY_PARAM_CONTENT:
        Size = 0i64;
        param_size = cur_pos_buffer->paramLen + 8;
        decryptedCmdSize = param_size;
        v49 = param_size;
        param_buffer = j__malloc_base(param_size);    // Allocates a buffer with a +8 size to account for the header (type, length)
        param_buffer_ = param_buffer;
        Size = param_buffer;
        if ( !param_buffer )
        {
          status_ = MEM_ERROR;
          free_stuff(&Size, 1u);
          goto DONE;
        }
        memset(param_buffer, 0, param_size);
        v32 = param_size;
        param_size_aligned = param_size & 0xFFFFFFFC;
        param_size_reminder_non_aligned = v32 & 3;
        if ( !param_size_reminder_non_aligned )
          param_size_aligned = decryptedCmdSize;
        v35 = param_buffer_;
        if ( param_size_aligned >= 4 )
        {
          memmove(param_buffer_, cur_pos_buffer, param_size_aligned);    // [1] copy to param_buffer the parameter entry, including the header coming from the cmd header
          v35 = &param_buffer_[param_size_aligned];
          Size = &param_buffer_[param_size_aligned];
          cur_pos_buffer = (cur_pos_buffer + param_size_aligned);
        }
        if ( param_size_reminder_non_aligned )
        {
          memmove(v35, cur_pos_buffer, param_size_reminder_non_aligned);
          cur_pos_buffer = (cur_pos_buffer + 4);
        }
        Size = param_buffer_;
        v36 = &dst_params_[cur_param_num];
        **v36 = 0i64;
        memmove(*v36, param_buffer_, v49);   //[2] copy the whole parameter entry including its header to the cur_param_num entry of the dst_params buffer (aka the decapsulated parameters) 
        if ( param_buffer_ )
          free(param_buffer_);
        goto NEXT_PARAM;
      }


           (...) // skipped handling of other edge cases

NEXT_PARAM:
      cur_param_num = (cur_param_num + 1);
      cur_param_num_ = cur_param_num;
      if ( cur_param_num > 0x18 )   // [3]  there's no check if the number of parameters parsed doesn't go beyond the request number of parameters in *pNumParameters, leading to a potential wild memcpy at [2]
      {
        status_ = CV_STATUS_TOO_MANY_PARAM;
        goto DONE;
      }
    }
  } 

The consequences of this vulnerability are threefold: - a type confusion with the expected type of data (paramType) can happen when used to unpack the parameters in cvhSaveReturnValue - a buffer overflow in the calling function if the size field from a deserialized parameter (cur_pos_buffer->paramLen) is bigger than the originally requested size for that given parameter - an out-of-bound dereference of the dst_params array of pointers subsequently used as the destination of a memcpy with attacker controlled data

Any of these issues can lead to a memory corruption on the stack, heap, or in the data segment, and thus can create the risk of an arbitrary/remote code execution in the context of the calling process.

VENDOR RESPONSE

Vendor advisory: https://www.dell.com/support/kbdoc/en-us/000276106/dsa-2025-053

TIMELINE

2025-03-04 - Vendor Disclosure
2025-06-13 - Vendor Patch Release
2025-08-09 - Public Release

Credit

Discovered by Philippe Laulheret of Cisco Talos.