
Proof-of-concept demonstrating a Windows Cloud Files access-check bypass (CVE-2026-83991) that converts a read-only file into a cloud placeholder via supersede semantics, with detailed technical analysis and reproduction steps.
CVE-2026-83991 is a Windows Cloud Files tampering vulnerability whose published affected range begins with Windows 10 version 1809, almost eight years before the September 2026 fix.
I asked Windows to open a file for writing. It answered ERROR_ACCESS_DENIED.
I asked Windows to delete the same file. Again, ERROR_ACCESS_DENIED.
Then I asked the Cloud Files stack to supersede that filename. It returned S_OK and changed the existing file into a Cloud Files placeholder.
The file had said no twice. The supersede path heard something closer to “please continue.”
That authorization split is CVE-2026-83991. Microsoft calls it the Windows Cloud Files Mini Filter Driver Tampering Vulnerability, rates it Important, and assigned a CVSS 3.1 base score of 5.5 Medium. The official vector is CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:N/E:U/RL:O/RC:C.
The proof of concept creates a fresh directory and an ordinary file. It gives the caller write access to the directory, then applies a protected DACL to the file that grants the caller read access without ordinary write or delete access.
Using one restricted medium-integrity thread token, it proves the following sequence:
The call processed one entry, returned a nonzero creation USN, and gave the file the IO_REPARSE_TAG_CLOUD tag, value 0x9000001A. In the reproduced run, the file ID was identical before and after the call. The same NTFS file persisted while its state changed into a cloud placeholder.
Windows 10 version 1709 introduced the Cloud Files API. It gives desktop sync engines a supported way to register a directory tree, create placeholder entries, and hydrate their content when needed.
Three pieces matter here:
CldApi.dll exposes the user-mode Cloud Filter API.cldflt.sys is the file-system minifilter at the center of the storage path.Cloud placeholders use reparse points. A reparse point is a file-system mechanism with a tag and associated data. It is a broader concept than a symbolic link, and the two should not be treated as synonyms.
The relevant API is CfCreatePlaceholders. It creates one or more placeholder files or directories beneath a registered sync root. Microsoft’s documentation says the caller must have WRITE_DATA or WRITE_DAC access to the base directory.
The per-entry flag CF_PLACEHOLDER_CREATE_FLAG_SUPERSEDE, value 0x4, supplies overwrite semantics for an existing placeholder. The interesting detail in this CVE is that the vulnerable path also accepted an ordinary existing file and converted it into a placeholder.
The experiment separates rights on the directory from rights on the existing file.
The parent directory grants the current user:
FILE_GENERIC_READ
FILE_GENERIC_WRITE
FILE_GENERIC_EXECUTE
DELETE
For a directory, FILE_GENERIC_WRITE includes FILE_WRITE_DATA, also called FILE_ADD_FILE. That is enough for the documented base-directory requirement used by CfRegisterSyncRoot and CfCreatePlaceholders.
The parent ACE does not grant FILE_DELETE_CHILD. Its DELETE bit applies to the parent object itself. This distinction matters because DeleteFileW requires either DELETE on the target file or FILE_DELETE_CHILD on its parent.
The existing leaf grants the current user only FILE_GENERIC_READ. Its DACL is marked protected with PROTECTED_DACL_SECURITY_INFORMATION, so it does not inherit the parent’s ACEs.
This creates the central question:
Does permission to create a new child in a directory also authorize a state-changing supersede operation against a more restrictive child that already exists?
Ordinary file access answers no. The vulnerable Cloud Files path answered yes.
Microsoft’s file security documentation explains why the distinction is expected. Access to a file is normally controlled by that file’s security descriptor. The parent’s descriptor does not generally replace the child’s access check, apart from specific rules such as inheritance and FILE_DELETE_CHILD.
Access-control demonstrations become unconvincing when the setup runs as one identity and the interesting operation runs as another. This PoC avoids that problem.
It derives a restricted token from the current process token, makes the Administrators SID absent or deny-only, sets medium integrity, duplicates an impersonation token at SecurityImpersonation, and installs it on the current thread with SetThreadToken.
The exact privilege wording deserves care. CreateRestrictedToken with DISABLE_MAX_PRIVILEGE disables every privilege except SeChangeNotifyPrivilege. That remaining privilege bypasses some directory traversal checks. It does not grant file data writes or deletion rights.
The PoC then performs the setup, control checks, sync-root registration, connection, and supersede call while that same thread impersonation token remains active. There is no convenient identity swap between “access denied” and S_OK.
The full source is in main_poc.c, with build.bat for GCC or Microsoft’s C compiler.
The program creates a new GUID-named test directory beneath the current user’s local application-data directory. An optional path can be supplied, but the program refuses to use one that already exists.
That restriction is deliberate. The PoC demonstrates the bug against data it creates for itself. It does not need an installed third-party sync provider and does not aim at an existing sync root.
The program creates protected_existing.bin, writes known test data, and gives it ordinary file attributes. It records the file’s basic metadata, logical size, attributes, and file ID.
Before the Cloud Files call, the file is not a reparse point.
The program replaces the leaf DACL with three non-inheriting allow ACEs:
| Principal | Leaf rights |
|---|---|
SYSTEM | Full control |
Administrators | Full control |
| Current user | FILE_GENERIC_READ |
Because the Administrators SID in the effective token is absent or deny-only, the Administrators allow ACE cannot grant administrator access to the thread.
The PoC then performs three controls. GENERIC_WRITE fails with error 5, DeleteFileW fails with error 5, and GENERIC_READ succeeds. It also confirms that the DACL is protected.
Those controls prove the permissions used by the two ordinary operations. The PoC does not test WRITE_DAC or every possible way the owner of its synthetic test file could affect that file. The demonstrated discrepancy is specifically between the denied write and delete operations and the successful Cloud Files supersede operation.
The PoC registers its new directory with a progressive hydration policy and a full population policy. It supplies a fresh GUID as the provider and root identity.
It then calls CfConnectSyncRoot with a callback table containing only the terminating CF_CALLBACK_NONE entry. No hydration callback is needed for the metadata transition being tested.
The placeholder entry preserves the original file’s timestamps and logical size, supplies a GUID file identity, and sets the local constant that maps to the official supersede flag:
#define CF_CREATE_SUPERSEDE 0x00000004UL
placeholder.RelativeFileName = leaf_name;
placeholder.FsMetadata.BasicInfo = basic_info;
placeholder.FsMetadata.FileSize = standard_info.EndOfFile;
placeholder.FileIdentity = &run_id;
placeholder.FileIdentityLength = sizeof(run_id);
placeholder.Flags = CF_CREATE_SUPERSEDE;
create_hr = cf.Create(root, &placeholder, 1, 0, &processed);
The source loads CldApi.dll dynamically and resolves the public functions at runtime. Its hand-declared structures cover only the ABI needed by this test.
A batch API can process an entry that failed. Microsoft’s documentation explicitly says EntriesProcessed includes failed entries, so a value of one does not establish success by itself.
The PoC therefore requires all of these conditions before it prints CONFIRMED and exits with code zero:
CfCreatePlaceholders returns exactly S_OK.S_OK.FILE_ATTRIBUTE_REPARSE_POINT.FSCTL_GET_REPARSE_POINT returns IO_REPARSE_TAG_CLOUD.The direct write, delete, read, DACL, and preexisting-file checks must also pass earlier in the program, or execution stops before the Cloud Files operation.
The reproduced state transition was:
0x00000020 is FILE_ATTRIBUTE_ARCHIVE. The resulting 0x00401600 mask contains FILE_ATTRIBUTE_SPARSE_FILE, FILE_ATTRIBUTE_REPARSE_POINT, FILE_ATTRIBUTE_OFFLINE, and FILE_ATTRIBUTE_RECALL_ON_DATA_ACCESS.
The unchanged file ID is especially useful. It shows that, in this run, the result was not merely a different file appearing at the same pathname. The existing file object survived while acquiring Cloud Files state.
The program preserves the logical file size, but it does not validate the original bytes after conversion. No claim about arbitrary content control follows from this PoC.
The observable failure can be stated without inventing an internal call stack:
CfCreatePlaceholders accepts a supersede request for that leaf.This behavior is consistent with a missing or incomplete check against the existing leaf on the supersede path. It does not identify the precise internal function, branch, IRP, or kernel callback responsible. The PoC contains no kernel debugging or binary-difference analysis, so the explanation stops at the externally verified boundary.
Microsoft assigned CWE-306, Missing Authentication for Critical Function. At the Windows object level, the experiment exposes inconsistent authorization enforcement. I use Microsoft’s official CWE in the metadata and describe the observed access-control behavior in the technical analysis.
The PoC demonstrates an integrity and file-state change that the tested direct operations could not perform. A local low-privilege caller with the required base-directory access can make an existing read-only leaf become a Cloud Files placeholder through the affected path.
Microsoft’s advisory gives the broader product impact: an attacker could make unauthorized changes to protected system data and alter system state or configuration beyond normal privileges. That is Microsoft’s assessment of the vulnerability. The isolated PoC does not select a protected system target or demonstrate a complete post-exploitation chain.
The CVSS vector reflects the same broad shape:
The careful answer is almost eight years in Microsoft’s published affected range.
The official CVE record starts the oldest affected branch at 10.0.17763.0 for Windows 10 version 1809 and Windows Server 2019. Microsoft’s Windows 10 release history lists the first version 1809 build, 17763.1, on October 2, 2018. Microsoft published the fix on September 8, 2026.
That puts the earliest confirmed point in the public range just under eight years before the fix.
The Cloud Files API itself arrived with Windows 10 version 1709 in 2017, and the API documentation lists 1709 as the minimum supported client. That does not prove this vulnerability existed in 1709. Microsoft’s CVE record does not list 1709, and this research did not test it. An API’s birthday is not automatically a bug’s birthday, however tempting the headline may be.
Use an affected test system on NTFS. Run the PoC from a normal, non-elevated command prompt.
build.bat
main_poc.exe
The build script uses MinGW-w64 GCC when available and falls back to Microsoft’s C compiler. The program creates and registers its own fresh test root. It unregisters and disconnects during cleanup, then leaves the test directory in place for inspection.
The optional path form is:
main_poc.exe C:\path\to\a\new-test-directory
The supplied path must not already exist. Keep the test isolated and remove its directory after collecting the results.
On a fixed system, the complete condition should not reach CONFIRMED. Do not infer patch status from one console line. Check the overall result, exit code, per-entry result, USN, attributes, and reparse tag together.
The most interesting Windows vulnerabilities are often arguments over which object an access check was really about.
Here, permission to create beneath a directory reached a path that could transform a stricter child already inside it. The ordinary file APIs respected that child’s current DACL. Cloud Files supersede did not preserve the same boundary.
No spectacular memory corruption was required. One object said no, another object supplied enough authority to continue, and a familiar filename quietly became something else.
That is the whole trick. It is also why the trick matters.
| Operation | Result |
|---|
Open the existing file with GENERIC_WRITE | ERROR_ACCESS_DENIED |
Delete it with DeleteFileW | ERROR_ACCESS_DENIED |
Open it with GENERIC_READ | Success |
| Register and connect the parent as a sync root | S_OK |
Call CfCreatePlaceholders with supersede semantics | S_OK |
| Inspect the resulting entry | Cloud reparse point |
| Property | Before | After |
|---|
| Attributes | 0x00000020 | 0x00401600 |
| Reparse point | No | Yes |
| Reparse tag | None | 0x9000001A |
| Entry result | Not applicable | S_OK |
| Creation USN | Not applicable | Nonzero |
| File ID | Recorded | Unchanged |