CVE-2024-49138 — CLFS Heap-Based Buffer Overflow via Shadow Block pbImage Sharing

Last updated: 2026-04-10
Component: clfs.sys
Bug Class: Heap-based buffer overflow — shadow block pbImage shares primary block’s memory; reference count discrepancy leads to invalid General Metadata Block with controlled pContainer
Patch: December 2024 Patch Tuesday
Exploited ITW: Yes (December 2024 disclosure)
Discoverer: HN Security
Related: Clfs, Cve 2022 37969, Heap Grooming
Tags: clfs, oob-write, shadow-block, pbimage, heap-overflow, kernel-mode, lpe


Summary

CVE-2024-49138 exploits a discrepancy between how primary and shadow metadata blocks are managed in CLFS. The primary and shadow General Metadata Blocks share the same pbImage pointer (same kernel memory), but have different reference counts (primary ≥ 1, shadow = 0). When ExtendMetadataBlock calls WriteMetadataBlock on the shadow block and it fails (via crafted BLF), ClfsReleaseMetadataBlock for the shadow checks the reference count, finds it is 0, and returns without decrementing — but the primary block still has its reference count inflated. The resulting state: an invalid General Metadata Block with reference count > 0 (preventing cleanup) and pContainer pointing to attacker-controlled user-mode memory.


Background: Shadow Block Reference Count Design

// AcquireMetadataBlock — only called for primary blocks (not shadow)
// m_rgcBlockReferences:
//   Primary block (General = index 2): count starts at 1, incremented by AcquireMetadataBlock
//   Shadow block  (GeneralShadow = index 3): count is ALWAYS 0

// Both m_rgBlocks[2].pbImage and m_rgBlocks[3].pbImage point to same allocation

ExtendMetadataBlock calls AcquireMetadataBlock only on primary blocks (even indices). It then loops over ALL blocks (including shadow, odd indices) for WriteMetadataBlock. This asymmetry is the root cause.


Root Cause: WriteMetadataBlock Failure on Shadow Frees Nothing

ExtendMetadataBlock:
  AcquireMetadataBlock(GeneralBlock)    → m_rgcBlockReferences[2] = 2 (already 1, now +1)
  WriteMetadataBlock(GeneralBlock)      → succeeds, m_rgcBlockReferences[2] stays 2
  WriteMetadataBlock(GeneralShadow)     → FAILS (crafted BLF triggers this)
    → WriteMetadataBlock → ClfsEncodeBlock → invalidates block (checksum = 0)
    → ClfsDecodeBlock fails (block now invalid)
    → ClfsReleaseMetadataBlock(shadow)
      → reads m_rgcBlockReferences[3] = 0 → returns immediately (no decrement!)
    → WriteMetadataBlock returns error
  → ExtendMetadataBlock fails and returns
  → exit path: ReleaseMetadataBlock(GeneralBlock) → m_rgcBlockReferences[2] = 1 (> 0!)
  
Result: GeneralBlock is INVALID (tampered pbImage/pContainer) and reference count = 1 (won't be freed)

Triggering the Failure: DumpCount Parity Attack

The exploit crafts the BLF so that WriteMetadataBlock on the shadow block fails:

  • Set ullDumpCount to an odd value in the General Metadata Block
  • First WriteMetadataBlock (on primary General block): odd ullDumpCount → just increments → becomes even; Usn NOT changed
  • Second WriteMetadataBlock (on shadow, same pbImage): even ullDumpCountUsn IS incremented
  • ClfsDecodeBlock fails (Usn mismatch) → triggers ClfsReleaseMetadataBlock with refcount 0 → no-op
  • Shadow write failure causes ExtendMetadataBlock to fail
  • Primary block survives in memory: tampered with controlled pContainer

Making pContainer Controllable

Simultaneously, the SignaturesOffset field is modified so that the signatures array overlaps the CLFS_CONTAINER_CONTEXT structure in the block:

  • During WriteMetadataBlock failure path, the sector signature bytes are written into pContainer at CLFS_CONTAINER_CONTEXT+0x18
  • The sector signature bytes are attacker-controlled (from crafted BLF sectors)
  • Result: pContainer = attacker_controlled_address

Exploitation Path via ScanContainerInfo

After establishing an invalid General Metadata Block with controlled pContainer, subsequent API calls that traverse container records will dereference the fake pointer.

Target API: CreateLogContainerScanContext() → kernel CClfsBaseFile::ScanContainerInfo()

CClfsBaseFile::ScanContainerInfo(...) {
    BaseLogRecord = GetBaseLogRecord(this);
    while (v16 < containerCount) {
        v21 = BaseLogRecord->rgContainers[v20];
        GetSymbol(this, v21, v20, &v41);  // retrieves CLFS_CONTAINER_CONTEXT
        CClfsContainer::QueryContainerInfo(v41->pContainer, v24);  // UAF/OOB dereference
    }
}

v41->pContainer is the attacker-controlled value → QueryContainerInfo dispatches through vtable → arbitrary dispatch.


Crafted BLF Characteristics

FieldValuePurpose
Usn1Odd starting value for DumpCount parity trigger
ullDumpCount1 (odd)Triggers DumpCount parity mechanism
SignaturesOffsetModifiedSignatures array overlaps CLFS_CONTAINER_CONTEXT
CLFS_CONTAINER_CONTEXT.pContainerShifted to overlap sector signatureBecomes attacker-controlled address
cbSymbolZoneLarge valueForces AddSymbol → ExtendMetadataBlock path
rgContainer[] offsetUpdatedPoints to new CONTAINER_CONTEXT location
CRC32 checksumRecomputedPasses checksum validation

Key Difference from CVE-2023-28252

AspectCVE-2023-28252CVE-2024-49138
TriggerValidSectorCount=1 → ClfsEncodeBlock returns errorDumpCount parity → shadow write failure
Shadow selectionShadow block selected due to corrupted primary checksumShadow write fails without corrupting primary checksum
Root causeIgnored return value of ClfsEncodeBlockReference count discrepancy between primary/shadow
Block typeCONTROL blockGENERAL block
OOB indexiExtendBlock/iFlushBlock OOB m_rgBlockspContainer controls subsequent container scan

Python CRC32 Utility (from HN Security PoC)

import binascii

def calculate_crc32(input_file):
    with open(input_file, "r") as f:
        hex_data = f.read().strip()
    hex_numbers = hex_data.split()
    byte_array = bytes(int(num, 16) for num in hex_numbers)
    crc32_checksum = binascii.crc32(byte_array) & 0xFFFFFFFF
    little_endian_hex = ' '.join(f"{b:02X}" for b in crc32_checksum.to_bytes(4, 'little'))
    print(f"CRC32 (LE): {little_endian_hex}")

References

  • HN Security — “CVE-2024-49138: Windows CLFS Heap-Based Buffer Overflow Analysis” (Parts 1+2) — hnsecurity.it, 2025-01
  • Microsoft MSRC — CVE-2024-49138 advisory — December 2024