# CVE-2022-24521 — Windows CLFS `clfs.sys` EoP via Indirect-Call on Attacker-Controlled `pContainer` (Signatures/Context Overlap Logical Error)

---

## Summary

| **Product**           | Microsoft Windows — `clfs.sys` (Common Log File System kernel driver) |
|-----------------------|-----------------------------------------------------------------------|
| **Vendor**            | Microsoft Corporation |
| **Severity**          | High (exploited in the wild as a 0-day) |
| **Affected Versions** | Windows 7 / 8.1 / 10 / 11 and Server 2008–2022, before the April 12, 2022 security updates |
| **Tested Version**    | Windows 11 21H2 — clfs.sys 10.0.22000.556 (pre-patch, KB5011493) vs 10.0.22000.613 (post-patch, KB5012592) |
| **Impact**            | Elevation of Privilege — arbitrary kernel indirect call → RIP control → SYSTEM |
| **CVE ID**            | CVE-2022-24521 |
| **CWE**               | CWE-822 (Untrusted Pointer Dereference) / logical error: lack of indirect-call validation |
| **Bug class**         | Logical error — attacker-controlled kernel pointer restored through encode/decode cycle, then called |
| **PoC Available**     | Trigger PoC below (crafted `.blf` generator) — blue-team/detection grade |
| **Exploit Available** | Exploited ITW (NSA + CrowdStrike report); no public full exploit |
| **Patch Available**   | Yes — April 12, 2022 (KB5012592 on Win11 21H2; KB5012599/KB5012647 etc. on other SKUs) |
| **Diff-verified**     | ghidriff: 2 added (`CClfsBaseFile::ValidateRgOffsets` @ `0x1c00274bc`, `Feature_Servicing_38197809__private_IsEnabled` @ `0x1c000c8d4`), 8 code-changed; `LoadContainerQ` 2,927→3,160 bytes |
| **Reporter(s)**       | NSA; Adam Podlosky & Amir Bazine (CrowdStrike) |
| **RCA source**        | Project Zero 0-days-in-the-wild RCA (Sergey Kornienko, PixiePoint Security) |

---

## CVSS 3.1 Detailed Scoring

**Base Score:** 7.8 (HIGH)
**Vector String:** `CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H`

| **Metric** | **Value** | **Rationale** |
|---|---|---|
| **Attack Vector (AV)** | Local | Attacker must run code / place a crafted `.blf` and open it |
| **Attack Complexity (AC)** | Low | Deterministic once the BLF layout trick is built; no race |
| **Privileges Required (PR)** | Low | Any standard user may create/open CLFS logs |
| **User Interaction (UI)** | None | — |
| **Scope (S)** | Unchanged | Kernel driver runs in the victim's own security scope |
| **Confidentiality (C)** | High | Full kernel memory disclosure achievable post-exploitation |
| **Integrity (I)** | High | Arbitrary kernel write via the call primitive |
| **Availability (A)** | High | BSOD possible; exploit yields full control |

---

## Product Description

`clfs.sys` implements the Windows Common Log File System — a high-performance
transactional logging engine used by TxF/TxR, Active Directory, MSDTC,
EventLog, and user apps through the `clfsw32.dll` API. A CLFS log consists of
a **Base Log File (`.blf`)** holding control records, metadata blocks, client
and container **contexts**, plus container files holding the actual log
stream.

The `.blf` is a **file format parsed by the kernel**, and its metadata blocks
are protected by a sector-signature scheme: when a block is written,
`ClfsEncodeBlock` lifts two bytes out of every 512-byte sector into a
*signatures array* (located at `_CLFS_LOG_BLOCK_HEADER->SignaturesOffset`,
header `+0x68`); when read back, `ClfsDecodeBlock` writes them back into the
sectors. Because both the signatures array **and the contexts it may overlap**
come from attacker-controlled file bytes, any field the kernel temporarily
stores *inside* the image and *later trusts* is fair game — and that is
exactly what happens with `_CLFS_CONTAINER_CONTEXT->pContainer` (`+0x18`).

