Dell ControlVault3 ControlVault WBDI Driver Broadcom Storage Adapter out-of-bounds write vulnerability
SUMMARYMultiple out-of-bounds read and write vulnerabilities exist in the ControlVault WBDI Driver 2025-11-16 23:59:28 Author: talosintelligence.com(查看原文) 阅读量:3 收藏

SUMMARY

Multiple out-of-bounds read and write vulnerabilities exist in the ControlVault WBDI Driver Broadcom Storage Adapter functionality of Dell ControlVault3 5.14.3.0. A specially crafted WinBioControlUnit call can lead to memory corruption. An attacker can issue an api call 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

PRODUCT URLS

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

CVSSv3 SCORE

7.3 - CVSS:3.1/AV:L/AC:L/PR:L/UI:R/S:U/C:H/I:H/A:H

CWE

CWE-805 - Buffer Access with Incorrect Length Value

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.

Control Vault WBDI Driver is Broadcom’s implementation of the Windows Biometric Driver Interface (WBDI) leveraging the Control Vault hardware to provide biometrics features using Windows APIs. The relevant features are implemented inside three DLLs (BrcmEngineAdapter.dll, BrcmSensorAdapter.dll, BrcmStorageAdapter.dll). The vulnerabilities in this reports are all found inside Broadcom’s implementation of the Storage Adapter. Biometrics drivers are instantiated by the WinBio Service running under svchost as SYSTEM.

When implementing the various WinBio interfaces, an Adapter can provide a mechanism akin to a DeviceIoControl by implementing the ControlUnit and/or ControlUnitPrivileged functions. Any unprivilege user can access the ControlUnit function by first opening a WinBio session (WinBioOpenSession) locking the relevant BiometricUnit (WinBioLockUnit) and finally issuing the control command (WinBioControlUnit). These control commands are vendor-defined and thus will vary between vendors/models. One can also choose to limit access to the more critical APIs by implementing sensitive features under the ControlUnitPrivileged function instead .

BrcmStorageAdapter.dll does not implement any ControlUnitPrivileged function, but instead has 4 different commands inside the ControlUnit function. For reference, the prototype of any ControlUnit function is as follow:

NTSTATUS __stdcall StorageAdapterControlUnit(
    PWINBIO_PIPELINE Pipeline,
    ULONG ControlCode,
    PUCHAR SendBuffer,
    SIZE_T SendBufferSize,
    PUCHAR ReceiveBuffer,
    SIZE_T ReceiveBufferSize,
    PSIZE_T ReceiveDataSize,
    PULONG OperationStatus) 

The key point to note is that the ControlCode specifies which functionality the caller wants to trigger, and SendBuffer, SendBufferSize, ReceiveBuffer ReceiveBufferSize are parameters that are controlled via the caller’s request. The BrcmStorageAdapter fails to properly bound-check the Receive/Send buffers which leads to multiple out-of-bound reads (if the SendBuffer is smaller than expected) and out-of-bound writes (if the ReceiveBuffer is too small). Memory corruptions inside the WinBioSVC can lead to Denial of Service (crashing the service), information leaks and possibly code execution as SYSTEM.

CVE-2025-36460 - WBIO_USH_GET_IDENTITY: Out-of-bound-write

This vulnerability is triggered when submitting a WinBioControlUnit call to the StorageAdapter with the ControlCode 2 (WBIO_USH_GET_IDENTITY) with 4 <= ReceiveBuferSize < 80 This will lead to an out-of-bound write of up to 75 bytes. These can be either null-bytes or potentially attacker controlled data if another vulnerability is leveraged to place attacked-controlled data as an WINBIO_IDENTITY object inside the storage’s database (see TALOS-XXXXX).

We can see the relevant code here:

    // ControlCode: 
