⚠️ Before submitting, please verify the following: ⚠️
Bug description
Windows Error 5: Access is denied on every download in a Talk / read-only share – client applies a BUILTIN\Users Deny (Delete) ACE to the .~XXXX temp file before renaming it
Related: comments on #6149. Filing a fresh issue with full evidence, repro and a verified workaround because the existing thread is stuck at "can't reproduce" / "no ACL info".
TL;DR
The desktop client downloads a file from a remote whose permission string lacks W (Talk attachments, read-only shares, "Can view" shares) to .<name>.~XXXX. Before renaming that temp file to its final name, it calls FileSystem::setFileReadOnly*(), which on Windows writes an explicit non-inherited BUILTIN\Users Deny (CreateFiles, AppendData, WriteExtendedAttributes, DeleteSubdirectoriesAndFiles, Delete) ACE onto the temp file. The next call, MoveFileExW(.~XXXX → final), requires DELETE on the source handle. Because the interactive user is a member of BUILTIN\Users and the Deny ACE is evaluated before the Authenticated Users Allow Modify ACE, the rename returns ERROR_ACCESS_DENIED (5). The orphan temp file keeps its Deny ACL, so every subsequent sync runs into exactly the same error forever.
Fix: apply the read-only ACL to the final path after the rename, not to the temp path before it. A simple reordering of two calls in PropagateDownloadFileQNAM::downloadFinished() removes the bug.
Environment
All host-, user- and file-identifying data in this report has been replaced by the placeholders shown in the table below.
|
|
| Client |
Nextcloud Desktop 33.0.2 (official Windows installer), Qt 6.10.2 |
| OS |
Windows 11 Pro 24H2, build 10.0.26200, x64 |
| Install path |
C:\Program Files\Nextcloud\nextcloud.exe |
| Hostname |
<HOST> (anonymized) |
| Interactive user |
<HOST>\<USER> – member of BUILTIN\Users, BUILTIN\Administrators (deny-only under UAC), NT AUTHORITY\Authenticated Users |
| Sync root |
<SYNC_ROOT> (a regular local NTFS directory, e.g. C:\Users\<USER>\Nextcloud) |
| Failing subfolder |
<SYNC_ROOT>\<SHARE>\ – a folder that is a Nextcloud Talk conversation share (attachments sent into a group chat by other participants) |
| Server |
Nextcloud server ≥ 28, standard deployment; remote items report permissions "GDNVS" (read/delete/rename/move/reshare, no write) |
| File system |
NTFS, local SSD, no redirection, no DFS, no compression, no encryption, no junction/mountpoint |
| Virtual files |
Not enabled for this folder – regular always-local sync |
| AV / Controlled Folder Access |
Windows Defender; CFA off; verified not involved (see below) |
| End-to-end encryption |
Not used on the failing folder |
| Placeholders used below |
<HOST> = Windows computer name, <USER> = Windows account name, <SYNC_ROOT> = local sync directory, <SHARE> = name of the failing Talk/read-only folder, <FILE_n> = actual filename of the n-th affected file, <FILEID> = Nextcloud fileid, <HEX> = the 1-4 char hex id the client appends after .~ |
Reproduced with a fresh install of 33.0.2 on two Windows 11 machines owned by the same account.
Symptom
30 files in <SYNC_ROOT>\<SHARE>\ never finish syncing. For each of them the full .<name>.~<HEX> temp is on disk at the correct size, but the final rename never happens. The client is locked in a retry loop. Counters below come from the attached diagnostic transcript, summed across 186 rotated client logs:
| Pattern |
Hits |
Rename failed: …\.~<HEX> => … |
4 950 |
Detected access denied ACL: assuming read-only |
4 950 |
WindowsError: 5: Access is denied. |
24 750 |
inserted error item "<SHARE>/<FILE_n>" |
4 956 |
Representative line-for-line sequence for one affected file (names replaced with placeholders):
discovery.cpp Processing "<SHARE>/<FILE_1>" | perm: "GDNVS" | fileid: "<FILEID>"
discovery.cpp discovered "<SHARE>/<FILE_1>" CSYNC_INSTRUCTION_NEW Down
propagator Starting CSYNC_INSTRUCTION_NEW propagation of "<SHARE>/<FILE_1>" by PropagateDownloadFile
filesystem.cpp:525 Detected access denied ACL: assuming read-only,
path="\\?\<SYNC_ROOT>\<SHARE>\.<FILE_1>.~<HEX>"
propagatedownload.cpp:1329 "Rename failed: <SYNC_ROOT>/<SHARE>/.<FILE_1>.~<HEX>
=> <SYNC_ROOT>/<SHARE>/<FILE_1>"
owncloudpropagator.cpp:280 Could not complete propagation of "<SHARE>/<FILE_1>" …
status SoftError and error: "WindowsError: 5: Access is denied."
This repeats ~165 times per file over the life of the log archive. No HTTP/network/e2ee/hydration failure anywhere in the ~630 000 scanned log lines - the failure is local and deterministic.
Why it is not my FS / AV / permissions
- Parent folder is fine.
<SYNC_ROOT>\<SHARE> itself has no non-inherited Deny, only the default SYSTEM/Administrators/Users/Authenticated Users Allow set.
- Healthy siblings are fine. Files in the same folder that were synced before the share started failing have no non-inherited Deny ACE; only the inherited Allow set. Example: a sibling JPEG of 1.7 MB reads
Deny-ACE here? False, attributes Archive.
- AV is not at play. The file can be opened for read by
<HOST>\<USER>, icacls … /deny works, Defender does not log any quarantine or access block, Controlled Folder Access is off. Removing the Deny ACE (and nothing else) makes the rename succeed.
- It's not the
FILE_ATTRIBUTE_READONLY attribute. Windows ignores ReadOnly on rename; the failure is purely due to the DACL.
The DACL the client puts on the temp file
Dumped from every one of the 30 stuck temp files - identical on all of them (values anonymized):
--- .<FILE_n>.~<HEX> (<SIZE> bytes) attr=ReadOnly, Hidden, Archive
Owner: <HOST>\<USER>
ACE BUILTIN\Users Deny CreateFiles, AppendData,
WriteExtendedAttributes,
DeleteSubdirectoriesAndFiles,
Delete isInherited=False
ACE NT AUTHORITY\Authenticated Users Allow Modify, Synchronize isInherited=False
ACE NT AUTHORITY\SYSTEM Allow FullControl isInherited=False
ACE BUILTIN\Administrators Allow FullControl isInherited=False
ACE BUILTIN\Users Allow ReadAndExecute, Synchronize isInherited=False
All ACEs are isInherited=False, i.e. the client is explicitly writing a full DACL onto each temp file, including the Deny. This is what filesystem.cpp:525 "Detected access denied ACL: assuming read-only" later keys off of.
Why this causes ERROR_ACCESS_DENIED (5) on rename
Windows evaluates explicit Deny ACEs before explicit Allow. The interactive user is in BUILTIN\Users, so Delete is explicitly denied to him on the source. MoveFileExW renames in place by opening the source with DELETE and calling NtSetInformationFile(FileRenameInformation) - it needs DELETE on the source handle. The Deny is matched first, CreateFile with DELETE fails with ERROR_ACCESS_DENIED, and the whole operation returns 5. The client catches this as SoftError, records an "inserted error item", and moves on; the orphan temp retains its bad DACL, so it is re-detected on every subsequent sync.
Where in the code this happens
src/libsync/filesystem.cpp
FileSystem::setFileReadOnly() / FileSystem::setFileReadOnlyWeak() - these are the Windows callsites that apply the Deny ACE through SetNamedSecurityInfoW. filesystem.cpp:525 is the read-back / detection side ("Detected access denied ACL: assuming read-only").
src/libsync/propagatedownload.cpp
PropagateDownloadFileQNAM::downloadFinished() calls FileSystem::setFileReadOnlyWeak() on the temp path and then FileSystem::rename(tmp, final). Swapping those two, or moving the ACL step to the final path, fixes the bug.
The underlying defect is a TOCTOU-style ordering error: the step that enforces "final file should be read-only" is being applied to an intermediate path that the same code must immediately rename. On POSIX this is harmless because a directory-writable user can unlink a file regardless of the file's mode; on NTFS it is not, because MoveFileExW needs DELETE on the source ACL.
Suggested fix (any one of)
- Preferred. In
PropagateDownloadFileQNAM::downloadFinished(), perform FileSystem::rename(tmp, final) first, then FileSystem::setFileReadOnly(final, true). If the rename fails, don't apply the ACL.
- Alternative: never call
setFileReadOnly*() on the .~<HEX> path. Only apply the DACL to the final file, after every rename in the propagator has completed.
- Band-aid: when computing the new DACL on Windows, leave
DELETE granted to CREATOR OWNER / the interactive SID, so MoveFileExW can still succeed even if the ACL is applied too early. (Still leaves the Deny ACL on an orphan file after a crash, which is messy.)
Option 1 also fixes an existing side-effect: the Deny ACL is currently only ever removed by a successful rename, so a crash between the ACL write and the rename leaves a stuck file on disk that the user can't delete without elevation.
Scripts
I wrote two self-contained PowerShell scripts to (a) prove the diagnosis on any machine and (b) break the sync loop. Attached: Test-NextcloudError5.ps1, Fix-NextcloudError5.ps1, and three double-click wrappers (1-Diagnose.cmd, 2-Fix-DryRun.cmd, 3-Fix-Apply.cmd). The diagnostic is read-only and safe to run on any live sync; the fix refuses to change anything unless you pass -Apply and are elevated.
Output of the diagnostic on my machine (anonymized):
==== 2. Temp files (".<name>.~<HEX>") in <SHARE> folder =====================
Temp files found: 30
...30 files, all with hasDenyACE=True
==== 3. Healthy reference file in the same folder ==========================
Healthy file : <FILE_ref> (1,754,351 bytes)
Deny-ACE here?: False <-- should be False
==== 5. Nextcloud client log scan ==========================================
Log files scanned : 186
Matches "Rename failed" : 4950
Matches "Detected ...ACL" : 4950
Matches "WindowsError: 5" : 24750
Matches "inserted error" : 4956
==== 6. Verdict ============================================================
Verdict: CONFIRMED
Reason : All 30 temp files carry an explicit 'BUILTIN\Users Deny' ACE;
this is exactly the pattern that causes MoveFileEx to fail with error 5.
Running the fix (elevated, -Apply -StopClient) processed all 30 files with icacls /remove:d "BUILTIN\Users" + rename to the final name, and the client now reports them as up-to-date:
Candidates seen : 30
fixed : 30
skipped (no deny ACE) : 0
failed : 0
The fix is idempotent and does not restore write access to the files - it only removes the Deny ACL that should never have been on the temp name to begin with. The resulting final files still carry the inherited read-only Allow set, which matches the GDNVS remote permission correctly.
Attachments
All attachments are anonymized (host, user, sync paths, filenames, fileids, GUIDs stripped). GitHub's upload form refuses .cmd / .ps1, so every script file has been renamed with a trailing .txt — reviewers just remove the .txt suffix to run them.
Evidence (the actual proof — please look here first):
<stamp>_anon\transcript.log + summary.json + acls.txt — output of Test-NextcloudError5.ps1 on my machine. Shows 30 temp files, all with the identical non-inherited BUILTIN\Users Deny (…Delete) ACE, plus the log-scan counters and final Verdict: CONFIRMED.
fix_<stamp>_anon\transcript.log + summary.json — output of Fix-NextcloudError5.ps1 -Apply -StopClient. Shows the 30 temp files being un-stuck after the Deny ACE is removed with icacls /remove:d "BUILTIN\Users", proving the ACL is the sole cause.
Scripts (optional — only if reviewers want to reproduce on their own machine):
Test-NextcloudError5.ps1.txt — read-only diagnostic.
Fix-NextcloudError5.ps1.txt — the workaround (refuses to change anything without -Apply; needs elevation).
1-Diagnose.cmd.txt, 2-Fix-DryRun.cmd.txt, 3-Fix-Apply.cmd.txt — double-click wrappers. The last one self-elevates via UAC.
README.md
Happy to collect a full nextcloud.exe ETW trace or procmon capture showing the CreateFile/DELETE → ACCESS_DENIED on the temp handle if that helps reviewers.
acls.txt
transcript.log
summary.json
transcript.log
1-Diagnose.cmd.txt
2-Fix-DryRun.cmd.txt
Fix-NextcloudError5.ps1.txt
Test-NextcloudError5.ps1.txt
Steps to reproduce
Minimal repro (no server required)
This reproduces the exact Win32 call sequence the client performs, without needing a Nextcloud instance at all. Save and run on any Windows 10/11 box; the repro user just has to be a normal (non-admin) Users member.
$dir = "$env:TEMP\nc-error5-repro"
New-Item -ItemType Directory -Force -Path $dir | Out-Null
$tmp = Join-Path $dir '.sample.bin.~1234'
'hello' | Set-Content -LiteralPath $tmp
# Exactly what FileSystem::setFileReadOnly() ends up doing on Windows.
# (CA=CreateFiles, AD=AppendData, WEA=WriteExtendedAttributes,
# DC=DeleteSubdirectoriesAndFiles, DE=Delete)
icacls $tmp /deny 'BUILTIN\Users:(CA,AD,WEA,DC,DE)' | Out-Null
attrib +r +h $tmp
# What the propagator does next:
Rename-Item -LiteralPath $tmp -NewName 'sample.bin'
# => Rename-Item : Access to the path '...\.sample.bin.~1234' is denied.
Remove the Deny and the rename succeeds:
icacls $tmp /remove:d 'BUILTIN\Users' | Out-Null
Rename-Item -LiteralPath $tmp -NewName 'sample.bin' # works
Trigger with the real client
The trigger is any share whose server-side permission string lacks W. Discovery logs perm: "GDNVS" on every affected file:
| Letter |
Meaning |
Present |
| G |
can Get (read) |
✔ |
| D |
can Delete |
✔ |
| N |
can reName |
✔ |
| V |
can moVe |
✔ |
| S |
can reShare |
✔ |
| W |
can Write |
✗ |
Reproduction recipe:
- On the server, create a new Talk conversation, or share a folder with another user with "Can view" (no edit).
- As the other participant, upload an attachment to the conversation / drop a file into the shared folder.
- On Windows with the desktop client syncing that account, observe the
.<name>.~<HEX> temp appear and stay, client Activity tab showing repeated Windows error 5.
Expected behavior
Files are synced normally.
Which files are affected by this bug
<FILE_72>.~
Operating system
Windows
Which version of the operating system you are running.
Windows 11
Package
Official Windows MSI
Nextcloud Server version
Nextcloud Hub 26 Winter (33.0.2)
Nextcloud Desktop Client version
Nextcloud Desktop 33.0.2
Is this bug present after an update or on a fresh install?
Fresh desktop client install
Are you using the Nextcloud Server Encryption module?
Encryption is Disabled
Are you using an external user-backend?
Nextcloud Server logs
Additional info
No response
Bug description
Windows
Error 5: Access is deniedon every download in a Talk / read-only share – client applies aBUILTIN\Users Deny (Delete)ACE to the.~XXXXtemp file before renaming itTL;DR
The desktop client downloads a file from a remote whose permission string lacks
W(Talk attachments, read-only shares, "Can view" shares) to.<name>.~XXXX. Before renaming that temp file to its final name, it callsFileSystem::setFileReadOnly*(), which on Windows writes an explicit non-inheritedBUILTIN\Users Deny (CreateFiles, AppendData, WriteExtendedAttributes, DeleteSubdirectoriesAndFiles, Delete)ACE onto the temp file. The next call,MoveFileExW(.~XXXX → final), requiresDELETEon the source handle. Because the interactive user is a member ofBUILTIN\Usersand the Deny ACE is evaluated before theAuthenticated Users Allow ModifyACE, the rename returnsERROR_ACCESS_DENIED (5). The orphan temp file keeps its Deny ACL, so every subsequent sync runs into exactly the same error forever.Fix: apply the read-only ACL to the final path after the rename, not to the temp path before it. A simple reordering of two calls in
PropagateDownloadFileQNAM::downloadFinished()removes the bug.Environment
All host-, user- and file-identifying data in this report has been replaced by the placeholders shown in the table below.
C:\Program Files\Nextcloud\nextcloud.exe<HOST>(anonymized)<HOST>\<USER>– member ofBUILTIN\Users,BUILTIN\Administrators(deny-only under UAC),NT AUTHORITY\Authenticated Users<SYNC_ROOT>(a regular local NTFS directory, e.g.C:\Users\<USER>\Nextcloud)<SYNC_ROOT>\<SHARE>\– a folder that is a Nextcloud Talk conversation share (attachments sent into a group chat by other participants)"GDNVS"(read/delete/rename/move/reshare, no write)<HOST>= Windows computer name,<USER>= Windows account name,<SYNC_ROOT>= local sync directory,<SHARE>= name of the failing Talk/read-only folder,<FILE_n>= actual filename of the n-th affected file,<FILEID>= Nextcloudfileid,<HEX>= the 1-4 char hex id the client appends after.~Reproduced with a fresh install of 33.0.2 on two Windows 11 machines owned by the same account.
Symptom
30 files in
<SYNC_ROOT>\<SHARE>\never finish syncing. For each of them the full.<name>.~<HEX>temp is on disk at the correct size, but the final rename never happens. The client is locked in a retry loop. Counters below come from the attached diagnostic transcript, summed across 186 rotated client logs:Rename failed: …\.~<HEX> => …Detected access denied ACL: assuming read-onlyWindowsError: 5: Access is denied.inserted error item "<SHARE>/<FILE_n>"Representative line-for-line sequence for one affected file (names replaced with placeholders):
This repeats ~165 times per file over the life of the log archive. No HTTP/network/e2ee/hydration failure anywhere in the ~630 000 scanned log lines - the failure is local and deterministic.
Why it is not my FS / AV / permissions
<SYNC_ROOT>\<SHARE>itself has no non-inherited Deny, only the defaultSYSTEM/Administrators/Users/Authenticated UsersAllow set.Deny-ACE here? False, attributesArchive.<HOST>\<USER>,icacls … /denyworks, Defender does not log any quarantine or access block, Controlled Folder Access is off. Removing the Deny ACE (and nothing else) makes the rename succeed.FILE_ATTRIBUTE_READONLYattribute. Windows ignoresReadOnlyon rename; the failure is purely due to the DACL.The DACL the client puts on the temp file
Dumped from every one of the 30 stuck temp files - identical on all of them (values anonymized):
All ACEs are
isInherited=False, i.e. the client is explicitly writing a full DACL onto each temp file, including the Deny. This is whatfilesystem.cpp:525 "Detected access denied ACL: assuming read-only"later keys off of.Why this causes
ERROR_ACCESS_DENIED (5)on renameWindows evaluates explicit Deny ACEs before explicit Allow. The interactive user is in
BUILTIN\Users, soDeleteis explicitly denied to him on the source.MoveFileExWrenames in place by opening the source withDELETEand callingNtSetInformationFile(FileRenameInformation)- it needsDELETEon the source handle. The Deny is matched first,CreateFilewithDELETEfails withERROR_ACCESS_DENIED, and the whole operation returns 5. The client catches this asSoftError, records an"inserted error item", and moves on; the orphan temp retains its bad DACL, so it is re-detected on every subsequent sync.Where in the code this happens
src/libsync/filesystem.cppFileSystem::setFileReadOnly()/FileSystem::setFileReadOnlyWeak()- these are the Windows callsites that apply the Deny ACE throughSetNamedSecurityInfoW.filesystem.cpp:525is the read-back / detection side ("Detected access denied ACL: assuming read-only").src/libsync/propagatedownload.cppPropagateDownloadFileQNAM::downloadFinished()callsFileSystem::setFileReadOnlyWeak()on the temp path and thenFileSystem::rename(tmp, final). Swapping those two, or moving the ACL step to the final path, fixes the bug.The underlying defect is a TOCTOU-style ordering error: the step that enforces "final file should be read-only" is being applied to an intermediate path that the same code must immediately rename. On POSIX this is harmless because a directory-writable user can unlink a file regardless of the file's mode; on NTFS it is not, because
MoveFileExWneedsDELETEon the source ACL.Suggested fix (any one of)
PropagateDownloadFileQNAM::downloadFinished(), performFileSystem::rename(tmp, final)first, thenFileSystem::setFileReadOnly(final, true). If the rename fails, don't apply the ACL.setFileReadOnly*()on the.~<HEX>path. Only apply the DACL to the final file, after every rename in the propagator has completed.DELETEgranted toCREATOR OWNER/ the interactive SID, soMoveFileExWcan still succeed even if the ACL is applied too early. (Still leaves the Deny ACL on an orphan file after a crash, which is messy.)Option 1 also fixes an existing side-effect: the Deny ACL is currently only ever removed by a successful rename, so a crash between the ACL write and the rename leaves a stuck file on disk that the user can't delete without elevation.
Scripts
I wrote two self-contained PowerShell scripts to (a) prove the diagnosis on any machine and (b) break the sync loop. Attached:
Test-NextcloudError5.ps1,Fix-NextcloudError5.ps1, and three double-click wrappers (1-Diagnose.cmd,2-Fix-DryRun.cmd,3-Fix-Apply.cmd). The diagnostic is read-only and safe to run on any live sync; the fix refuses to change anything unless you pass-Applyand are elevated.Output of the diagnostic on my machine (anonymized):
Running the fix (elevated,
-Apply -StopClient) processed all 30 files withicacls /remove:d "BUILTIN\Users"+ rename to the final name, and the client now reports them as up-to-date:The fix is idempotent and does not restore write access to the files - it only removes the Deny ACL that should never have been on the temp name to begin with. The resulting final files still carry the inherited read-only Allow set, which matches the
GDNVSremote permission correctly.Attachments
All attachments are anonymized (host, user, sync paths, filenames, fileids, GUIDs stripped). GitHub's upload form refuses
.cmd/.ps1, so every script file has been renamed with a trailing.txt— reviewers just remove the.txtsuffix to run them.Evidence (the actual proof — please look here first):
<stamp>_anon\transcript.log+summary.json+acls.txt— output ofTest-NextcloudError5.ps1on my machine. Shows 30 temp files, all with the identical non-inheritedBUILTIN\Users Deny (…Delete)ACE, plus the log-scan counters and finalVerdict: CONFIRMED.fix_<stamp>_anon\transcript.log+summary.json— output ofFix-NextcloudError5.ps1 -Apply -StopClient. Shows the 30 temp files being un-stuck after the Deny ACE is removed withicacls /remove:d "BUILTIN\Users", proving the ACL is the sole cause.Scripts (optional — only if reviewers want to reproduce on their own machine):
Test-NextcloudError5.ps1.txt— read-only diagnostic.Fix-NextcloudError5.ps1.txt— the workaround (refuses to change anything without-Apply; needs elevation).1-Diagnose.cmd.txt,2-Fix-DryRun.cmd.txt,3-Fix-Apply.cmd.txt— double-click wrappers. The last one self-elevates via UAC.README.md
Happy to collect a full
nextcloud.exeETW trace orprocmoncapture showing theCreateFile/DELETE → ACCESS_DENIEDon the temp handle if that helps reviewers.acls.txt
transcript.log
summary.json
transcript.log
1-Diagnose.cmd.txt
2-Fix-DryRun.cmd.txt
Fix-NextcloudError5.ps1.txt
Test-NextcloudError5.ps1.txt
Steps to reproduce
Minimal repro (no server required)
This reproduces the exact Win32 call sequence the client performs, without needing a Nextcloud instance at all. Save and run on any Windows 10/11 box; the repro user just has to be a normal (non-admin)
Usersmember.Remove the Deny and the rename succeeds:
Trigger with the real client
The trigger is any share whose server-side permission string lacks
W. Discovery logsperm: "GDNVS"on every affected file:Reproduction recipe:
.<name>.~<HEX>temp appear and stay, client Activity tab showing repeatedWindows error 5.Expected behavior
Files are synced normally.
Which files are affected by this bug
<FILE_72>.~
Operating system
Windows
Which version of the operating system you are running.
Windows 11
Package
Official Windows MSI
Nextcloud Server version
Nextcloud Hub 26 Winter (33.0.2)
Nextcloud Desktop Client version
Nextcloud Desktop 33.0.2
Is this bug present after an update or on a fresh install?
Fresh desktop client install
Are you using the Nextcloud Server Encryption module?
Encryption is Disabled
Are you using an external user-backend?
Nextcloud Server logs
Additional info
No response