CLFS has been one of the most exploited Windows components:
CVE-2022-24521, CVE-2022-37969, CVE-2023-28252, CVE-2023-23376, and more.

---

## Vulnerability Summary

When a base log file is opened, `CClfsBaseFilePersisted::LoadContainerQ`
walks the container contexts embedded in the base log record. For each
context marked for removal (`cidContainer == -1`), the kernel — aware that
`pContainer` is file-controlled — **defensively zeroes
`containerContext->pContainer` before calling
`CClfsBaseFilePersisted::RemoveContainer`**. The intent: a user can never
supply a fake `pContainer` for the indirect calls inside `RemoveContainer`.

The defense is defeated by the encode/decode cycle *inside*
`RemoveContainer`'s own `FlushImage → WriteMetadataBlock` path:

1. `FlushImage` snapshots every valid context's `pContainer` into a side
   array (`this + 0x1C0 + i*8`) and **zeroes the in-image field**.
2. `ClfsEncodeBlock` runs — if the attacker placed the signatures array so it
   **overlaps the container context**, encoding copies attacker bytes from
   the signature slots *into* the zeroed `pContainer` region (the bytes were
   copied out of the sectors at encode time and land in the array; the
   attacker pre-arranged those sector bytes to be their fake pointer, so the
   decode in step 3 restores them over the zeroed field).
3. After the sector write, `ClfsDecodeBlock` restores sector bytes from the
   signatures array — repopulating `pContainer` with the attacker value.
4. Back in `FlushImage`, the restore loop tries to put the *real* saved
   pointer back via `CClfsBaseFile::AcquireContainerContext` — but if the
   attacker makes that call **fail** (trivial: corrupt a field the acquire
   validates), the real pointer is never restored and the fake one survives.
5. Execution returns to `RemoveContainer`, which now performs:

```c
pContainer = containerContext->pContainer;          // attacker-controlled
containerContext->pContainer = 0;
(*(void (**)(ULONG64))(*(ULONG64 *)pContainer + 0x18))(pContainer); // vtbl[+0x18] remove
(*(void (**)(ULONG64))(*(ULONG64 *)pContainer + 0x08))(pContainer); // vtbl[+0x08] release
```

A fully attacker-controlled `this` pointer **and** a fully
attacker-controlled virtual-call target → direct RIP control in kernel
context (with KASLR/CFG considerations handled by standard techniques —
ITW exploit reportedly used pipe-attribute pool grooming + an arbitrary
read built from a module gadget, per the P0 RCA).

**Root cause:** the signatures array offset (`SignaturesOffset`) was never
validated against the context objects it can overlap; a field the kernel
deliberately zeroed as a security measure could be silently resurrected by
the block encode/decode machinery.

---

## Prerequisites and Constraints

- Local authenticated session, standard user (no admin needed)
- CLFS is present on every Windows install — no optional feature required
- Attacker must write a **crafted `.blf`** to a user-writable path, then open
  it as a CLFS log
- The BLF must be crafted so that:
  - container context and the signatures array are adjacent/overlapping,
    such that copying context bytes to `SIG_0..SIG_X` and the encode/decode
    round-trip does not corrupt the context (format-level trick — see P0 RCA)
  - one container context has `cidContainer == -1` to arm `RemoveContainer`
  - a field that `CClfsBaseFile::AcquireContainerContext` validates is
    corrupted so the stage-3 restore fails
- No race condition; no special hardware; works from low-IL processes

---

## Vulnerability Details

### Reaching the bug from userspace (the "hint")

The load path is reached by simply **opening the crafted base log file** —
no exotic IOCTL crafting needed. Userspace APIs (all `clfsw32.dll`):

| API | Role |
|---|---|
| `CreateLogFile(L"LOG:\\path\\evil.blf", GENERIC_READ\|GENERIC_WRITE, ...)` | Opens the `.blf`; kernel parses the base log record → reaches `LoadContainerQ` — **this is the trigger** |
| `AddLogContainer` / `RemoveLogContainer` | Alternative paths that manipulate container contexts (useful while *building* a valid baseline BLF before corrupting it) |
| `WriteLogRestart` / `ReserveAndAppendLog` | Force metadata flushes (exercise the same `FlushImage`/`WriteMetadataBlock` encode path) |

