# Root Cause Analysis — CVE-2022-21877

## Summary

| Field | Value |
|---|---|
| **CVE** | CVE-2022-21877 |
| **Binary** | spaceport.sys (Windows Storage Spaces Controller Driver) |
| **Component** | SpIoctlCreateTier — IOCTL 0xE7D410 handler |
| **Bug Class** | Out-of-Bounds Read / Information Disclosure (CWE-125) |
| **Impact** | Information Disclosure — kernel memory leak |
| **CVSS 3.1** | 5.5 (AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N) |
| **Exploited ITW** | No |
| **Patch** | January 2022 Patch Tuesday (KB5009543 Win10 21H2) |

## Vulnerability Overview

The Windows Storage Spaces Controller driver (spaceport.sys) exposes IOCTL handlers for
managing storage pools, tiers, and spaces. The IOCTL 0xE7D410 (`SpIoctlCreateTier`) handler
accepts a `POOLTIER` structure from user-mode that contains an `offsetGuids` field and a
`numOfGuids` field. The driver uses these to locate and copy GUID data from the IRP system
buffer into a newly allocated `SDB_TIER` object. However, it fails to validate that the
offset + size falls within the buffer boundary, allowing out-of-bounds reads from adjacent
kernel pool memory.

## Root Cause — Missing Bounds Check on User-Controlled Offset

### The Vulnerable Code (pre-patch)

In `SpIoctlCreateTier`:

```c
// User-controlled values from the POOLTIER structure:
//   offsetGuids = buffer[0xA7C]  — offset into the buffer where GUIDs start
//   numOfGuids  = buffer[0xA78]  — number of GUIDs to copy

// SpIdsCopyHelper copies GUIDs from (buffer + offsetGuids)
// with total size = numOfGuids * sizeof(GUID) (16 bytes each)

// PRE-PATCH: No check that offsetGuids + (numOfGuids * 16) <= buffer_length
SpIdsCopyHelper(buffer + offsetGuids, numOfGuids, &sdb_tier->guidList);
```

### What SpIdsCopyHelper Does

```c
NTSTATUS SpIdsCopyHelper(PVOID source, ULONG count, PGUID_LIST dest)
{
    ULONG totalSize = count * sizeof(GUID);  // count * 16

    // Allocate buffer for the GUIDs
    PVOID guidBuffer = ExAllocatePoolWithTag(PagedPool, totalSize, 'sldI');
    if (!guidBuffer)
        return STATUS_INSUFFICIENT_RESOURCES;

    // Copy GUIDs from source — NO BOUNDS CHECK on source address
    memcpy(guidBuffer, source, totalSize);  // OOB READ if offset is beyond buffer

    dest->guids = guidBuffer;
    dest->count = count;
    return STATUS_SUCCESS;
}
```

### The Bug

The IRP system buffer is allocated in `NonPagedPoolNxCacheAligned` with a fixed size. The
attacker controls `offsetGuids` which is added to the buffer base address to form the copy
source pointer. By setting `offsetGuids` to a value beyond the buffer's actual length, the
`memcpy` in `SpIdsCopyHelper` reads from kernel pool memory adjacent to the IRP buffer.

```
IRP System Buffer (NonPagedPoolNxCacheAligned)
┌─────────────────────────────────┐ ← buffer base
│  POOLTIER structure             │
│  (0xAB0+ bytes of user data)    │
│  ...                            │
│  +0xA78: numOfGuids = N         │
│  +0xA7C: offsetGuids = 0xFFF0  │ ← attacker-controlled (beyond buffer)
│  ...                            │
├─────────────────────────────────┤ ← end of buffer
│  ADJACENT KERNEL POOL DATA      │ ← LEAKED via SpIdsCopyHelper
│  (may contain kernel pointers,  │
│   object headers, etc.)         │
└─────────────────────────────────┘
```

### Retrieving Leaked Data

After `SpIoctlCreateTier` creates the tier with the out-of-bounds GUID data, the attacker
retrieves it via IOCTL 0xE71408 (`SpIoctlGetTierInfo`), which returns the full POOLTIER
structure including the leaked GUIDs — no access check required.

### The Patch

The patch adds two checks:

```c
// POST-PATCH: Safe addition + bounds check
ULONG totalGuidSize = numOfGuids * sizeof(GUID);  // 16 bytes each
ULONG endOffset;

// 1. Safe integer addition (prevents overflow)
NTSTATUS status = RtlULongAdd(offsetGuids, totalGuidSize, &endOffset);
if (!NT_SUCCESS(status))
    return status;

// 2. Bounds check against buffer length
if (endOffset > buffer_length)
    return STATUS_INVALID_PARAMETER;

// Now safe to copy
SpIdsCopyHelper(buffer + offsetGuids, numOfGuids, &sdb_tier->guidList);
```

## IOCTL Interface

### Key IOCTLs

| IOCTL Code | Function | Access Check | Purpose |
|---|---|---|---|
| 0xE70004 | SpIoctlGetPools | None | List all pool GUIDs |
| 0xE70008 | SpIoctlGetPoolInfo | None | Get pool details (name, desc) |
| 0xE7D410 | SpIoctlCreateTier | SpAccessCheckPool | Create tier (VULNERABLE) |
| 0xE7D414 | SpIoctlDeleteTier | SpAccessCheckPool | Delete tier |
| 0xE71408 | SpIoctlGetTierInfo | None | Get tier info (LEAK RETRIEVAL) |

