In December 2020, Microsoft shipped a patch for CVE-2020-17103 — an elevation-of-privilege flaw in the Windows Cloud Files Mini Filter Driver (cldflt.sys), reported by James Forshaw at Project Zero. The patch removed Forshaw's specific exploitation primitive. The CVE moved to "Closed." The taxonomy moved nowhere: a legitimate driver function with an under-protected privilege boundary remained a legitimate driver function with an under-protected privilege boundary. In 2026 the cluster #1 Abuse of Functions began producing the next exploitation primitive against the same surface. TLCTC's cause-side classification predicted exactly this — not by clairvoyance, but by refusing to confuse the effect (SYSTEM rights) with the cause (the boundary itself).
1. What CVE-2020-17103 actually was
James Forshaw of Project Zero reported the issue to Microsoft in late 2020. The Cloud Files Mini Filter Driver — the kernel component that backs OneDrive's placeholder / "files-on-demand" feature — exposes an undocumented interface used by the user-mode service that synchronises cloud state. One of the routines reachable through that interface, HsmOsBlockPlaceholderAccess, performed registry operations against the .DEFAULT hive without enforcing the access checks a non-privileged caller would otherwise face when touching that hive directly.
The shape of the bug is worth stating precisely, because the precise shape is what the patch addresses and what the cluster does not. The shape was: a legitimate driver function, designed to be invoked from a specific user-mode component, was reachable by any local low-privilege caller, and performed an operation the caller would not have been permitted to perform directly. The driver did not crash. No memory was corrupted. No parser was confused. The function executed exactly as written; what was missing was the privilege gate that should have stopped a low-privilege caller from invoking it for that target.
NVD enumerates this as "Windows Cloud Files Mini Filter Driver Elevation of Privilege Vulnerability." CVSS-wise: AV:L / AC:L / PR:L / UI:N / S:U / C:H / I:H / A:H, base 7.8 (Microsoft's CNA scoring uses AC:H for a 7.0). CWE-269: Improper Privilege Management. The CWE label is the cause-side anchor — improper privilege management, not improper buffer handling — and it is the anchor TLCTC's classification turns on.
2. What the December 2020 patch addressed
Microsoft's Patch Tuesday in December 2020 included a fix for CVE-2020-17103. The fix closed the specific call path Forshaw demonstrated: the cited interface stops accepting the operation that produced the .DEFAULT-hive write, the PoC stops working, the line item on the bulletin moves to "Fixed." That is a true statement of what the patch achieved.
It is also the complete statement. The patch did not redesign the driver's privilege model. It did not remove the undocumented interface. It did not introduce a general access-check policy across the routines reachable through that interface. It addressed the specific instance of the boundary violation that Forshaw enumerated. The structural condition — a kernel driver with an internal user-mode API surface and per-route privilege enforcement rather than per-interface — was preserved by design, because the driver still has to do its job.
This is normal. Vendor patches are scoped to the reproducer they have in hand, because vendor patches that try to do more than that tend to regress the feature. It is also exactly the shape that produces the "patched, not fixed" failure mode whenever a CVE's primary cluster is cause-side rather than effect-side. The bulletin tells you the demonstrated path is closed. It does not — cannot — tell you the cluster surface has shrunk.
A patch's scope is the set of inputs that no longer reach the bug. A cluster's scope is the set of generic causes that classify under it. Patches operate on the former; TLCTC operates on the latter. When the cause is "an under-protected privilege boundary in a legitimate function," a single-input patch shrinks the patch scope without shrinking the cluster scope. The next input that traverses the same boundary, by a different route, classifies under the same cluster — and the bulletin will not warn you.
3. The TLCTC reading: #1, not #7
The temptation, looking at the NVD entry, is to read the EoP wording as if it determined the cluster. It does not. TLCTC is cause-oriented (Axiom III: threats are causes, not outcomes), and "Elevation of Privilege" is an outcome.
The candidates the framework offers and rejects:
#7 Malware— rejected. R-EXEC requires foreign executable content. If post-escalation tooling is loaded after SYSTEM is reached, that is a separate#7step (Path 2 below); it is never the primary classification of the CVE.#2 Exploiting Server/#3 Exploiting Client— rejected. These clusters name implementation flaws in server-role or client-role components: memory corruption, parser confusion, deserialization, sandbox escape.HsmOsBlockPlaceholderAccesshas none of those properties. The function executes correctly; the missing piece is the access check, not the code path.#1 Abuse of Functions— accepted. The cluster names exactly the cause-side condition the CWE-269 label points at: a legitimate function that performs the operation it was designed to perform, invoked by a caller that should not have been permitted to invoke it for that target. The bug is in the boundary, not in the function.
The TLCTC-CWE mapping reaches the same verdict mechanically: CWE-269 (Improper Privilege Management) maps to #1 Abuse of Functions, not to #2 or #7. The mapping does not depend on whether the privilege transition lands at SYSTEM or somewhere weaker; the impact is a downstream property, not a cluster input.
3.1 Attack paths
Three paths are admissible for this CVE, distinguished by what (if anything) follows the privilege transition:
Path 1 #1 |[privilege][@user→@SYSTEM]| Path 2 #1 |[privilege][@user→@SYSTEM]| → #7 Path 3 #1 |[privilege][@user→@SYSTEM]| → #1 + [DRE: I]
Two notational details matter. First, the intra-system boundary annotation |[privilege][@user→@SYSTEM]| is observability metadata — under R-INTRA-7 it never changes cluster classification. The cluster is #1 because of the cause-side analysis above; the boundary annotation records where in the host the transition was observed, not why it succeeded. Second, the DRE tag in Path 3 (+ [DRE: I]) is registered against the second #1 step — the privileged use of the abused function to mutate registry / system state — and not against the boundary crossing itself, which has no DRE attached.
Path 1 is the pure case: a local user reaches SYSTEM via the boundary flaw and the analysis stops there. Path 2 applies when the post-escalation step loads foreign executable content (a service binary, an in-memory loader, a follow-on driver); R-EXEC requires that the FEC moment be recorded as its own #7. Path 3 applies when the privileged caller's purpose is to manipulate state in place — registry keys, system configuration, audit-relevant entries — without a separable FEC step.
4. 2026: Chaotic Eclipse re-opens what was never closed
In 2026, the group reporting under the name Chaotic Eclipse published claims of reliable local privilege escalation on fully-patched Windows 11 systems, from a standard-user context, via the Cloud Files Mini Filter Driver. The specifics of the primitive differ from Forshaw's 2020 work — different reachable routine, different downstream operation, different registry artefact — but the cause-side shape is identical: a legitimate driver function, reachable from a low-privilege caller, performing an operation the caller could not have performed directly.
The TLCTC verdict for the 2026 chain is the same verdict as for the 2020 chain. #1 Abuse of Functions at cldflt.sys, with the same boundary annotation, the same three admissible path shapes (pure escalation, escalation + FEC, escalation + state mutation), the same CWE-269 anchor. The cluster did not move because the cluster was never patched.
This is not an anomaly. It is what happens when an effect-scoped patch is treated by downstream consumers — vulnerability scanners, SBOMs, audit programs, risk registers — as if it were a cluster-scoped closure. The bulletin is honest about what it did. The interpretation layer above the bulletin is not.
Press coverage of the 2026 chain frames it as a "re-discovery" of CVE-2020-17103 or as an "incomplete patch." Neither phrasing is precise. CVE-2020-17103 names Forshaw's specific finding, and that finding remains patched. What persists is the cluster surface the finding sat on top of. The 2026 chain is a new finding against the same surface — not a re-discovery of the old one, but the next finding in a family the cluster has been naming since 2020. A new CVE would be appropriate; calling it "the same one" is what produced the analytic confusion in the first place.
5. Loud fix as the inverse of silent fix
The silent-fix-window analysis described an attacker advantage that comes from a missing signal: a security-relevant commit lands in the upstream tree, the CVE follows weeks or years later, and advisory-anchored defenders are blind to the exploit window in between. The Linux kernel was the canonical case; AI agents are the velocity multiplier.
The loud-fix failure mode is the mirror image. The signal is present — bulletin issued, CVE assigned, severity scored, patch shipped, KB article published — but the signal carries the wrong information. It tells the consumer a specific reproducer is no longer reachable, and is read as if it told them the cluster surface has shrunk. The reading is incorrect, and the bulletin does not — cannot — flag the incorrectness, because the bulletin is scoped to the reproducer and not to the cluster.
The symmetry is exact in operational consequence:
| Failure mode | Effect on defender |
|---|---|
| Silent fix | Defender has no signal. Exploit exists; bulletin does not. Patching KCI under-counts the threat surface in scope at any given moment. |
| Loud fix | Defender has the wrong signal. Bulletin closes; cluster does not. Patching KCI over-counts the protective effect of "100% patched against the advisory feed." |
Both modes produce the same final state: the advisory-anchored vulnerability-management KCI loses its predictive relationship to cluster-level risk reduction. The silent-fix piece anchored this on the patching clock — the reference event was wrong because the advisory arrived after the exploit window opened. The loud-fix case anchors it on the patching predicate — the reference event ("CVE-X is patched") is the right event, but the predicate it is read against ("therefore the underlying cause is closed") does not follow.
This is the same axiom-compliance failure described in the framework's v2.1 §10.8 in a different costume: a control's measured effectiveness must be measured against the threat (the cluster, the cause), not against an administrative artefact (the CVE identifier, the bulletin status). When the artefact and the cluster diverge — as they do here, by design — the indicator built on the artefact stops indicating the cluster.
6. What changes for defenders
Three concrete shifts follow:
6.1 Read "patched" as patch-scoped, not cluster-scoped
The first discipline is a reading discipline. A vendor bulletin closing a CVE is a statement that the demonstrated reproducer no longer works. It is not a statement that the cluster the CVE classified under has lost surface area. For CVEs whose primary TLCTC cluster is #1 Abuse of Functions — privilege-boundary flaws in legitimate functions, undocumented interfaces with per-route enforcement, designed-feature scope creep — this distinction is the operational core of the post-patch posture.
The simple form of the rule: if the patch eliminated the demonstrated input but did not redesign the boundary, treat the cluster surface as unchanged. The harder corollary: a vendor advisory cannot, in general, tell you whether the patch redesigned the boundary or only removed the demonstrated input. The CVE description, the KB article, and the CVSS vector are all scoped to the demonstrated impact. None of them are scoped to "did the cluster surface shrink."
6.2 Re-anchor the patching KCI from CVE-closure to cluster-cause elimination
The silent-fix piece argued for re-anchoring the patching KCI from advisory publication to commit landing, because the advisory clock had drifted out of alignment with the threat economics. The loud-fix analogue is to re-anchor the same KCI from CVE-closure to cluster-cause elimination, for cause-side clusters specifically.
In practice this means the patching KCI for #1-class CVEs needs a second predicate beyond "patched within N days." The second predicate asks whether the patch addressed the cluster-cause or only the demonstrated input — and, if only the demonstrated input, whether the residual cluster surface has compensating cause-side controls (least-privilege boundaries, interface removal, surface reduction at the driver level). Without that second predicate the KCI continues to indicate something — but not the thing the audit programme assumes it indicates.
This is more honest than easy. Operationalising "did the patch eliminate the cluster-cause" requires reading the patch, not the bulletin — and most vulnerability-management programmes are organised around the bulletin. The acknowledgement worth carrying forward is that current CVE-closure-anchored KCIs systematically over-report protective effectiveness for the #1 class, exactly as the advisory-closure-anchored KCIs over-report for the silent-fix class. Different failure mechanism; same direction of error.
6.3 Cause-side surface reduction at the cluster level
The durable control is cluster-scoped, not CVE-scoped. For the cldflt.sys case the cause-side options are the usual #1 hardening levers: remove the undocumented interface entirely if the dependent feature can be redesigned; enforce per-interface access checks rather than per-route, so that an unanticipated new route inherits the gate by default; reduce the kernel attack surface attributable to features the host does not need (disable the Cloud Files filter on machines that do not use OneDrive placeholders at all). None of these depend on the next CVE arriving. All of them shrink the cluster surface, not the patch surface.
This is the same recommendation TLCTC has carried since the framework's first publication: cause-side prevention dominance. CVE-2020-17103 → Chaotic Eclipse 2026 is a five-year worked example of why the recommendation is structural rather than rhetorical.
7. Regulatory note
NIS2, DORA, and equivalent regimes mandate "timely" patching against the advisory feed and audit "vulnerability closure" against CVE status. Both verification mechanisms are scoped to the same administrative artefact that this analysis has just argued is misaligned with cluster-level risk. The auditor verifies that CVE-2020-17103 was patched within the prescribed window. The auditor does not — is not equipped to — verify that the #1 cluster surface at cldflt.sys was reduced.
The Kreinz Thesis line carries unchanged from the silent-fix piece: regulation mandates controls without identifying the threat economics those controls assume. The silent-fix case showed the regulatory clock running behind the exploit clock. The loud-fix case shows the regulatory predicate ("CVE closed") substituting for the cluster predicate ("cause eliminated"). Both are the same omission in different costumes. The regulatory framework will lag in both directions. Defenders should not.
8. Summary
CVE-2020-17103 was a privilege-boundary flaw in a legitimate driver function — #1 Abuse of Functions at cldflt.sys, anchored by CWE-269. The December 2020 patch closed Forshaw's demonstrated reproducer. It did not redesign the privilege boundary. The 2026 Chaotic Eclipse chain reaches SYSTEM through a different route in the same surface, classifies under the same cluster, and produces the same three admissible attack-path shapes. The cluster did not move because the cluster was never patched.
The cause-side lesson generalises beyond this CVE. Any CVE whose primary cluster is #1 — privilege-boundary flaws, abuse of legitimate functions, designed-feature scope creep — should be read on two levels: the patch level (closed) and the cluster level (probably not). The advisory feed reports the first and is silent on the second. KCIs anchored on advisory closure read the first as if it implied the second. It does not.
Defensive posture: read "patched" as patch-scoped, add a cluster-cause-elimination predicate to #1-class KCIs, and invest in cause-side surface reduction that does not require the next CVE to arrive. The companion piece on silent fixes argued the same posture from the velocity side. This piece argues it from the patch-semantics side. The conclusion is the same: cause-side classification names the layer that survives the patch.
A patch closes an input. A cluster names a cause. When the CVE's primary cluster is cause-side — #1 Abuse of Functions here, but the principle is general — closing the demonstrated input leaves the cluster surface intact. The bulletin is honest about what it did; the interpretation layer above the bulletin is what produces the "patched ≠ closed" error.
The KCI shift mirrors the silent-fix shift. Silent fix re-anchored the patching KCI from advisory publication to commit landing because the clock drifted. Loud fix re-anchors the same KCI from CVE-closure to cluster-cause elimination because the predicate drifted. Different mechanism, same direction of error: the indicator systematically over-reports protective effectiveness.
The operating shift. Read patches as patch-scoped, not cluster-scoped. Add a second predicate to #1-class KCIs that asks whether the patch redesigned the boundary or only removed the demonstrated input. Reduce cluster surface ahead of disclosure — interface removal, per-interface gating, feature disablement on hosts that do not use the feature. The CVE-2020-17103 → Chaotic Eclipse 2026 timeline is the five-year worked example of why this is structural, not rhetorical.
9. Final classification
cldflt.sys), Forshaw / Project Zero 2020, Chaotic Eclipse 2026#1 Abuse of Functions; CWE-269 (Improper Privilege Management)#7 Malware (R-EXEC — FEC, if any, is a separate step); #2/#3 (no implementation flaw — function executes as written)#1 |[privilege][@user→@SYSTEM]| · Path 2 … → #7 · Path 3 … → #1 + [DRE: I]|[privilege][@user→@SYSTEM]| — intra-system; observability only (R-INTRA-7), does not change classificationAV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H) · 7.0 (Microsoft CNA, AC:H)#1-class CVEs; second predicate (cluster-cause elimination) requiredAbout the methodology
This analysis applies the TLCTC v2.1 framework: ten cause-oriented threat clusters, the Bow-Tie SRE/DRE separation, intra-system boundary operators (R-INTRA-7), R-EXEC for FEC steps, and the Kreinz Thesis distinction between cause-side and consequence-side controls. For framework definitions: tlctc.net. Licensed under CC BY 4.0. Source: github.com/Barnes70/TLCTC.
Primary references for the technical claims above: NVD entry for CVE-2020-17103; Microsoft Security Update Guide (December 2020); Google Project Zero issue tracker / James Forshaw write-up on HsmOsBlockPlaceholderAccess; 2026 reporting on the Chaotic Eclipse re-exploitation chain. The TLCTC classification is the work of this post; the source material is cited as published.