//    0: WBIO_USH_GET_TEMPLATE
//    2: WBIO_USH_GET_IDENTITY
//    3: WBIO_USH_CREATE_CHALLENGE
//    4: WBIO_USH_ADD_RECORD
NTSTATUS __stdcall StorageAdapterControlUnit(
        PWINBIO_PIPELINE Pipeline,
        ULONG ControlCode,
        PUCHAR SendBuffer,
        SIZE_T SendBufferSize,
        PUCHAR ReceiveBuffer,
        SIZE_T ReceiveBufferSize,
        PSIZE_T ReceiveDataSize,
        PULONG OperationStatus)
{
   (...) // vaiable init

  log_stuff((int)L"StorageAdapterControlUnit() enter\n");
  if ( !Pipeline
    || !SendBuffer
    || !SendBufferSize
    || (ReceiveBuffer_ = ReceiveBuffer) == 0i64
    || (ReceiveBufferSize_ = ReceiveBufferSize) == 0   //[0]
    || (ReceiveDataSize_ = ReceiveDataSize) == 0i64
    || (OperationStatus_ = OperationStatus) == 0i64 )
  {
    log_stuff((int)L"StorageAdapterControlUnit() ERROR: invalid parameter\n");
    status = E_POINTER;
    goto DONE;
  }

   (....)
  if ( !ControlCode )
  {
     //Case 0
  }
  control_code_minus_2 = ControlCode - 2;
  if ( control_code_minus_2 )
  {
      // case 3-4
  }
  // Case ControlCode == 2
  log_stuff((int)L"StorageAdapterControlUnit() WBIO_USH_GET_IDENTITY\n");
  *(_OWORD *)ReceiveBuffer_ = 0i64;
  *((_OWORD *)ReceiveBuffer_ + 1) = 0i64;  //[1]
  *((_OWORD *)ReceiveBuffer_ + 2) = 0i64;
  *((_OWORD *)ReceiveBuffer_ + 3) = 0i64;
  *((_OWORD *)ReceiveBuffer_ + 4) = 0i64;
  *ReceiveDataSize_ = 0i64;
  if ( ReceiveBufferSize_ < 4 )            //[2]
    goto BAD_ReceiveBufferToSmall;
  if ( !WBFUSH_ExistsCVObject(*(_DWORD *)SendBuffer, &objType) )
  {
    log_stuff((int)L"StorageAdapterControlUnit. Object does not exist\n");
    status = 0;
    *OperationStatus_ = 8;
    goto DONE;
  }
  if ( objType != CV_TYPE_FINGERPRINT )
  {
    log_stuff((int)L"StorageAdapterControlUnit. Object is not an FP template\n");
    status = 0;
    *OperationStatus_ = 10;
    goto DONE;
  }
  v21 = StorageAdapterQueryByCVHandle(Pipeline, *(_DWORD *)SendBuffer); // [5]
  if ( v21 >= 0 )
  {
    v22 = Pipeline->StorageInterface;
    if ( v22 )
    {
      FirstRecord_ = v22->FirstRecord;
      if ( FirstRecord_ )
      {
        status = FirstRecord_(Pipeline);
        if ( status >= 0 )
        {
          status = BrcmStorageAdapterGetCurrentRecord(Pipeline, &v32);
          if ( status >= 0 )
          {
            Indentity = v32.Indentity;
            status = 0;
            *(_OWORD *)ReceiveBuffer_ = *(_OWORD *)&v32.Indentity->Type;                    //[3]
            *((_OWORD *)ReceiveBuffer_ + 1) = *(_OWORD *)&Indentity->Value.SecureId[12];
            *((_OWORD *)ReceiveBuffer_ + 2) = *(_OWORD *)&Indentity->Value.SecureId[28];
            *((_OWORD *)ReceiveBuffer_ + 3) = *(_OWORD *)&Indentity->Value.SecureId[44];
            *((_QWORD *)ReceiveBuffer_ + 8) = *(_QWORD *)&Indentity->Value.SecureId[60];
            *((_DWORD *)ReceiveBuffer_ + 18) = *(_DWORD *)&Indentity->Value.SecureId[68];
            ReceiveBuffer_[76] = v32.Subfactor;                                            // [4]
            *ReceiveDataSize_ = 0x50i64;
            *OperationStatus_ = 0;
            goto DONE;
          }
      // Error Handling
DONE:
  log_stuff((int)L"StorageAdapterControlUnit() exit\n");
  return status;
}

We can see at [0] the checks to ensure the relevant size and buffer aren’t 0. At [1] 0x50 null-bytes are written in the ReceiveBuffer (note the OWORD* cast explaining the amount of data written); at [2] the ReceiveBuffer’s size is checked to be at least 4 (too little too late). Then the logic of the function continues normally and tries to retrieve a WINBIO_IDENTITY object from the local storage database based on a handle provided by the caller function. If the content of this WINBIO_IDENTITY object is controlled by the attacker (and it can be), then we have potentially up to 72 (76-4) attacker-controlled bytes written out-of-bound. Subsequently, the Subfactor is also potentially written out of bound (as a BYTE), but this value is not fully attacker controlled as only a limited range is allowed by the Storage Api.

Note: There might also be an OOB-Read at [5] if 0< SendBufferSize < 4 but memory alignment considerations may make this difficult to target in any meaningful way.

Crash Information

Here we can seen an example of crash that occurs soon after triggering the Out-of-bound write:

0:013> !analyze -v 
*******************************************************************************
*                                                                             *
*                        Exception Analysis                                   *
*                                                                             *
*******************************************************************************


KEY_VALUES_STRING: 1

    Key  : AV.Dereference
    Value: NullPtr

    Key  : AV.Type
    Value: Write

    Key  : Analysis.CPU.mSec
    Value: 718

    Key  : Analysis.Elapsed.mSec
    Value: 28299

    Key  : Analysis.IO.Other.Mb
    Value: 10

    Key  : Analysis.IO.Read.Mb
    Value: 11

    Key  : Analysis.IO.Write.Mb
    Value: 32

    Key  : Analysis.Init.CPU.mSec
    Value: 640

    Key  : Analysis.Init.Elapsed.mSec
    Value: 31213

    Key  : Analysis.Memory.CommitPeak.Mb
    Value: 110

    Key  : Analysis.Version.DbgEng
    Value: 10.0.27793.1000

    Key  : Analysis.Version.Description
    Value: 10.2410.02.02 amd64fre

    Key  : Analysis.Version.Ext
    Value: 1.2410.2.2

    Key  : Failure.Bucket
    Value: SVCHOSTGROUP_WbioSvcGroup_NULL_POINTER_WRITE_NULL_INSTRUCTION_PTR_INVALID_POINTER_WRITE_c0000005_wbiosrvc.dll!std::list_std::shared_ptr_Binding_,std::allocator_std::shared_ptr_Binding_____::_Unchecked_erase

    Key  : Failure.Exception.Code
    Value: 0xffffffffc0000005

    Key  : Failure.Exception.IP.Address
    Value: 0x7ff8cb794dc8

    Key  : Failure.Exception.IP.Module
    Value: wbiosrvc

    Key  : Failure.Exception.IP.Offset
    Value: 0x14dc8

    Key  : Failure.Hash
    Value: {f0bb19bb-0b4e-e7c4-4847-8fb3f45a7134}

    Key  : ProblemClass.Collapse.After.BUCKET_ID
    Value: SVCHOSTGROUP_WbioSvcGroup_NULL_POINTER_WRITE_NULL_INSTRUCTION_PTR_INVALID_POINTER_WRITE

    Key  : ProblemClass.Collapse.After.DEFAULT_BUCKET_ID
    Value: SVCHOSTGROUP_WbioSvcGroup_NULL_POINTER_WRITE_NULL_INSTRUCTION_PTR_INVALID_POINTER_WRITE

    Key  : ProblemClass.Collapse.After.PRIMARY_PROBLEM_CLASS
    Value: SVCHOSTGROUP_WbioSvcGroup_NULL_POINTER_WRITE

    Key  : ProblemClass.Collapse.Before.BUCKET_ID
    Value: SVCHOSTGROUP_WbioSvcGroup_NULL_POINTER_WRITE_NULL_INSTRUCTION_PTR_INVALID_POINTER_WRITE

    Key  : ProblemClass.Collapse.Before.DEFAULT_BUCKET_ID
    Value: SVCHOSTGROUP_WbioSvcGroup_NULL_POINTER_WRITE_NULL_INSTRUCTION_PTR_INVALID_POINTER_WRITE

    Key  : ProblemClass.Collapse.Before.PRIMARY_PROBLEM_CLASS
    Value: SVCHOSTGROUP_WbioSvcGroup_NULL_POINTER_WRITE

    Key  : Timeline.OS.Boot.DeltaSec
    Value: 1948

    Key  : Timeline.Process.Start.DeltaSec
    Value: 617

    Key  : WER.OS.Branch
    Value: vb_release

    Key  : WER.OS.Version
    Value: 10.0.19041.1

    Key  : WER.Process.Version
    Value: 10.0.19041.4355


FILE_IN_CAB:  sensor_dump1.dmp

NTGLOBALFLAG:  0

APPLICATION_VERIFIER_FLAGS:  0

CONTEXT:  (.ecxr)
rax=0000000000000000 rbx=00000187b06f2640 rcx=00000187af619810
rdx=00000076fc37f6f0 rsi=00000187af619810 rdi=00000187af63b610
rip=00007ff8cb794dc8 rsp=00000076fc37f670 rbp=00000076fc37f700
 r8=0000000000000000  r9=fffffffffffe0898 r10=00000fff1bc71f6c
r11=280a112240100000 r12=0000000000000001 r13=0000000000000002
r14=00000076fc37f6f0 r15=00000187af619810
iopl=0         nv up ei pl nz na pe nc
cs=0033  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010200
wbiosrvc!std::list<std::shared_ptr<Binding>,std::allocator<std::shared_ptr<Binding> > >::_Unchecked_erase+0x28:
00007ff8`cb794dc8 4c8900          mov     qword ptr [rax],r8 ds:00000000`00000000=????????????????
Resetting default scope

EXCEPTION_RECORD:  (.exr -1)
ExceptionAddress: 00007ff8cb794dc8 (wbiosrvc!std::list<std::shared_ptr<Binding>,std::allocator<std::shared_ptr<Binding> > >::_Unchecked_erase+0x0000000000000028)
   ExceptionCode: c0000005 (Access violation)
  ExceptionFlags: 00000000
NumberParameters: 2
   Parameter[0]: 0000000000000001
   Parameter[1]: 0000000000000000
Attempt to write to address 0000000000000000

PROCESS_NAME:  svchost.exe

WRITE_ADDRESS:  0000000000000000 

ERROR_CODE: (NTSTATUS) 0xc0000005 - The instruction at 0x%p referenced memory at 0x%p. The memory could not be %s.

EXCEPTION_CODE_STR:  c0000005

EXCEPTION_PARAMETER1:  0000000000000001

EXCEPTION_PARAMETER2:  0000000000000000

GROUP:  WbioSvcGroup

FAULTING_SERVICE_NAME:  WbioSrvc

STACK_TEXT:  
00000076`fc37f670 00007ff8`cb7d0249     : 00000187`af63b610 00000076`fc37f7b0 00000187`af63b610 00007ff8`cb7d066e : wbiosrvc!std::list<std::shared_ptr<Binding>,std::allocator<std::shared_ptr<Binding> > >::_Unchecked_erase+0x28
00000076`fc37f6a0 00007ff8`cb7c51aa     : 00000076`fc37f7b0 00000187`b0d302d0 00000000`ffffffff 00000187`b0d2a240 : wbiosrvc!BindingManager::Release+0x79
00000076`fc37f730 00007ff8`cb7c1baa     : 00000187`af645ad0 00000076`fc37f7b0 00000187`b0d2a250 00000076`fc37f960 : wbiosrvc!CSensorPool::ReleaseBinding+0x16
00000076`fc37f760 00007ff8`cb803564     : 00000187`af645ad0 00000187`b0d2a240 00000187`b0d2a240 00000187`b0d2a250 : wbiosrvc!CBiometricServiceProvider::ReleaseBinding+0x276
00000076`fc37f860 00007ff8`cb7ac9b7     : 00000000`00000006 00000000`ffffffff 00000000`00000006 00000000`ffffffff : wbiosrvc!CWinBioSrvUnlockUnit::Execute+0x204
00000076`fc37f9e0 00007ff8`cb786e53     : 00000000`00000000 00000000`00000000 00000187`b0df7a50 00000000`00000000 : wbiosrvc!_sensorControlThreadRoutine+0x9a7
00000076`fc37fde0 00007ff8`cb786ea5     : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : wbiosrvc!CThread::ThreadMain+0x1f
00000076`fc37fe10 00007ff8`e01f7374     : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : wbiosrvc!CThread::ThreadStartup+0x45
00000076`fc37fe40 00007ff8`e04bcc91     : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : kernel32!BaseThreadInitThunk+0x14
00000076`fc37fe70 00000000`00000000     : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!RtlUserThreadStart+0x21


SYMBOL_NAME:  wbiosrvc!std::list<std::shared_ptr<Binding>,std::allocator<std::shared_ptr<Binding> > >::_Unchecked_erase+28

MODULE_NAME: wbiosrvc

IMAGE_NAME:  wbiosrvc.dll

STACK_COMMAND:  ~13s; .ecxr ; kb

FAILURE_BUCKET_ID:  SVCHOSTGROUP_WbioSvcGroup_NULL_POINTER_WRITE_NULL_INSTRUCTION_PTR_INVALID_POINTER_WRITE_c0000005_wbiosrvc.dll!std::list_std::shared_ptr_Binding_,std::allocator_std::shared_ptr_Binding_____::_Unchecked_erase

OS_VERSION:  10.0.19041.1

BUILDLAB_STR:  vb_release

OSPLATFORM_TYPE:  x64

OSNAME:  Windows 10

IMAGE_VERSION:  10.0.19041.4355

FAILURE_ID_HASH:  {f0bb19bb-0b4e-e7c4-4847-8fb3f45a7134}

Followup:     MachineOwner
---------

The crash is a null-pointer write in the winbiosvc that hosts the StorageAdapter. This is because the Out-of-bound write corrupted adjacent memory leading to the crash. As one of the dll loaded along with the adapter lacks ASLR, this could potentially be turned into an actually targeted corruption of its data section, assuming one can control the data being written (step [3] in the previous code description).

CVE-2025-36461 - WBIO_USH_GET_TEMPLATE: Out-of-bound read/write

This vulnerability is triggered when submitting a WinBioControlUnit call to the StorageAdapter with the ControlCode 0 (WBIO_USH_GET_TEMPLATE) and with either 0 < ReceiveBuferSize < 4 and/or 0 < SendBufferSize < 76. The former will lead to an out-of-bound write of up to 3 bytes and the latter will trigger an out-of-bound read of up to 75 bytes.

We can see the relevant code here:

    // ControlCode: 
//    0: WBIO_USH_GET_TEMPLATE
//    2: WBIO_USH_GET_IDENTITY
//    3: WBIO_USH_CREATE_CHALLENGE
//    4: WBIO_USH_ADD_RECORD
NTSTATUS __stdcall StorageAdapterControlUnit(
        PWINBIO_PIPELINE Pipeline,
        ULONG ControlCode,
        PUCHAR SendBuffer,
        SIZE_T SendBufferSize,
        PUCHAR ReceiveBuffer,
        SIZE_T ReceiveBufferSize,
        PSIZE_T ReceiveDataSize,
        PULONG OperationStatus)
{
   (...) // vaiable init

  log_stuff((int)L"StorageAdapterControlUnit() enter\n");
  if ( !Pipeline
    || !SendBuffer
    || !SendBufferSize
    || (ReceiveBuffer_ = ReceiveBuffer) == 0i64
    || (ReceiveBufferSize_ = ReceiveBufferSize) == 0   //[0]
    || (ReceiveDataSize_ = ReceiveDataSize) == 0i64
    || (OperationStatus_ = OperationStatus) == 0i64 )
  {
    log_stuff((int)L"StorageAdapterControlUnit() ERROR: invalid parameter\n");
    status = E_POINTER;
    goto DONE;
  }

   (....)
  if ( !ControlCode )
  {
  log_stuff((int)L"StorageAdapterControlUnit() WBIO_USH_GET_TEMPLATE\n");
    *(_DWORD *)ReceiveBuffer_ = 0;              // oob write   [1]
    *ReceiveDataSize_ = 0i64;
    if ( ReceiveBufferSize_ < 4 )               // [2]
      goto BAD_ReceiveBufferToSmall;
    SubFactor = SendBuffer[76];                 // [3]
    if ( SubFactor == 0xFF )                    // OOB Read ?
    {
      log_stuff((int)L"StorageAdapterControlUnit() ERROR: Unsupported sub factor\n");
      status = 0x80070057;
      goto DONE;
    }
    StorageInterface = Pipeline->StorageInterface;
    if ( StorageInterface && (QueryBySubject = StorageInterface->QueryBySubject) != 0i64 )
    {
      v28 = QueryBySubject(Pipeline, (PWINBIO_IDENTITY)SendBuffer, SubFactor);   // [4]
      if ( v28 >= 0 )
      {
        v29 = Pipeline->StorageInterface;
        if ( v29 )
        {
          FirstRecord = v29->FirstRecord;
          if ( FirstRecord )
          {
            status = FirstRecord(Pipeline);
            if ( status >= 0 )
            {
              status = BrcmStorageAdapterGetCurrentRecord(Pipeline, &v32);
              if ( status >= 0 )
              {
                memmove(ReceiveBuffer_, (const void *)v32.TemplateBlob, v32.TemplateBlobSize);
                *ReceiveDataSize_ = 4i64;
                status = 0;
                *OperationStatus_ = 0;
                goto DONE;
              }
              goto LABEL_60;
            }
LABEL_63:
            log_stuff((int)L"StorageAdapterControlUnit. WbioStorageFirstRecord Failed\n");
            *OperationStatus_ = 1;
            goto DONE;
          }
BAD_E_NOTIMPL:
          status = E_NOTIMPL;
          goto LABEL_63;
        }
LABEL_62:
        status = E_POINTER;
        goto LABEL_63;
      }
      log_stuff((int)L"StorageAdapterControlUnit. WbioStorageQueryBySubject Failed\n");
      if ( v28 == WINBIO_E_DATABASE_NO_RESULTS )
      {
        *OperationStatus_ = 1;
        status = 0;
        goto DONE;
      }
    }
    else
    {
      log_stuff((int)L"StorageAdapterControlUnit. WbioStorageQueryBySubject Failed\n");
    }
    status = 0;
    goto DONE;
}
  control_code_minus_2 = ControlCode - 2;
  if ( control_code_minus_2 )
  {
      // case 3-4
  }
  // Case ControlCode == 2
       (...)

      (...) // Error Handling
DONE:
  log_stuff((int)L"StorageAdapterControlUnit() exit\n");
  return status;
}

We have a check at [0] to ensure the pointers are defined and the sizes are not null. Then, a null-DWORD is written at [0], potentially out-of-bound if 0< ReceiveBufferSize < 4, before a bound checks is performed at [1]. Beyond the initial null-check at [0], no subsequent check is done on the SendBuffer which can then be read out-of-bound at [3] and [4]. The latter call assumes the SendBuffer contains a WINBIO_IDENTITY object and tries to look it up in the local database. If an attacker can control part of the content of the local database, this could be used as an oracle to leak a couple of bytes of heap memory.

CVE-2025-36462 - WBIO_USH_CREATE_CHALLENGE: Out-of-bound-write

This vulnerability is triggered when submitting a WinBioControlUnit call to the StorageAdapter with the ControlCode 3 (WBIO_USH_CREATE_CHALLENGE) and with 0 < ReceiveBuferSize < 4. Up to three null-bytes will be written past the end of the ReceiveBuffer.

The relevant code is as follow:

    // ControlCode: 
//    0: WBIO_USH_GET_TEMPLATE
//    2: WBIO_USH_GET_IDENTITY
//    3: WBIO_USH_CREATE_CHALLENGE
//    4: WBIO_USH_ADD_RECORD
NTSTATUS __stdcall StorageAdapterControlUnit(
        PWINBIO_PIPELINE Pipeline,
        ULONG ControlCode,
        PUCHAR SendBuffer,
        SIZE_T SendBufferSize,
        PUCHAR ReceiveBuffer,
        SIZE_T ReceiveBufferSize,
        PSIZE_T ReceiveDataSize,
        PULONG OperationStatus)
{
   (...) // vaiable init

  log_stuff((int)L"StorageAdapterControlUnit() enter\n");
  if ( !Pipeline
    || !SendBuffer
    || !SendBufferSize
    || (ReceiveBuffer_ = ReceiveBuffer) == 0i64
    || (ReceiveBufferSize_ = ReceiveBufferSize) == 0   //[0]
    || (ReceiveDataSize_ = ReceiveDataSize) == 0i64
    || (OperationStatus_ = OperationStatus) == 0i64 )
  {
    log_stuff((int)L"StorageAdapterControlUnit() ERROR: invalid parameter\n");
    status = E_POINTER;
    goto DONE;
  }

   (....)
  if ( !ControlCode )
  {
  // case 0
    (...)
  }
  control_code_minus_2 = ControlCode - 2;
  if ( control_code_minus_2 )
  {
      control_code_minus_3 = control_code_minus_2 - 1;
      if ( control_code_minus_3 )
      {
             
      
         if ( control_code_minus_3 == 1 )
         {
                           //case 4
         }
     }
     else {
         //case 3
         *(_DWORD *)ReceiveBuffer_ = 0;         //[1]
         *ReceiveDataSize_ = 0i64;
        if ( ReceiveBufferSize_ >= 4 )          //[2]
        {
            (...)  //WBIO_USH_CREATE_CHALLENGE function's logic
            goto DONE;
        }

       BAD_ReceiveBufferToSmall:
        log_stuff((int)L"StorageAdapterControlUnit. Receive buffer too small\n");
        status = TPC_E_INSUFFICIENT_BUFFER;
        goto DONE;
      }
  }
  //case 2
DONE:
  log_stuff((int)L"StorageAdapterControlUnit() exit\n");
  return status;
}

We can see that at [0] the code ensures the ReceiveBuffer is not null or zero-sized. At [1] a 4 null-bytes are written into ReceiveBuffer and at [2] the code verify that ReceiveBuffer size is at least 4 bytes. This leads to up to 3-bytes of out-of-bound write in the case 0 < ReceiveBuferSize < 4. Memory alignment constraints may make this bug difficult to trigger.

CVE-2025-36463 - WBIO_USH_ADD_RECORD: Out-of-bound-read

This vulnerability is triggered when submitting a WinBioControlUnit call to the StorageAdapter with the ControlCode 4 (WBIO_USH_ADD_RECORD) and with 0 < SendBufferSize < 104. A various amount of bytes can be read ouf of bound past the end the SendBuffer. The constraints surrounding this exploitation are pretty tough and may make the exploitation of this vulnerability less likely or limited to a Denial of Services.

The relevant code is as follow:

    // ControlCode: 
//    0: WBIO_USH_GET_TEMPLATE
//    2: WBIO_USH_GET_IDENTITY
//    3: WBIO_USH_CREATE_CHALLENGE
//    4: WBIO_USH_ADD_RECORD
NTSTATUS __stdcall StorageAdapterControlUnit(
        PWINBIO_PIPELINE Pipeline,
        ULONG ControlCode,
        PUCHAR SendBuffer,
        SIZE_T SendBufferSize,
        PUCHAR ReceiveBuffer,
        SIZE_T ReceiveBufferSize,
        PSIZE_T ReceiveDataSize,
        PULONG OperationStatus)
{
   (...) // vaiable init

  log_stuff((int)L"StorageAdapterControlUnit() enter\n");
  if ( !Pipeline
    || !SendBuffer
    || !SendBufferSize
    || (ReceiveBuffer_ = ReceiveBuffer) == 0i64
    || (ReceiveBufferSize_ = ReceiveBufferSize) == 0   //[0]
    || (ReceiveDataSize_ = ReceiveDataSize) == 0i64
    || (OperationStatus_ = OperationStatus) == 0i64 )
  {
    log_stuff((int)L"StorageAdapterControlUnit() ERROR: invalid parameter\n");
    status = E_POINTER;
    goto DONE;
  }

   (....)
  if ( !ControlCode )
  {
  // case 0
    (...)
  }
  control_code_minus_2 = ControlCode - 2;
  if ( control_code_minus_2 )
  {
      control_code_minus_3 = control_code_minus_2 - 1;
      if ( control_code_minus_3 )
      {
             
      
         if ( control_code_minus_3 == 1 )
         {
                           //case 4

            log_stuff((int)L"StorageAdapterControlUnit() WBIO_USH_ADD_RECORD\n");
            if ( *(_QWORD *)(SendBuffer + 84) == *(_QWORD *)&StorageContext->Challenge.field_0         //[1]
              && *(_QWORD *)(SendBuffer + 92) == *((_QWORD *)&StorageContext->Challenge.field_0 + 1)
              && *((_DWORD *)SendBuffer + 25) == StorageContext->Challenge.field_10 )
            {
              if ( WBFUSH_ExistsCVObject(*((_DWORD *)SendBuffer + 20), &objType) )                //    [2]
              {
                if ( objType == CV_TYPE_FINGERPRINT )
                {
                  v32.Subfactor = SendBuffer[76];                                                // [3]
                  v32.IndexVector = 0i64;
                  v32.IndexElementCount = 0i64;
                  v32.PayloadBlob = 0i64;
                  v32.PayloadBlobSize = 0i64;
                  v32.Indentity = (WINBIO_IDENTITY *)SendBuffer;                                 // [4]
                  v32.TemplateBlob = (__int64)(SendBuffer + 80);// templateBlob_cv_handle
                  v32.TemplateBlobSize = 4i64;
                  if ( StorageContext->BrcmStorageAdapterAddRecord(Pipeline, &v32) >= 0 )        // [5]
                  {
                    *ReceiveDataSize_ = 0i64;
                    status = 0;
                    *OperationStatus_ = 0;
                  }
                  else
                  {
                    log_stuff((int)L"StorageAdapterControlUnit. BrcmAddRecord\n");
                    *OperationStatus_ = 9;
                    status = 0;
                  }
                }
                else
                {
                  log_stuff((int)L"StorageAdapterControlUnit. Not a FP template\n");
                  status = 0;
                  *OperationStatus_ = 10;
                }
              }
              else
              {
                log_stuff((int)L"StorageAdapterControlUnit. WBFUSH_ExistsCVObject Failed\n");
                status = 0;
                *OperationStatus_ = 8;
              }
            }
            else
            {
              log_stuff((int)L"StorageAdapterControlUnit. WBIO_USH_ADD_RECORD authorization Failed\n");
              status = 0;
              *OperationStatus_ = 7;
            }

     }
     
     else {
         //case 3
         (...)
  
       BAD_ReceiveBufferToSmall:
        log_stuff((int)L"StorageAdapterControlUnit. Receive buffer too small\n");
        status = TPC_E_INSUFFICIENT_BUFFER;
        goto DONE;
      }
  }
  //case 2
DONE:
  log_stuff((int)L"StorageAdapterControlUnit() exit\n");
  return status;
}

We can see that at [0] the code ensures the SendBuffer is not null or zero-sized. At [1] data is read out of bound (range SendBuffer[84:104]) that is compared to a 20-byte Challenge. This Challenge is null by default but can be changed during runtime operations (in which case the StorageContext->Challenge will not be attacker-controlled). If the challenge-check is successful, data is read from SendBuffer[80:84] as a template handle which will be used at [2] as a lookup into the ControlVault device to confirm it maches a template stored on the device. Upon success, SendBuffer[0:76] is stored in the local database as a WINBIO_IDENTITY object (at [4], [5]) assuming certains checks are met. Heap shaping techniques might be used to trigger a situation where a ~70 bytes hole is created between two attacker-controlled blobs in which case the stored identity in the local database could be retrieved and used to leak data via the WBIO_USH_GET_IDENTITY command. The many constraints on this attack make it less likely to be successful, while a Denial-Of-Service due to readding an unmapped memory region is the more likely outcome of this attack; this would then trigger a crash of the WinBio Service.

TIMELINE

2025-04-22 - Vendor Disclosure
2025-06-13 - Vendor Patch Release
2025-11-17 - Public Release

Discovered by Philippe Laulheret of Cisco Talos.


文章来源: https://talosintelligence.com/vulnerability_reports/TALOS-2025-2175
如有侵权请联系:admin#unsafe.sh