### POOLTIER Structure

```c
typedef struct {
    int length_bis;              // +0x00
    int length;                  // +0x04
    GUID PoolGUID;               // +0x08
    GUID TierGUID;               // +0x18
    GUID spaceGUID;              // +0x28
    wchar_t friendlyName[256];   // +0x38
    wchar_t description[1024];   // +0x238
    int usage;                   // +0xA38
    int field_A3C;               // +0xA3C
    // ... more fields ...
    int mediatype;               // +0xA68
    int faultDomainAwareness;    // +0xA70
    int AllocationUnitSize;      // +0xA74
    int numOfGuids;              // +0xA78  ← attacker-controlled count
    int offsetGuids;             // +0xA7C  ← attacker-controlled offset (BUG)
    // ... more fields ...
    char additionalData[];       // variable length at end
} POOLTIER;
```

### Access Control

The vulnerability requires passing `SpAccessCheckPool`:

- Checks against `SpControlExt` object's security descriptor
- Default: only local Administrators and SYSTEM can pass
- However, pool security descriptors can be modified via WMI:
  ```powershell
  Invoke-WmiMethod -path '<pool_path>' -name SetSecurityDescriptor `
    -ArgumentList "O:BAG:SYD:(A;;FA;;;WD)(A;;FA;;;SY)(A;;FA;;;BA)(A;;FA;;;<user_sid>)"
  ```
- If an admin grants pool access to a user, that user can trigger the vulnerability

### Additional Patched Functions

Two other functions had the same missing bounds check:

1. **SP_POOL::SetTierInfo** — similar offset/count validation added
2. **SP_POOL::SetSpaceInfoTransaction** — similar offset/count validation added

## Reachability

- **Attack vector**: Local — requires code execution on the target
- **Privileges required**: Low (requires pool access — admin by default, grantable to users)
- **User interaction**: None
- **Prerequisites**:
  - Storage Spaces enabled (requires 3+ disks, or virtual disks)
  - A pool must exist that the attacker has access to
  - Cannot create tiers on the primordial pool
- **Information leaked**: Kernel pool memory adjacent to IRP system buffer
  - May contain kernel pointers, object headers, pool metadata
  - Buffer allocated in NonPagedPoolNxCacheAligned, appears page-aligned
  - Leak content depends on pool layout at time of IOCTL

## Detection

### YARA — Exploit Artifacts

```yara
rule CVE_2022_21877_Exploit_Indicators
{
    meta:
        description = "Detects compiled exploit artifacts for CVE-2022-21877 spaceport.sys"
        cve         = "CVE-2022-21877"
        author      = "OnlyFm252"

    strings:
        $ioctl_create = { 10 D4 E7 00 }
        $ioctl_get    = { 08 14 E7 00 }
        $ioctl_pools  = { 04 00 E7 00 }
        $s_spaceport  = "spaceport" ascii wide nocase
        $s_storagep   = "StoragePool" ascii wide nocase

    condition:
        uint16(0) == 0x5A4D and
        $ioctl_create and
        ($ioctl_get or $ioctl_pools) and
        ($s_spaceport or $s_storagep)
}
```

### Sigma — Suspicious Storage Spaces Controller Access

```yaml
title: CVE-2022-21877 Storage Spaces Controller Information Disclosure
id: c2d3e4f5-a6b7-8901-cdef-234567890abc
status: experimental
description: >
    Detects potential exploitation of CVE-2022-21877 via suspicious
    Storage Spaces driver tier operations from non-system processes.
author: OnlyFm252
date: 2026/07/26
references:
    - https://big5-sec.github.io/posts/an-analysis-of-cve-2022-21877/
    - https://github.com/Big5-sec/cve-2022-21877
    - https://msrc.microsoft.com/update-guide/vulnerability/CVE-2022-21877
logsource:
    product: windows
    category: driver_load
detection:
    selection:
        ImageLoaded|endswith: '\spaceport.sys'
    condition: selection
level: low
tags:
    - attack.discovery
    - attack.t1082
    - cve.2022.21877
```

### Sysmon Configuration

```xml
<Sysmon schemaversion="4.90">
  <EventFiltering>
    <!-- Event 7: Detect spaceport.sys driver load -->
    <DriverLoad onmatch="include">
      <ImageLoaded condition="end with">\spaceport.sys</ImageLoaded>
    </DriverLoad>

    <!-- Event 1: Detect processes that may interact with Storage Spaces -->
    <ProcessCreate onmatch="include">
      <CommandLine condition="contains">StoragePool</CommandLine>
    </ProcessCreate>
  </EventFiltering>
</Sysmon>
```

## References

- [big5-sec — An Analysis of CVE-2022-21877](https://big5-sec.github.io/posts/an-analysis-of-cve-2022-21877/)
- [Big5-sec — CVE-2022-21877 PoC](https://github.com/Big5-sec/cve-2022-21877)
- [ZDI Advisory — ZDI-22-048](https://www.zerodayinitiative.com/advisories/ZDI-22-048/)
- [Synacktiv — Windows LPE via Storage Spaces](https://www.synacktiv.com/sites/default/files/2021-10/2021_sthack_windows_lpe.pdf)
- [MSRC Advisory — CVE-2022-21877](https://msrc.microsoft.com/update-guide/vulnerability/CVE-2022-21877)
