# clfs.sys Patch Diff — CVE-2026-50697

| | |
|---|---|
| Binary | clfs.sys (Common Log File System Driver) |
| Pre-patch version | 10.0.26100.8737 |
| Post-patch version | 10.0.26100.8875 |
| KB | KB5101650 |
| CVE | CVE-2026-50697 — Exposure of Sensitive Information to an Unauthorized Actor (CWE-200), Elevation of Privilege, CVSS 7.8 |
| Diff tool | ghidriff (Ghidra VersionTrackingDiff engine) |
| Functions changed | 4 with code changes (out of ~1,000 total) |
| Functions added | 4 (WIL feature-flag accessors for `Feature_326875449`) |

## Summary

The fix is an information-disclosure scrub inside
`CClfsBaseFileSnapshot::CopyImage` — the routine that serializes a base-file
log image (the CLFS metadata blocks) into a caller-supplied buffer. Before the
patch, the copied image included a per-container-context pointer field that
holds live kernel state; the patched build blanks that field out of every
container context for the duration of the copy, then restores it, so the image
handed back to the caller no longer leaks it. Everything is gated behind a new
Controlled-Feature-Rollout flag, `Feature_326875449`.

Two coordinated changes implement it:

**1. Constructor zero-inits a new save area.**
`CClfsBaseFileSnapshot::CClfsBaseFileSnapshot` gains, behind the flag, a
`memset` of a fresh 0x2000-byte region at `this + 0xa0` — 1024 slots of 8 bytes
each, matching the 0x400 container-context ceiling used in `CopyImage`:

```c
*(undefined ***)this = &_vftable_;
uVar1 = Feature_326875449__private_IsEnabledDeviceUsageNoInline();
if ((int)uVar1 != 0) {
    memset(this + 0xa0, 0, 0x2000);   // 0x400 slots * 8 bytes
}
```

**2. `CopyImage` moves the sensitive field out, copies, then restores it.**
When the flag is enabled, a pre-pass walks up to 0x400 container contexts,
lifts the pointer at `container_context + 0x18` into the `this + 0xa0` save
array and **nulls the source**:

```c
// pre-pass (post-patch, behind Feature_326875449)
uVar6 = Feature_326875449__private_IsEnabledDeviceUsageNoInline();
if ((int)uVar6 != 0) {
    while ((uint)uVar7 < 0x400) {
        lVar4 = CClfsBaseFile::AcquireContainerContext((CClfsBaseFile *)this, uVar13, &local_80);
        if (lVar4 < 0) {
            *(undefined8 *)(this + uVar7 * 8 + 0xa0) = 0;
        } else {
            *(undefined8 *)(this + uVar7 * 8 + 0xa0) = *(undefined8 *)(local_80 + 0x18);
            *(undefined8 *)(local_80 + 0x18) = 0;   // scrub before the image is copied out
            CClfsBaseFile::ReleaseContainerContext((CClfsBaseFile *)this, &local_80);
        }
        uVar7 = ...+1;
    }
}
```

The existing block-copy loop (`memcpy` into `param_3`, the caller buffer) then
runs against the now-sanitized image. Afterwards, a post-pass restores each
saved pointer back into its container context so runtime state is unchanged:

```c
// post-pass restore (post-patch, behind Feature_326875449)
uVar7 = Feature_326875449__private_IsEnabledDeviceUsageNoInline();
if ((int)uVar7 != 0) {
    pCVar12 = this + 0xa0;
    do {
        if (*(longlong *)pCVar12 != 0) {
            lVar5 = CClfsBaseFile::AcquireContainerContext((CClfsBaseFile *)this, uVar8, &local_80);
            if (lVar5 < 0) {
                *(longlong *)pCVar12 = 0;
            } else {
                *(longlong *)(local_80 + 0x18) = *(longlong *)pCVar12;   // put it back
                *(longlong *)pCVar12 = 0;
                CClfsBaseFile::ReleaseContainerContext((CClfsBaseFile *)this, &local_80);
            }
        }
        pCVar12 = pCVar12 + 8;
    } while (... < 0x400);
}
```

The pre-patch `CopyImage` had none of this: it acquired the image resource and
copied blocks straight through, `container_context + 0x18` included. That field
is a live kernel pointer, so an authorized local caller that reads a base-file
image snapshot could recover kernel address information — an ASLR/kernel-info
leak usable as a stepping stone to elevation, which is exactly what CWE-200 +
"allows an authorized attacker to elevate privileges locally" describes.

**Confidence:** high on mechanism (the scrub-copy-restore of a specific pointer
field, gated by a kill-switch flag, is unambiguous in the diff), moderate on
the precise semantic value of `container_context + 0x18` — treat the exact
"kernel pointer" characterization as a strong diff-based inference rather than a
confirmed field identity from Microsoft.

## Functions changed

### CClfsBaseFileSnapshot::CopyImage

| | |
|---|---|
| Address | 140039668 -> 1400396a0 |
| Change type | code, length, address, called |
| Similarity | 0.28 (ratio), 0.61 (b_ratio) |
| Length | 610 -> 904 bytes |
| Scrub added | **Yes** — pre-pass null + post-pass restore of `container_context+0x18`, gated by `Feature_326875449` |

Now calls `CClfsBaseFile::AcquireContainerContext` / `ReleaseContainerContext`
(new in its `called` list) to iterate container contexts. `memmove` → `memcpy`
on the block copy is a recompile artifact, not part of the fix.

### CClfsBaseFileSnapshot::CClfsBaseFileSnapshot (constructor)

| | |
|---|---|
| Change type | code, length | 
| Save area added | **Yes** — `memset(this+0xa0, 0, 0x2000)`, gated by `Feature_326875449` |

Zero-initializes the 1024-slot save array that `CopyImage` uses to stash the
lifted pointers.

### `CClfsBaseFileSnapshot::CopyImage'::__l1::fin$0

The SEH unwind funclet for `CopyImage`, updated to match the reworked body
(cleanup of the acquired container context on the exception path).

### CClfsLogFcbPhysical::AppendLog

| | |
|---|---|
| Address | 1400056a0 -> 140005e00 |
| Length | 3109 -> 2467 bytes |

Substantially rewritten (b_ratio 0.08) but with no feature-gated security check
visible — the change is a refactor/recompile of the append path, not part of
the CopyImage information-disclosure fix. Noted for completeness; worth a
direct read if you want to rule out a second, independent change.

### New functions (feature-flag plumbing for `Feature_326875449`)

`GetCachedFeatureEnabledState`, `GetCurrentFeatureEnabledState`, `ReportUsage`,
`__private_IsEnabledDeviceUsageNoInline` — standard WIL Controlled Feature
Rollout accessors for the new flag gating the scrub.

---

<sub>Source: ghidriff diff of clfs-2026-06.sys (10.0.26100.8737, pre-patch)
vs clfs-2026-07.sys (10.0.26100.8875, post-patch) —
[download pre](/data/patch_diffs/binaries/clfs-2026-06.sys) /
[download post](/data/patch_diffs/binaries/clfs-2026-07.sys).</sub>
