fix: release the stream when a bitmap preview cannot be decoded - #41835
Merged
Merged
Conversation
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>
This comment was marked as resolved.
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
approved these changes
Sep 16, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Bitmap::getThumbnail()opens the file and closes it only on the success path — thecatcharoundgetResizedPreview()returns before thefclose(). 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
masterand 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. Sooccpreview pre-generation, or a cron preview job, over a directory of.heic/.psdfiles leaks a handle per file untilEMFILE.Second defect, same three lines
$file->fopen('r')was unchecked. A storage that cannot open the file returnsfalse, andstream_get_contents(false)raises aTypeError— an\Error, so it escapes thecatch (\Exception)directly underneath and surfaces as a 500 instead of the missing preview every other failure here degrades to. Confirmed, not theorised:What changed
fclose()moves into afinally, so it runs on both paths.falsereturn fromfopen()is handled explicitly and logged.Tests
New
tests/lib/Preview/BitmapStreamTest.php, 3 cases. It asserts the contract directly viais_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 (theTypeErrorabove); the success-path case passed unfixed, as the control. GREEN after: 3 tests, 5 assertions.Verification
tests/lib/Preview/onowncloudci/php:8.3: 55 tests, 152 assertions, 0 failures.make test-php-style: 0 of 2435 files need fixing.php -lclean 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/fcloseblock.🤖 Generated with Claude Code