# clfs.sys Patch Diff — CVE-2023-28252

| | |
|---|---|
| Binary | clfs.sys (Common Log File System) |
| Pre-patch version | 10.0.22621.1265 |
| Post-patch version | 10.0.22621.1555 |
| KB | KB5025239 (Windows 11 22H2) |
| CVE | CVE-2023-28252 — Out-of-Bounds Read/Write (CWE-787), Elevation of Privilege, CVSS 7.8 |
| Exploited | **Yes — exploited in the wild (Nokoyawa ransomware)** |
| Diff tool | ghidriff (Ghidra VersionTrackingDiff engine) |
| Functions changed | 13 with code changes (out of 2,867 matched, 99.2% avg similarity) |
| Functions added | 5 (WPP trace helpers) |
| Independent analysis | [Google Project Zero — 0-day RCA](https://googleprojectzero.github.io/0days-in-the-wild/0day-RCAs/2023/CVE-2023-28252.html) by Genwei Jiang (FLARE OTF) |

## Summary

The fix closes an **out-of-bounds read/write** vulnerability in the
Windows Common Log File System (CLFS) driver that allows an unprivileged
local attacker to escalate privileges to SYSTEM. A crafted BLF (Base Log
File) can set the `iExtendBlock` and `iFlushBlock` fields in the control
record shadow to **0x13** — far beyond the valid range of block indices
(normally 2–5). When `CClfsBaseFilePersisted::ExtendMetadataBlock` or
`WriteMetadataBlock` uses these indices to compute offsets into the
metadata block descriptor array, it reads and writes **out of bounds**
into adjacent pool allocations. The OOB write in `WriteMetadataBlock`
performs a **1-byte increment** at a controlled offset, which the exploit
uses to redirect a `CONTAINER_CONTEXT` pointer to a fake object with a
user-space vtable at `0x5000000`.

The fix is a **broad hardening** of BLF format validation, gated behind
the `g_signatureOffsetsValidation` global (WIL feature flag). The two
functions cited by the P0 RCA — `GetControlRecord` (tripled in size) and
`WriteMetadataBlock` — received the most critical changes, but 11 other
functions were also hardened.

**Confidence:** high. The ghidriff output confirms the P0 RCA's
description of added validation in `GetControlRecord` and
`WriteMetadataBlock`. The new `iExtendBlock`/`iFlushBlock` range checks
in `GetControlRecord` directly prevent the 0x13 index that triggers the
OOB.

**Credit.** Boris Larin (oct0xor) with Kaspersky, Genwei Jiang with
FLARE OTF of Google Cloud + Mandiant, Quan Jin with DBAPPSecurity WeBin
Lab.

## Product background

`clfs.sys` is the kernel driver implementing the Common Log File System,
a general-purpose logging subsystem used by TxF, Active Directory,
MSDTC, and other Windows components. CLFS operates on Base Log Files
(`.blf`) that contain a control record, general metadata, scratch
blocks, and container descriptors. The control record includes fields
like `iExtendBlock` and `iFlushBlock` that index into an array of
metadata block descriptors. These fields select which scratch block is
"active" for extend and flush operations.

CLFS has been a repeated source of EoP vulnerabilities — CVE-2023-28252
is closely related to the earlier CVE-2023-23376 (February 2023) and
shares exploitation strategies with CVE-2022-37969.

## Functions changed

### CClfsBaseFile::GetControlRecord (the critical validation)

| | |
|---|---|
| Change type | code, length, address, called |
| Similarity | 0.25 (b_ratio) |
| Length | 299 → 891 bytes (3x growth) |
| Fix pattern | BLF field range validation added |

This function tripled in size. The post-patch version adds multiple
layers of validation behind `g_signatureOffsetsValidation`:

**New check 1 — block size consistency** (`g_signatureOffsetsValidation
& 0x80`): verifies that `sectorCount * 0x200` equals the expected block
size. If mismatched, returns `STATUS_LOG_CORRUPT`.

**New check 2 — iExtendBlock/iFlushBlock range** (`g_signatureOffsetsValidation
& 0x10`): validates that:
- `eExtendState != 0` implies `iExtendBlock` and `iFlushBlock` are
  non-zero
- `iExtendBlock` is 2 or 3 (enforced by `(iExtendBlock - 2) & 0xFFFD
  == 0`)
- `iFlushBlock >= iExtendBlock`
- Both indices are less than the total block count

This directly prevents the exploit's technique of setting both fields
to **0x13**, which is far outside the valid 2–5 range.

**New check 3 — block descriptor monotonicity**: iterates through up to
6 block descriptors at offset +0x58 (stride 0x18) and verifies that
their offsets are monotonically increasing and don't overflow via
`RtlULongAdd`.

### CClfsBaseFilePersisted::WriteMetadataBlock (encode validation)

| | |
|---|---|
| Change type | code, length, address, called |
| Similarity | 0.73 (b_ratio) |
| Length | 627 → 727 bytes |
| Fix pattern | Return value check + conditional decode |

Before (pre-patch — vulnerable):

```c
ClfsEncodeBlock(p_Var3, sectorCount << 9, dumpCount, 0x10, 1);
// Return value ignored — always proceeds to WriteSector
WriteSector(...);
// Always decodes in cleanup
ClfsDecodeBlock(p_Var3, ...);
```

After (post-patch — fixed):

```c
local_54 = ClfsEncodeBlock(p_Var3, sectorCount << 9, dumpCount, 0x10, 1);
if ((g_signatureOffsetsValidation & 0x20) != 0) {
    if (local_54 < 0) {
        WPP_SF_sl(..., "CClfsBaseFilePersisted::WriteMetadataBlock");
        goto LAB_0;  // skip WriteSector
    }
    bVar4 = true;  // encode succeeded
}
WriteSector(...);
// Cleanup: only decode if encode actually succeeded
if (((g_signatureOffsetsValidation & 0x20) == 0) || (bVar4)) {
    ClfsDecodeBlock(...);
}
```

The fix ensures that if `ClfsEncodeBlock` fails (returns negative —
indicating corruption in the metadata block), the write is aborted
rather than proceeding with corrupted data.

### CClfsLogFcbPhysical::ValidateScratchBlockOffsets (massive hardening)

| | |
|---|---|
| Change type | code, fullname, length, sig, address, called |
| Similarity | 0.14 (b_ratio) |
| Length | 743 → 1,519 bytes (2x growth) |
| Fix pattern | Moved to FCB level; extensive bounds validation |

Moved from `CClfsBaseFile::` to `CClfsLogFcbPhysical::`. Signature
expanded from 2 to 4 parameters (adds `_CLFS_TRUNCATE_CONTEXT *` and
`_FILE_OBJECT *`). Now performs:

- Verifies scratch block sector count matches base block size
- Validates `cExtendStartSectors == 0x10` exactly
- Validates `cExtendSectors > 0x10` and within block bounds
- Checks owner page offsets don't overflow (`RtlULongSub`,
  `+ 0x1000` and `+ 0x230` overflow checks)
