# clfs.sys Patch Diff — CVE-2022-24521

| | |
|---|---|
| Binary | clfs.sys (Common Log File System Driver) |
| Pre-patch version | 10.0.22000.556 (Windows 11 21H2, KB5011493, March 2022) |
| Post-patch version | 10.0.22000.613 (Windows 11 21H2, KB5012592, April 2022) |
| KB | KB5012592 |
| CVE | CVE-2022-24521 — Windows Common Log File System Driver Elevation of Privilege, CVSS 7.8, exploited in the wild |
| Diff tool | ghidriff (Ghidra VersionTrackingDiff, Ghidra 12.1.2, full PDB symbols) |
| Functions | 2,836 matched (99.93%); **2 added**; **8 modified with code changes**; 7 modified (no code changes) |
| Key new function | `CClfsBaseFile::ValidateRgOffsets` @ `0x1c00274bc` |
| Key changed function | `CClfsBaseFilePersisted::LoadContainerQ` @ `0x1c0036a90 → 0x1c0036ba0` |

## Summary

The fix closes a **logical error (lack of indirect-call validation)** in the
CLFS base-log-file load path. In the pre-patch driver,
`_CLFS_CONTAINER_CONTEXT->pContainer` (`+0x18`) lives inside the base log
record image and is round-tripped through `ClfsEncodeBlock`/`ClfsDecodeBlock`
during metadata writes. Because the on-disk *signatures array*
(`_CLFS_LOG_BLOCK_HEADER->SignaturesOffset`, header `+0x68`) is
attacker-controlled file content, a crafted `.blf` can overlap the signatures
array with a container context so the encode/decode cycle **restores
attacker-supplied bytes over the in-memory `pContainer` field after the
kernel defensively zeroed it** — turning the indirect calls in
`CClfsBaseFilePersisted::RemoveContainer` into arbitrary kernel RIP control.

The patch, verified by the ghidriff diff, adds a validation gate inside
`LoadContainerQ` behind the WIL servicing flag **`Feature_Servicing_38197809`**
(Win11 21H2; the P0 RCA shows the same check under `Feature_Servicing_38197806`
on a different build):

1. Count non-zero `rgContainers[]` slots (base log record `+0x328`) and
   compare against `CClfsBaseFile::ContainerCount`; proceed only if equal.
2. If `Feature_Servicing_38197809__private_IsEnabled()`: allocate a
   `0x11f0`-byte scratch buffer (`0x1000` for the copied `rgContainers`
   array + `0x1f0` for the record's object-offset extension copied from
   base log record `+0x138`), then call the **new**
   `CClfsBaseFile::ValidateRgOffsets(this, scratch, 0x1000)`.
   Any failure returns `STATUS_LOG_METADATA_INVALID` (`0xC01A000D`,
   `-0x3fe5fff3`) and the base log load is aborted.
   (With the flag off, the old path — `ValidateContainerOffsets` on the
   `rgContainers` copy only — is kept as fallback.)

`ValidateRgOffsets` (decompiled below) sorts the record's object offsets with
`qsort(..., CompareOffsets)`, resolves each with
`CClfsBaseFile::OffsetToAddr`, validates `CLFS_NODE_ID` values
(`0xC1FDF008` client context → span `0x30`; `0xC1FDF007` container context →
span `0x88`), checks consecutive-object range consistency, and enforces the
actual fix: **every context object's critical field range must end at or
below `LogBlock + SignaturesOffset`** — the signatures array can no longer
intersect any context object, so encode/decode can never resurrect attacker
bytes into `pContainer`.

The diff also shows a secondary hardening in `CClfsLogFcbVirtual::Open`,
which now calls `KeBugCheckEx` on an internal consistency failure
(new `called` entry; length 974 → 1046). Treat as defense-in-depth
accompanying the same update; moderate confidence it is CVE-related
hardening rather than an independent change.

**Confidence:** high — the new `SignaturesOffset` overlap check maps
one-to-one onto the root cause in the Project Zero RCA, and the flag-gated
insertion point in `LoadContainerQ` matches Microsoft's servicing pattern.

## Functions added

### CClfsBaseFile::ValidateRgOffsets — NEW @ `0x1c00274bc` (267 bytes)

Called only from `LoadContainerQ`; calls `OffsetToAddr` + `qsort`.
Actual post-patch decompilation (Ghidra, PDB symbols):

```c
long CClfsBaseFile::ValidateRgOffsets(CClfsBaseFile *this, ulong *rgObject, ulong param_2)
{
    lVar6 = 0; uVar7 = 0; lVar8 = 0;
    piVar3 = *(int **)(*(longlong *)(this + 0x30) + 0x30);   // _CLFS_LOG_BLOCK_HEADER*
    if ((piVar3 == NULL) ||
        (uVar1 = piVar3[0x1a],                                // +0x68 SignaturesOffset
         (int *)(uVar1 + (longlong)piVar3) < piVar3))
        return -0x3fe5fff3;                                   // STATUS_LOG_METADATA_INVALID
    qsort(rgObject, 0x47c, 4, CompareOffsets);
    do {
        uVar2 = *rgObject;
        if (uVar2 - 1 < 0xfffffffe) {
            piVar5 = OffsetToAddr(this, uVar2);
            if ((piVar5 == NULL) || (uVar2 < 0x30)) return -0x3fe5fff3;
            uVar4 = uVar7 + 0x30 + (int)lVar8;
            if (uVar4 < uVar7) return -0x3fe5fff3;            // range overflow
            if ((uVar7 != 0) && (uVar2 - 0x30 < uVar4))       // objects overlap
                return -0x3fe5fff3;
            if (*piVar5 == -0x3e020ff8)      lVar8 = 0x30;    // 0xC1FDF008 CLIENT_CONTEXT
            else {
                if (*piVar5 != -0x3e020ff9) return -0x3fe5fff3;
                lVar8 = 0x88;                                 // 0xC1FDF007 CONTAINER_CONTEXT
            }
            if ((int *)(lVar8 + piVar5) < piVar5) return -0x3fe5fff3;
            uVar7 = uVar2 - 0x30;
            if ((int *)(uVar1 + piVar3) < (int *)(lVar8 + piVar5))
                return -0x3fe5fff3;                           // *** context must not cross
        }                                                     //     SignaturesOffset — the fix ***
        rgObject++;
    } while (++uVar9 < 0x47c);
    return 0;
}
```

### Feature_Servicing_38197809__private_IsEnabled — NEW @ `0x1c000c8d4` (94 bytes)

Standard WIL Controlled Feature Rollout accessor
(`GetCachedFeatureEnabledState` + `ReportUsageToService`); called from
`LoadContainerQ` and its SEH funclet. Gates the new validation block.

## Functions modified (code changes)

### CClfsBaseFilePersisted::LoadContainerQ

| | |
|---|---|
| Address | `0x1c0036a90 → 0x1c0036ba0` |
| Length | 2,927 → 3,160 bytes |
| Similarity | ratio 0.15 / b_ratio 0.85 (heavily reworked) |
| New called | `CClfsBaseFile::ValidateRgOffsets`, `Feature_Servicing_38197809__private_IsEnabled` |
| Callers (unchanged) | `CClfsLogFcbPhysical::Initialize` |

New block (post-patch, actual Ghidra decompilation, abridged):

```c
_Src = BaseLogRecord + 0x328;                 // rgContainers[]
uVar10 = CClfsBaseFile::ContainerCount(this);
uVar25 = 0;
while (i < 0x400) { if (_Src[i]) uVar25++; }  // count live slots
...
else if (uVar25 == uVar10) {
    iVar11 = Feature_Servicing_38197809__private_IsEnabled();
    if (iVar11 == 0) {
        // --- legacy path (flag off) ---
        _Dst = operator_new(0x1000, PagedPool);
        memcpy(_Dst, _Src, 0x1000);
        local_f8 = CClfsBaseFile::ValidateContainerOffsets(this, _Dst, 0x1000);
    } else {
        // --- NEW CVE-2022-24521 validation (flag on) ---
        pCVar16 = operator_new(0x11f0, PagedPool);      // 0x1000 + 0x1f0
        memcpy(pCVar16, _Src, 0x1000);
        // copy 0x1f0 bytes of record extension from BaseLogRecord + 0x138
        // (3 x 0x80 + 0x70 bytes, unrolled) into scratch + 0x1000
        ...
        local_f8 = CClfsBaseFile::ValidateRgOffsets(this, pCVar16, 0x1000);
        operator_delete(pCVar16);
    }
    if (local_f8 < 0) goto FAIL;                // 0xC01A000D aborts the load
}
```

### CClfsLogFcbVirtual::Open

| | |
|---|---|
| Address | `0x1c0042b40 → 0x1c0042d40` |
| Length | 974 → 1,046 bytes |
| Similarity | ratio 0.99 / b_ratio 0.97 |
| New called | `NTOSKRNL.EXE::KeBugCheckEx` |

Now bugchecks on an internal consistency failure instead of continuing —
secondary hardening shipped in the same update. Worth a direct read in Ghidra
if you want to rule an independent second fix in or out.

### `CClfsBaseFilePersisted::LoadContainerQ'::__l1::fin$0`

SEH unwind funclet for `LoadContainerQ` (ratio 0.98) — updated to clean up
the new `0x11f0` scratch buffer on the exception path.

### Feature_Servicing_37529451__private_IsEnabled

Pre-existing servicing flag accessor (ratio 0.91) — recompiled due to the
shared WIL state-cache layout shifting; no security logic change.

### wil_details_FeatureReporting_* (4 functions)

`RecordUsageInCache`, `ReportUsageToServiceDirect`, `IncrementUsageInCache`,
`ReportUsageToService` — WIL telemetry plumbing recompiled for the new
feature flag's reporting cache. Not security-relevant.

## Modified (no code changes) — 7

`OffsetToAddr`, `operator_new`, `operator_delete`,
`wil_details_FeatureStateCache_GetCachedFeatureEnabledState`, `memcpy`,
`NTOSKRNL.EXE::qsort`, `CompareOffsets` — address/reference shifts only.
Note `CompareOffsets`, `qsort`, `OffsetToAddr`, `operator_new/delete` appear
here precisely because the **new** validator references them.

## Deleted

None.

---

<sub>Source: ghidriff diff (VersionTrackingDiff, full MSDL PDB symbols) of
[clfs-10.0.22000.556.sys](/data/patch_diffs/binaries/clfs-10.0.22000.556.sys)
(KB5011493, pre-patch) vs
[clfs-10.0.22000.613.sys](/data/patch_diffs/binaries/clfs-10.0.22000.613.sys)
(KB5012592, post-patch), Windows 11 21H2. Raw report:
`ghidriff/CVE-2022-24521/ghidriffs/clfs-10.0.22000.556.sys-clfs-10.0.22000.613.sys.ghidriff.md`.
Diff stats: 2,836/2,838 functions matched (99.93%); matched-function
similarity 99.47%; 8 code-changed, 2 added, 0 deleted. Root cause and field
semantics cross-checked against the Project Zero 0-days-in-the-wild RCA for
CVE-2022-24521.</sub>
