Skip to content

fix(SecureViewService): handle fopen() returning false for non-existent paths#5577

Open
chrip wants to merge 1 commit intomainfrom
fix/secure-view-fclose-on-nonexistent-file
Open

fix(SecureViewService): handle fopen() returning false for non-existent paths#5577
chrip wants to merge 1 commit intomainfrom
fix/secure-view-fclose-on-nonexistent-file

Conversation

@chrip
Copy link
Copy Markdown

@chrip chrip commented Apr 20, 2026

Summary

When shouldSecure() is called with tryOpen=true on a path that does not
exist yet — such as a rename target or a files_versions snapshot path —
the underlying fopen() call returns false. In PHP 8, the subsequent
fclose(false) throws a TypeError that propagates as an uncaught
exception, aborting the entire DAV operation with HTTP 500.

This causes all rename and file-overwrite operations to fail silently
for users when Secure View watermarking is active.

Error in nextcloud.log (searchable)

TypeError: fclose(): Argument #1 ($stream) must be of type resource, bool given
in …/richdocuments/lib/Service/SecureViewService.php line 37

Stack trace for rename (MOVE):

SecureViewService->shouldSecure()         SecureViewService.php:37
SecureViewWrapper->checkSourceAndTarget() SecureViewWrapper.php:89
SecureViewWrapper->rename()               SecureViewWrapper.php:73

Stack trace for file overwrite / version snapshot (PUT):

SecureViewService->shouldSecure()         SecureViewService.php:37
SecureViewWrapper->checkSourceAndTarget() SecureViewWrapper.php:89
SecureViewWrapper->copy()                 SecureViewWrapper.php:55
OCA\Files_Versions\Versions\LegacyVersionsBackend->createVersion()

How to reproduce

  1. occ config:app:set files watermark_enabled --value="yes"
  2. occ config:app:set files watermark_allGroups --value="yes"
  3. occ config:app:set files watermark_allGroupsList --value="admin"
  4. Upload any .docx or .pdf, then rename it → HTTP 500 / TypeError in log
  5. Re-upload the same file (triggers version snapshot) → same error

Fix

When fopen() returns false (file does not exist yet), return true
(assume the target path will be in a secure context). This causes
checkSourceAndTarget to evaluate source=true && !target=!true=false
and allow the operation instead of throwing ForbiddenException.

This is safe because:

  • checkFileAccess always passes tryOpen=false and is completely unaffected
  • rename() and copy() operate within the same storage mount, so the
    target inherits the same watermark conditions as the source
  • Returning false instead causes ForbiddenException on every rename
    when Secure View is active — equally broken, just differently

Checklist

  • Code is properly formatted
  • Sign-off message is added to all commits
  • Documentation has been updated or is not required

…nt paths

When shouldSecure() is called with tryOpen=true on a path that does not
exist yet (e.g. a rename target or a files_versions snapshot path), the
underlying fopen() call returns false. Calling fclose(false) on that
value throws a TypeError in PHP 8, which propagates as an uncaught
exception and aborts the entire DAV operation with HTTP 500.

To reproduce: enable Secure View watermarking for a group, add your
user to that group, then rename or overwrite any Office document. The
rename fails immediately with HTTP 500 and the following error appears
in nextcloud.log:

  fclose(): Argument #1 ($stream) must be of type resource, bool given
  in …/richdocuments/lib/Service/SecureViewService.php:37

Signed-off-by: Christoph Schaefer <christoph.schaefer@nextcloud.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant