Skip to content

fix: release the stream when a bitmap preview cannot be decoded - #41835

Merged
phil-davis merged 2 commits into
masterfrom
fix/oc10-164-bitmap-stream-leak
Sep 16, 2026
Merged

phil-davis merged 2 commits into
masterfrom
fix/oc10-164-bitmap-stream-leak

Conversation

@oc-tmueller

Copy link
Copy Markdown
Contributor

Summary

Bitmap::getThumbnail() opens the file and closes it only on the success path — the catch around getResizedPreview() returns before the fclose(). Every failed decode therefore leaks one file descriptor for the lifetime of the process.

Flagged as out of scope in #41827 ("that's a resource leak, not a security defect, and deserves its own focused PR"), so this is that PR. It targets master and is independent of the #41827/#41834 chain.

Why it matters more than it looks

A failed decode is not an edge case. Any content ImageMagick has no coder for lands in that catch, and #41834 makes throwing a designed outcome — if a build does not register a provider's coder, the pin throws rather than falling back to content sniffing. So occ preview pre-generation, or a cron preview job, over a directory of .heic/.psd files leaks a handle per file until EMFILE.

Second defect, same three lines

$file->fopen('r') was unchecked. A storage that cannot open the file returns false, and stream_get_contents(false) raises a TypeError — an \Error, so it escapes the catch (\Exception) directly underneath and surfaces as a 500 instead of the missing preview every other failure here degrades to. Confirmed, not theorised:

TypeError: stream_get_contents(): Argument #1 ($stream) must be of type resource, false given
  lib/private/Preview/Bitmap.php:89
  lib/private/Preview/Bitmap.php:51

What changed

  • fclose() moves into a finally, so it runs on both paths.
  • A false return from fopen() is handled explicitly and logged.

Tests

New tests/lib/Preview/BitmapStreamTest.php, 3 cases. It asserts the contract directly via is_resource($stream) on the caller's own handle rather than counting descriptors, so it is portable rather than Linux-only.

Confirmed RED before the fix on owncloudci/php:8.3 — 1 failure (the stream must be closed on the failure path) and 1 error (the TypeError above); the success-path case passed unfixed, as the control. GREEN after: 3 tests, 5 assertions.

Verification

  • tests/lib/Preview/ on owncloudci/php:8.3: 55 tests, 152 assertions, 0 failures.
  • make test-php-style: 0 of 2435 files need fixing.
  • php -l clean under PHP 7.4, so this backports to the 10.x line without syntax changes.

Note on sequencing

#41827 modifies these same lines. Whichever lands second needs a trivial rebase — the conflict is confined to the try/catch/fclose block.

🤖 Generated with Claude Code

Bitmap::getThumbnail() opened the file and closed it only on the success path.
The catch around getResizedPreview() returned without closing, so every failed
decode leaked one file descriptor for the lifetime of the process. A preview
pre-generation run or a cron preview job over a directory of files ImageMagick
has no coder for exhausts the descriptors one file at a time.

The open was also unchecked. A storage that cannot open the file returns false
rather than throwing, and stream_get_contents(false) raises a TypeError - an
\Error, so it escapes the \Exception handler directly underneath and surfaces as
a 500 instead of the missing preview every other failure here degrades to.

Closing moves into a finally block, and a false return from fopen() is handled
explicitly.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Thomas Müller <323649642+oc-tmueller@users.noreply.github.com>
@oc-tmueller
oc-tmueller requested a review from a team as a code owner September 16, 2026 10:33
@update-docs

This comment was marked as resolved.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Thomas Müller <323649642+oc-tmueller@users.noreply.github.com>
@phil-davis
phil-davis merged commit 0d0a306 into master Sep 16, 2026
31 checks passed
@phil-davis
phil-davis deleted the fix/oc10-164-bitmap-stream-leak branch September 16, 2026 11:41
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.

2 participants