IOCTL layer: `CreateLogFile` on a `LOG:`-prefixed path ends in an IRP to the
CLFS device; metadata-flush IOCTLs such as `0x8007681F`/`0x80076828`-family
(CLFS flush/scan) exercise `FlushImage` on an already-open log. The bug,
however, fires during the **open/load** itself, before any of those are
needed.

### Complete kernel call flow

```
User mode (clfsw32.dll):
  CreateLogFile("LOG:\\C:\\Users\\user\\evil.blf", GENERIC_READ|GENERIC_WRITE, 0, NULL, OPEN_ALWAYS, 0)
      │   NtCreateFile → I/O manager → CLFS device
      ▼
Kernel mode (clfs.sys):
  CClfsDriver::LogIoDispatch .................. IRP_MJ_CREATE / device dispatch
    → ClfsDispatchIoRequest ................... builds CClfsRequest
      → CClfsRequest::Dispatch ................ routes open/create
        → CClfsLogFcbPhysical::OpenLogFile .... opens FCB for the .blf
          → CClfsBaseFilePersisted::LoadImage . reads control record + base log record
            → CClfsBaseFilePersisted::LoadContainerQ   [*** BUGGY FUNCTION ***]
                │   for each container context in the base log record:
                │     if (context->cidContainer == -1) {
                │         context->pContainer = NULL;        // intended defense
                │         RemoveContainer(this, idx);
                │     }
                ├──→ CClfsBaseFilePersisted::RemoveContainer
                │      → CClfsBaseFilePersisted::FlushImage
                │          │  for i in 0..0x3FF:
                │          │    AcquireContainerContext(i,&ctx) // succeeds
                │          │    saved[i] = ctx->pContainer;      // snapshot
                │          │    ctx->pContainer = 0;             // zero in-image
                │          ├──→ WriteMetadataBlock
                │          │      → ClfsEncodeBlock  *** attacker bytes land in
                │          │            signatures array slots that OVERLAP the
                │          │            container context ***
                │          │      → CClfsContainer::WriteSector
                │          │      → ClfsDecodeBlock  *** pContainer region is
                │          │            repopulated with attacker value ***
                │          │  for i in 0..0x3FF:               // stage [3]
                │          │    if (saved[i] &&
                │          │        AcquireContainerContext(i,&ctx) >= 0)  // ← attacker
                │          │        ctx->pContainer = saved[i];            //   makes this FAIL
                │          └── (fake pContainer survives)
                │      pContainer = context->pContainer;      // ATTACKER VALUE
                │      context->pContainer = 0;
                │      (*pContainer->vtbl[0x18])(pContainer);  // ← RIP control #1 (remove)
                │      (*pContainer->vtbl[0x08])(pContainer);  // ← RIP control #2 (release)
                ▼
```

Surrounding functions worth reviewing beyond the diff (ghidriff may omit
context): `LoadContainerQ`'s caller `LoadImage`, the whole
`FlushImage`/`WriteMetadataBlock` pair, `ClfsEncodeBlock`/`ClfsDecodeBlock`
(they define *why* the overlap works), `AcquireContainerContext` (defines
*how* to make stage 3 fail), and `CClfsBaseFile::OffsetToAddr` (used by the
new validator).

### The patch (ghidriff-verified)

`LoadContainerQ` (`0x1c0036a90 → 0x1c0036ba0`, 2,927 → 3,160 bytes) gains a
validation gate behind `Feature_Servicing_38197809` (Win11 21H2 flag name as
verified in the diff; the P0 RCA shows the same check as
`Feature_Servicing_38197806` on another build):

