Repository navigation
Close the TIFF on readTIFF() tiled-image error paths - #16
Open
billdenney wants to merge 2 commits into
Open
billdenney wants to merge 2 commits into
billdenney wants to merge 2 commits into
Conversation
Fixes s-u#13 write_tiff() opens the TIFF at src/write.c:66 with a pointer to the stack-allocated tiff_job_t declared at src/write.c:31, but TIFFClose() is only reached on the success path. Every Rf_error() raised after TIFF_Open() therefore longjmps back to R leaving the TIFF open, the output FILE* unclosed, and common.c's static last_tiff pointing at a TIFF whose client data is a stack frame that no longer exists. That stale pointer is what crashes. TIFF_Open() assigns last_tiff only after TIFFClientOpen() returns, so when a later readTIFF() hits an unreadable header, libtiff raises the error from inside TIFFClientOpen() while last_tiff still refers to the dead write job. TIFFErrorHandler_() then calls TIFFClose(last_tiff) on it and dereferences the unwound frame. This also explains why only a failing read crashes: a successful read never invokes the error handler, so it merely overwrites last_tiff. Reproducible example (crashed 10/10 runs before this change, with the symptom alternating between "caught segfault", "free(): invalid size" and "glibc detected an invalid stdio handle"; clean 20/20 after): bad <- tempfile(fileext = ".tif") good <- tempfile(fileext = ".tif") file.create(good) try(tiff::writeTIFF(array(runif(80), dim = c(4L, 4L, 5L)), bad), silent = TRUE) tiff::readTIFF(good) Route the five Rf_error() calls sited after TIFF_Open() through a helper that closes the TIFF first. TIFFClose() runs TIFFCloseProc_(), which fcloses the file or frees the in-memory buffer and resets last_tiff, so it is the complete cleanup for both output targets. read.c already uses this same TIFFClose()-then-Rf_error() pattern at lines 380 and 385; this applies it to write.c. Error messages and all success paths are unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Follow-on to the writeTIFF() fix for s-u#13, which reported the crash only via write_tiff(). read_tiff() has two instances of the same defect. The tiled-image branch raises errors at src/read.c:543 and :546 without closing the TIFF first, unlike the equivalent checks at :380 and :385. As in write_tiff(), the TIFF's client data is the stack-allocated tiff_job_t at src/read.c:173, and common.c's static last_tiff keeps pointing at it after Rf_error() longjmps out. The next libtiff error makes TIFFErrorHandler_() call TIFFClose(last_tiff) on a frame that no longer exists. Confirmed against a build that already carries the write_tiff() fix, so this is an independent reproducer: ## a 16x16 tiled RGB TIFF with PlanarConfiguration = SEPARATE try(tiff::readTIFF("tiled_planar.tif"), silent = TRUE) #> "Planar format tiled images are not supported" f <- tempfile(fileext = ".tif"); file.create(f) tiff::readTIFF(f) #> *** caught segfault *** Crashed 15/15 runs before this change, clean 15/15 after. As with the write path, following the failed read with a *successful* read is harmless: a successful open never invokes the error handler, so it only overwrites last_tiff rather than dereferencing it. Also close the file handle when TIFF_Open() itself returns NULL at src/read.c:202. That path is defensive rather than observed: libtiff reports open failures through TIFFErrorHandler_(), which calls Rf_error() and longjmps, so TIFF_Open() generally does not return at all on failure. Error messages and all success paths are unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.
Stacked on the
writeTIFF()fix for #13 — please merge #15 first. Theissue reported the crash only through
write_tiff();read_tiff()has twoinstances of the same defect.
Cause
The tiled-image branch raises errors at
src/read.c:543and:546withoutclosing the TIFF first:
The equivalent checks at
:380and:385already doTIFFClose(tiff);first. As in
write_tiff(), the TIFF's client data is the stack-allocatedtiff_job_tatsrc/read.c:173, andcommon.c's staticlast_tiffkeepspointing at it after
Rf_error()longjmps out. The next libtiff error makesTIFFErrorHandler_()callTIFFClose(last_tiff)on a frame that no longerexists.
Reproducible example
Confirmed against a build that already carries the
write_tiff()fix, sothis is an independent reproducer rather than a re-run of the original.
The trigger needs a tiled TIFF with
PlanarConfiguration = SEPARATE, whichwriteTIFF()cannot produce. This builds a minimal one with no extradependencies:
mk_tiled_planar() — 16x16 tiled RGB, PLANARCONFIG_SEPARATE
Expected
Error: pkg:tiff: Cannot read TIFF header.Environment: tiff 0.1-12, R 4.6.1, x86_64-pc-linux-gnu, Ubuntu 24.04.4,
libtiff 4.5.1+git230720-4ubuntu2.5.
Verification
Each check ran in its own
Rscriptprocess with stdin closed, since asegfault cannot be caught by
tryCatch(). Baseline is a build that alreadyhas the
write_tiff()fix applied.Repeating R2, since the symptom is nondeterministic: 0/15 clean before,
15/15 clean after.
As with the write path, following the failed read with a successful read
(R3) is harmless: a successful open never invokes the error handler, so it
only overwrites
last_tiffrather than dereferencing it.R CMD build+R CMD check --no-manual: Status: OK. Thewrite_tiff()regression checks from the parent PR all still pass against this build.
Also included
src/read.c:202now closes the file handle whenTIFF_Open()returns NULL.This one is defensive rather than observed: libtiff reports open failures
through
TIFFErrorHandler_(), which callsRf_error()and longjmps, soTIFF_Open()generally does not return at all on failure.Not changed here
A failed read still leaks the
FILE*opened atsrc/read.c:196wheneverlibtiff raises the error from inside
TIFFClientOpen()— the handlerlongjmps while
fis open and before anyTIFF*exists to carry it, so nosite-level
fclose()can catch it. Fixing that needs an unwind protector(or for the error handler to know about the pending
FILE*), which seemedout of scope for a targeted crash fix.
Error messages and all success paths are unchanged.