- Reads the scratch block from disk and validates sector counts
- Verifies owner pages are monotonically increasing
- 12+ distinct error paths with WPP tracing

### CClfsBaseFile::AcquireMetadataBlock (null check)

| | |
|---|---|
| Change type | code, length, address, called, calling |
| Similarity | 0.58 (b_ratio) |
| Length | 119 → 209 bytes |

Adds post-acquisition validation: if `g_signatureOffsetsValidation` bit
7 is set and the metadata block pointer is NULL after the vtable-based
load, returns `STATUS_LOG_CORRUPT` instead of proceeding with a null
pointer.

New caller: `ValidateScratchBlockOffsets` (which now acquires block 4
before validation).

### Other hardened functions

- **`CClfsLogFcbPhysical::AppendRegion`** (b_ratio 0.39, 14 refs) —
  significant restructuring for bounds checking
- **`ClfsDecodeBlockPrivate`** (b_ratio 0.77) — validation of sector
  signatures during decode
- **`CClfsBaseFileSnapshot::CopyImage`** (b_ratio 0.88) — bounds checks
  on snapshot copy
- **`CClfsLogFcbPhysical::SetEndOfLog`** (b_ratio 0.93) — minor
  validation
- **`CClfsLogFcbPhysical::TruncateLogRewriteOwnerPages`** (b_ratio
  0.95) — offset validation
- **`CClfsLogFcbPhysical::RecoverTruncateLog`** (b_ratio 0.89) —
  recovery path validation
- **`CClfsLogFcbPhysical::FlushMetadata`** (b_ratio 0.99) — minimal
- **`WriteMetadataBlock::__l1::fin$0`** (b_ratio 0.81) — exception
  handler updated

## Exploitation

This vulnerability was **exploited in the wild** by the Nokoyawa
ransomware group and patched in April 2023. The P0 RCA documents the
full exploit flow:

1. Create a target BLF file with a `CONTAINER_CONTEXT` at offset
   `0x1470` and a **fake** `CONTAINER_CONTEXT` at offset `0x1570`
   (containing user-space vtable pointer `0x5000000`)
2. Create 10 triggering BLF files with `iExtendBlock = iFlushBlock =
   0x13` and `eExtendState = ClfsExtendStateFlushingBlock`
3. Spray non-paged pool via named pipes, filling pipe data with the
   target BLF file's kernel object address
4. Create holes by closing specific pipe handles, then fill holes by
   opening the triggering BLF files (`CreateLogFile`)
5. Trigger OOB via `AddLogContainer` → `ExtendMetadataBlock` →
   `WriteMetadataBlock`, causing a **1-byte increment** of
   `rgContainers[0]` in the target BLF object
6. The increment changes `rgContainers[0]` from `0x1470` to `0x1570`,
   redirecting it to the fake `CONTAINER_CONTEXT` with vtable at
   `0x5000000`
7. Detonate via `CreateLogFile` on the target BLF — CLFS follows the
   fake vtable

**Windows 10 exploit:** Places `nt!RtlClearBit` at vtable+0x28 to
clear `PreviousMode` bit in `KTHREAD`, enabling `NtWriteVirtualMemory`
for arbitrary kernel R/W → token swap.

**Windows 11 exploit:** Uses `nt!PoFxProcessorNotification` →
`nt!SeSetAccessStateGenericMapping` gadget chain to corrupt pipe
attributes for arbitrary kernel R/W → token swap.

---

<sub>Source: ghidriff diff of clfs-2023-03.sys (10.0.22621.1265,
pre-patch) vs clfs-2023-04.sys (10.0.22621.1555, post-patch) —
[download pre](/data/patch_diffs/binaries/clfs-2023-03.sys) /
[download post](/data/patch_diffs/binaries/clfs-2023-04.sys).
Reference: [Google Project Zero RCA](https://googleprojectzero.github.io/0days-in-the-wild/0day-RCAs/2023/CVE-2023-28252.html),
[Kaspersky — Nokoyawa ransomware](https://securelist.com/nokoyawa-ransomware-attacks-with-windows-zero-day/109483/).</sub>