```c
// post-patch LoadContainerQ — actual Ghidra decompilation (abridged)
_Src = BaseLogRecord + 0x328;                 // rgContainers[]
uVar10 = CClfsBaseFile::ContainerCount(this);
uVar25 = count_non_zero(_Src, 0x400);
...
else if (uVar25 == uVar10) {
    if (!Feature_Servicing_38197809__private_IsEnabled()) {
        // legacy path (flag off)
        scratch = operator_new(0x1000, PagedPool);
        memcpy(scratch, _Src, 0x1000);
        status = CClfsBaseFile::ValidateContainerOffsets(this, scratch, 0x1000);
    } else {
        // NEW CVE-2022-24521 validation (flag on)
        scratch = operator_new(0x11f0, PagedPool);      // 0x1000 + 0x1f0
        memcpy(scratch, _Src, 0x1000);
        // copy 0x1f0 bytes of the record's object-offset extension
        // from BaseLogRecord + 0x138 into scratch + 0x1000 (unrolled)
        status = CClfsBaseFile::ValidateRgOffsets(this, scratch, 0x1000);  // NEW
        operator_delete(scratch);
    }
    // fail load with STATUS_LOG_METADATA_INVALID (0xC01A000D) on error
}
```

`ValidateRgOffsets` (new function @ `0x1c00274bc`, 267 bytes, called only
from `LoadContainerQ`): sorts the record's object offsets with
`qsort(..., CompareOffsets)`, resolves each with `OffsetToAddr`, validates
`CLFS_NODE_ID` (`0xC1FDF007` container context → span `0x88`;
`0xC1FDF008` client context → span `0x30`), checks consecutive-object range
consistency, and enforces **every context object's span must end at or below
`LogBlock + SignaturesOffset`** — the signatures array may no longer
intersect any context object, so encode/decode can never write attacker
bytes into `pContainer`. Violations return `0xC01A000D`
(`STATUS_LOG_METADATA_INVALID`).

The same update also hardens `CClfsLogFcbVirtual::Open`
(`0x1c0042b40 → 0x1c0042d40`, +72 bytes), which now calls `KeBugCheckEx` on
an internal consistency failure — defense-in-depth shipped alongside the
CVE fix (moderate confidence it is related hardening, not a second CVE).

---

## Exploitation Scenario (from P0 RCA, no ITW sample available)

1. Craft `evil.blf`: container context adjacent to/overlapping the
   signatures array; sector bytes pre-arranged so encode → decode restores a
   fake kernel pointer into `pContainer`; one context has
   `cidContainer = -1`; acquire-validation field corrupted so stage-3
   restore fails.
2. Groom the paged pool with pipe objects / `PipeAttribute` entries
   (SSTIC 2020 "Scoop the Windows 10 pool" technique).
3. Leak kernel addresses with `NtQuerySystemInformation`
   (`SystemHandleInformation`) — pipe object addresses + a gadget-module base.
4. `CreateLogFile` on `evil.blf` → the indirect call hits a stack/gadget
   pivot; build an arbitrary-read, then overwrite the exploit process token
   (second trigger) → **SYSTEM**.

---

## Trigger PoC

See [`poc/poc_cve_2022_24521.c`](/data/patch_diffs/poc/poc_cve_2022_24521.c).
It **generates a structurally-crafted BLF** (signatures array overlapping
container context, `cidContainer = -1`, sabotaged acquire-validation field,
fake `pContainer = 0x4141414141414141`) and opens it with `CreateLogFile`:

- **Pre-patch (vulnerable) system:** `RemoveContainer` dereferences
  `0x4141414141414141` → `KMODE_EXCEPTION_NOT_HANDLED` bugcheck in a test VM.
  Run only inside a snapshotted VM.
- **Patched system:** `ValidateRgOffsets` rejects the file with
  `STATUS_LOG_METADATA_INVALID` (`0xC01A000D`) and `CreateLogFile` fails
  cleanly — no crash.

This is a detector/validator for blue teams (YARA/Sigma/Sysmon tuning), not
a weaponized exploit.

---

## Detection Rules

### YARA — crafted BLF / PoC artifacts

```yara
rule CVE_2022_24521_CLFS_Crafted_BLF {
    meta:
        description = "Detects crafted CLFS base log files targeting CVE-2022-24521 (signatures array overlapping container context)"
        author = "OnlyFm252"
        date = "2026-07-19"
        cve = "CVE-2022-24521"
        tlp = "white"
    strings:
        // CLFS control record / base log record magic
        $blf_magic = { 0E 00 00 C1 F5 F0 D0 C1 }   // CLFS_CONTROL_RECORD-ish header bytes
        // Container context node id 0xC1FDF007 appearing with cidContainer = -1 nearby
        $container_ctx = { 07 F0 FD C1 }
        $client_ctx    = { 08 F0 FD C1 }
        // PoC marker fake pointer
        $fake_ptr = { 41 41 41 41 41 41 41 41 }
        $marker = "CVE-2022-24521" ascii wide
    condition:
        uint32(0) != 0 and filesize < 16MB and
        ($container_ctx and ($fake_ptr or $marker)) or
        ($marker and $client_ctx)
}

rule CVE_2022_24521_CLFS_PoC_Binary {
    meta:
        description = "Detects compiled PoC tooling for CVE-2022-24521"
        author = "OnlyFm252"
        date = "2026-07-19"
        cve = "CVE-2022-24521"
    strings:
        $api1 = "CreateLogFile" ascii wide
        $api2 = "AddLogContainer" ascii wide
        $api3 = "RemoveLogContainer" ascii wide
        $api4 = "WriteLogRestart" ascii wide
        $log  = "LOG:\\" ascii wide nocase
        $blf  = ".blf" ascii wide nocase
        $ctx  = { 07 F0 FD C1 }
    condition:
        uint16(0) == 0x5A4D and filesize < 1MB and
        $api1 and ($api2 or $api4) and ($log or $blf) and $ctx
}
```

### Sigma — suspicious CLFS log open from user-writable paths

```yaml
title: Suspicious CLFS Base Log File Creation or Open (CVE-2022-24521)
id: 7c1f2a90-2452-1cde-9ab1-202204120001
status: experimental
description: >
    Detects creation of or access to .blf base log files in user-writable
    locations by non-system processes. CVE-2022-24521 is triggered by
    opening a crafted .blf with CreateLogFile; legitimate CLFS logs are
    almost always created by system components under system directories.
author: OnlyFm252
date: 2026/07/19
references:
    - https://msrc.microsoft.com/update-guide/vulnerability/CVE-2022-24521
    - https://googleprojectzero.github.io/0days-in-the-wild/0day-RCAs/2022/CVE-2022-24521.html
logsource:
    category: file_event
    product: windows
detection:
    selection_ext:
        TargetFilename|endswith: '.blf'
    filter_system:
        TargetFilename|startswith:
            - 'C:\Windows\'
            - 'C:\Program Files\'
            - 'C:\Program Files (x86)\'
        Image|startswith:
            - 'C:\Windows\System32\'
            - 'C:\Windows\SysWOW64\'
    condition: selection_ext and not filter_system
falsepositives:
    - CLFS-aware backup software
    - Custom applications using the CLFS API legitimately
level: high
tags:
    - attack.privilege_escalation
    - attack.t1068
    - cve.2022.24521
```

### Sigma — crash signature of a failed exploit attempt

```yaml
title: CLFS Driver Bugcheck After User-Context Log Open (CVE-2022-24521)
id: 8d2a3b01-2452-2def-8bc2-202204120002
status: experimental
description: >
    Correlates a user-mode process opening a .blf followed by a system
    bugcheck (Event 1001/41) referencing clfs.sys — the signature of a
    CVE-2022-24521 attempt hitting a mitigated or partially-crafted trigger.
author: OnlyFm252
date: 2026/07/19
logsource:
    product: windows
    service: system
detection:
    selection:
        EventID: 1001
        Provider_Name: 'Microsoft-Windows-WER-SystemErrorReporting'
    keywords:
        - 'clfs.sys'
        - 'KMODE_EXCEPTION_NOT_HANDLED'
    condition: selection and keywords
level: critical
tags:
    - attack.privilege_escalation
    - attack.t1068
    - cve.2022.24521
```

### Sysmon configuration

