Skip to content

[stable32] fix(SecureViewService): handle fopen() returning false for non-existent paths#5584

Merged
elzody merged 2 commits intostable32from
backport/5577/stable32
Apr 22, 2026
Merged

[stable32] fix(SecureViewService): handle fopen() returning false for non-existent paths#5584
elzody merged 2 commits intostable32from
backport/5577/stable32

Conversation

@backportbot
Copy link
Copy Markdown

@backportbot backportbot Bot commented Apr 22, 2026

Backport of PR #5577

chrip and others added 2 commits April 22, 2026 20:48
…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>
… on delete

The previous fix (returning true when fopen() returns false) resolved the
rename/copy TypeError, but delete still failed. When files_trashbin moves
a watermarked file to trash it calls:

  trashStorage->moveFromStorage(sourceStorage, srcPath, trashPath)

The SecureViewWrapper on trashStorage calls shouldSecure(srcPath,
sourceStorage, tryOpen=true). shouldSecure then calls
sourceStorage->fopen(), which hits SecureViewWrapper::fopen() →
checkFileAccess() → ForbiddenException because the file is watermarked
and the request is not a WOPI request.

SecureViewWrapper does not override file_exists(), so replacing fopen()
with file_exists() goes straight to the underlying storage without any
access check. When the path does not exist we return true early (same
semantic: assume target is in a secure context); when it does exist we
fall through to the cache-based shouldWatermark() check as before.

Signed-off-by: Christoph Schaefer <christoph.schaefer@nextcloud.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@backportbot backportbot Bot requested a review from elzody as a code owner April 22, 2026 20:48
@backportbot backportbot Bot requested a review from elzody April 22, 2026 20:48
@backportbot backportbot Bot requested a review from juliusknorr as a code owner April 22, 2026 20:48
@backportbot backportbot Bot requested a review from chrip April 22, 2026 20:48
@backportbot backportbot Bot added the 3. to review Ready to be reviewed label Apr 22, 2026
@elzody elzody merged commit 2808af2 into stable32 Apr 22, 2026
69 checks passed
@elzody elzody deleted the backport/5577/stable32 branch April 22, 2026 22:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

3. to review Ready to be reviewed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants