# CVE-2026-62728 — Windows CLFS `clfs.sys` Elevation of Privilege via Double-Fetch of the Block Sector Count in `ClfsValidateBlock`

---

## Summary

| **Product**           | Microsoft Windows — `clfs.sys` (Common Log File System Driver) |
|-----------------------|-----------------------------------------------------------------|
| **Vendor**            | Microsoft Corporation |
| **Severity**          | Important |
| **Impact**            | Elevation of Privilege |
| **CVE ID**            | CVE-2026-62728 |
| **CVSS (MSRC)**       | 7.0 |
| **CWE**               | CWE-367: Time-of-check Time-of-use (TOCTOU) Race Condition — double fetch |
| **Patch Date**        | August 11, 2026 |
| **Pre-patch binary**  | `clfs.sys` 10.0.26100.8972 (2026-07-28) — SHA256 `0a784dad4648caf108f7fe98…` |
| **Post-patch binary** | `clfs.sys` 10.0.26100.9168 (2026-08-11) — SHA256 `5880c90802c71110794d4be2…` |
| **Feature flag**      | `Feature_344697147` — **fix is CFR-gated** |

---

## Product Description

CLFS is the kernel logging layer that NTFS, the registry, KTM and MSDTC build on.
It operates on Base Log Files (`.blf`) whose on-disk structure is parsed directly
by the kernel. A log can be created by any standard user in any user-writable
directory via `CreateLogFile`, which makes every field in that file attacker
input — the reason CLFS has produced a long run of kernel EoP bugs
(CVE-2022-24521, CVE-2022-37969, CVE-2023-28252, CVE-2026-40397, CVE-2026-40407,
CVE-2026-44809, CVE-2026-50697).

Each log block carries a header whose `+0x4` field is the block's **sector
count**. Validation multiplies it by 512 to check the block fits the buffer that
was read, then walks each 512-byte sector checking its trailing signature and
flag bytes at `+0x1fe` / `+0x1ff`.

---

## Vulnerability Summary

`ClfsValidateBlock` read the sector count **twice from the same attacker-supplied
buffer**: once for the bounds check, and again on every iteration of the
sector-walk loop.

```c
// ClfsValidateBlock — VULNERABLE (10.0.26100.8972)
if (param_3 < (uint)uVar2 << 9)          // uVar2 read earlier from *(ushort*)(param_2 + 4)
    return -0x3fe5fffc;                  // bounds check against THAT read

uVar8 = 1;
while (true) {
    *param_6 = uVar8;
    if (*(ushort *)(param_2 + 4) - 1 <= uVar8) break;   // ← RE-READ every iteration
    _Var1 = param_2[(ulonglong)uVar8 * 0x200 + 0x1fe];  // indexed access
    if ((char)_Var1 < '\0')                    return -0x3fe5fffd;
    if (param_2[uVar8 * 0x200 + 0x1ff] != param_4) return -0x3fe5fffe;
    /* flag checks */
    uVar8 = uVar8 + 1;
}
```

The bounds check proves the block fits the buffer *for the value it read*. The
loop then trusts a **fresh read of the same field** for its termination
condition. If that field changes between the two, the loop iterates further than
the check licensed, and `param_2[uVar8 * 0x200 + 0x1fe]` walks past the end of
the validated region.

---

## Prerequisites and Constraints

- Local authenticated session (standard user; no administrative rights)
- CLFS is a core kernel component — always present, no optional feature required
- `.blf` files can be created in any user-writable directory via `CreateLogFile`
- The attacker must be able to change the header's `+0x4` field **after** the
  bounds check and **during** the sector walk

That last condition is the whole exploitability question. The buffer is the block
just read from the log file, and `ClfsValidateBlock` is reached from
`CompleteAsyncReadBlock` — an **asynchronous read completion** path. Establishing
whether a second thread, a mapped section, or in-flight DMA can mutate the buffer
inside that window is what separates a bugcheck from a controlled OOB access.
**This analysis does not establish it.** The patch's shape shows Microsoft treated
the double fetch itself as the defect.

---

## Vulnerability Details

### Call Chain

```
User mode:
  CreateLogFile()                          → unprivileged, any writable directory
  log read / flush activity
                                             ↓
Kernel mode (clfs.sys):
  CClfsLogFcbPhysical::ReadLogBlock
    → CClfsLogFcbPhysical::CompleteAsyncReadBlock   [async completion, multi-block walk]
      → CClfsLogFcbPhysical::ValidateLogBlock
        → ClfsValidateBlock                          [*** VULNERABLE ***]
          → ClfsValidateSector
```

`CClfsLogFcbPhysical::IsEof` is also modified in this patch and participates in
the same read path.

### The patch

The fix captures the count once and uses that single value for both the bounds
check and the loop bound:

```c
// ClfsValidateBlock — PATCHED (10.0.26100.9168), feature-enabled branch
uVar2 = *(ushort *)(param_2 + 4);        // read ONCE into a local
if (param_3 < (uint)uVar2 << 9)
    return -0x3fe5fffc;                  // bound checked against the capture

uVar5 = 1; uVar6 = 1; *param_6 = 1;
if (1 < uVar2 - 1) {
    do {
        _Var1 = param_2[uVar5 * 0x200 + 0x1fe];
        ...
        uVar6 = uVar6 + 1;
        uVar5 = (ulonglong)uVar6;
        *param_6 = uVar6;
    } while (uVar6 < uVar2 - 1);         // loop bound uses the SAME capture
}
```

The patch also restructures the per-sector flag validation into `ClfsValidateSector`
calls at entry (`param_5 | 0x40`) and exit (`param_5 | 0x20`), which is why the
function's similarity score against the pre-patch build is low.

### Patch Completeness Assessment

**The fix is gated behind a Controlled Feature Rollout flag.** Both branches ship
in `10.0.26100.9168`:

```c
uVar5 = Feature_344697147__private_IsEnabledDeviceUsageNoInline();
if ((int)uVar5 == 0) {
    /* ORIGINAL DOUBLE-FETCH — re-reads *(ushort *)(param_2 + 4) in the loop */
} else {
    /* FIXED — single capture */
}
```

With the flag disabled, the double fetch still executes on a fully patched
machine. Patch state cannot be inferred from file version alone.

This is the **fifth** August-window CVE in our set where the fix is CFR-gated,
alongside CVE-2026-59125 (`Feature_3417294137`, `Feature_1070577979`),
CVE-2026-42980 (`Feature_1045423416`), CVE-2026-44809 (`Feature_3035089211`) and
CVE-2026-42912 (`Feature_500158777`).

### Investigated and ruled out: is this a re-fix of CVE-2026-40407?

`CClfsLogFcbPhysical::ReadLogBlock` is modified in both the May 2026 patch
(CVE-2026-40407) and this one, which looked like an incomplete-fix pair. It is
not. Two pieces of evidence, both from the August 24H2 binary:

**1. The August `ReadLogBlock` change is part of *this* fix.** `Feature_344697147`
is called from four functions, `ReadLogBlock` among them:

```
ValidateLogBlock        @ 14000f907   x1
ClfsValidateBlock       @ 14000e0d2   x1
ReadLogBlock            @ 14000f6ba, 14000f717   x2
CompleteAsyncReadBlock  @ 140078c42 ... 14007943b  x9
```

All thirteen call sites gate the same double-fetch remediation across the whole
block-read path. `ReadLogBlock` appears because it is central to that path, not
because August revisited May's defect.

**2. CVE-2026-40407's flag is not in this branch.** `Feature_748929339` — which
gates the May LSN/owner-page bounds check — does not exist anywhere in
`clfs.sys` 10.0.26100.9168. That fix was diffed on the **10.0.28000.x** (Server)
branch; this CVE is **10.0.26100.x** (24H2). Different servicing branches,
different flags, different defects.

**Conclusion: not a variant pair.** Two distinct bugs that happen to share a
function on a hot path. Recorded here because the surface-level signal — same
binary, same function, four months apart — is exactly the kind of thing that
becomes a wrong claim if not checked.

### Feature-flag inventory (August 24H2 `clfs.sys`)

The binary carries **fourteen** concurrent CFR flags, so flag *count* alone does
not map to fix count — only flags **added by the diff** do. Several are named
rather than numeric, and the names leak provenance:

| Flag | What the name reveals |
|---|---|
| `Feature_Servicing_MSRC97927` | an MSRC case number embedded in the binary |
| `Feature_Servicing_CLFSFuzzerBugInvalidContainerID` | found by fuzzing, and what it hit |
| `Feature_ClfsFixCpfHeaderLeak` | an information-leak fix |
| `Feature_ClfsContainerMemberInitFixes` | uninitialised member fixes |
| `Feature_ClfsHashNodeReturnCode` | error-handling fix |
| `Feature_CLFS_Signing`, `Feature_CLFS_EventLog` | feature work, not fixes |
| `Feature_Servicing_CLFS_AuthenticationExemptions` | auth-path servicing |
| `344697147`, `326875449`, `950255931`, `1403240762`, `3236415803`, `3650604346` | opaque |

Only `Feature_344697147` is new in this diff.

---

## Detection Guidance

**Crash signature.** Bugchecks inside `clfs!ClfsValidateBlock` or
`clfs!CClfsLogFcbPhysical::CompleteAsyncReadBlock`, typically `0x50`
(PAGE_FAULT_IN_NONPAGED_AREA), on systems with `.blf` activity from
non-administrative users. Driver Verifier with Special Pool on `clfs.sys` makes
the overrun far more observable.

**Behavioural.** Creation of `.blf` files in user-writable directories by
non-system processes, particularly with repeated open/read cycles against the same
log — a double fetch has to be raced, so the trigger pattern is repetition rather
than a single malformed file.

**Sysmon.** File-create events with `TargetFilename` ending `.blf` outside
`%SystemRoot%`, correlated by process. Long-standing CLFS detection guidance for
CVE-2022-37969 and CVE-2023-28252 applies unchanged.

**Config note.** Because the fix is CFR-gated, verifying `Feature_344697147` is
enabled is the only way to confirm the corrected path is live. File version alone
is insufficient.

---

## References

- MSRC advisory — CVE-2026-62728, August 2026
- Full binary diff: `/data/patch_diffs/clfs_sys-cve-2026-62728-ghidriff.md`
- Related: CVE-2026-40407 (`ReadLogBlock`, May 2026), CVE-2026-44809, CVE-2026-50697