```xml
<!-- Sysmon rules for CVE-2022-24521 detection -->
<RuleGroup name="CVE-2022-24521" groupRelation="or">

  <!-- .blf creation outside system dirs -->
  <FileCreate onmatch="include">
    <Rule name="CLFS_BLF_UserDir" groupRelation="and">
      <TargetFilename condition="end with">.blf</TargetFilename>
      <TargetFilename condition="excludes">C:\Windows\</TargetFilename>
    </Rule>
  </FileCreate>

  <!-- clfsw32.dll loaded by non-system processes -->
  <ImageLoad onmatch="include">
    <Rule name="CLFS_DLL_Load" groupRelation="and">
      <ImageLoaded condition="end with">clfsw32.dll</ImageLoaded>
      <Image condition="excludes">C:\Windows\</Image>
      <Image condition="excludes">C:\Program Files\</Image>
      <Image condition="excludes">C:\Program Files (x86)\</Image>
    </Rule>
  </ImageLoad>

  <!-- File open of .blf (Event 11/23) by non-system image -->
  <FileCreateStreamHash onmatch="include">
    <Rule name="CLFS_BLF_Stream" groupRelation="and">
      <TargetFilename condition="end with">.blf</TargetFilename>
      <TargetFilename condition="excludes">C:\Windows\</TargetFilename>
    </Rule>
  </FileCreateStreamHash>

</RuleGroup>
```

---

## Remediation

1. **Apply the April 2022 cumulative update** (KB5012592 Win11 21H2;
   KB5012599/KB5012647/KB5012591 etc. per SKU). Verify
   `clfs.sys ≥ 10.0.22000.613` (or the equivalent per build).
2. **Deploy the Sysmon/Sigma rules above** — the `.blf` in user-writable
   paths + `clfsw32.dll` load heuristics catch the whole CLFS bug class, not
   just this CVE.
3. **Block or alert on `.blf` attachments** in mail/web gateways.
4. **Enable kernel mitigations** (HVCI, Kernel-mode CFG) — raises the bar for
   turning the indirect-call primitive into code execution.
5. **Hunt**: sweep endpoints for `.blf` files outside known system locations
   created before the patch date.

---

## Timeline

| Date | Event |
|---|---|
| 2022-04-12 | Microsoft patches CVE-2022-24521 (April 2022 Patch Tuesday) — ITW 0-day |
| 2022-2023 | Project Zero publishes 0-days-in-the-wild RCA |
| 2026-07-19 | This analysis: ghidriff diff of Win11 21H2 .556 → .613 + blue-team package |

---

## References

- [Project Zero — 0-days in the Wild RCA: CVE-2022-24521](https://googleprojectzero.github.io/0days-in-the-wild/0day-RCAs/2022/CVE-2022-24521.html)
- [PixiePoint Security — Analysing and Exploiting the CLFS Logical-Error Vulnerability](https://googleprojectzero.github.io/0days-in-the-wild/0day-RCAs/2022/CVE-2022-24521.html)
- [MSRC — CVE-2022-24521](https://msrc.microsoft.com/update-guide/vulnerability/CVE-2022-24521)
- [SSTIC 2020 — Scoop the Windows 10 pool! (pipe pool grooming)](https://www.sstic.org/2020/presentation/scoop_the_windows_10_pool/)
- [clfsw32 API — CreateLogFile](https://learn.microsoft.com/en-us/windows/win32/api/clfsw32/nf-clfsw32-createlogfile)
- [OnlyFm252 diff report — clfs_sys-kb5012592](/data/patch_diffs/clfs_sys-kb5012592.md)

---

<sub>Analysis: ghidriff (Ghidra VersionTrackingDiff, Ghidra 12.1.2, full MSDL
PDB symbols) of clfs-10.0.22000.556.sys (KB5011493, pre-patch) vs
clfs-10.0.22000.613.sys (KB5012592, post-patch), Windows 11 21H2 — 2,836/2,838
functions matched, 2 added, 8 code-changed. Patch block and validator
decompilation verified against the ghidriff report; vulnerable-path call flow
cross-checked against the Project Zero RCA. Raw diff report:
`ghidriff/CVE-2022-24521/ghidriffs/`.</sub>
