PUBLIC MIRROR A read-only public view of Anvil. Only publicly-disclosed findings are shown; the Playbook, techniques, sessions and embargoed research are hidden.

← Findings

LIBTIFF-0003

medium verified

Heap OOB read (+ misaligned load) in JPEGEncodeRaw 12-bit path — packed samples read as 2-byte

✓ Disclosure ready Real-world verified and packaged for disclosure (or already carries a CVE / advisory).
Crash heap-buffer-overflow (READ, 2 bytes) + misaligned load (odd width)
Topmost entry point TIFFWriteEncodedStrippublic API
Verified through libtiff TIFFWriteEncodedStrip (public API) built against libjpeg-turbo 3.1.4.1 dual-mode, clang-20 ASan+UBSan
Real-world impact 6.5 CVSS · medium
Reproduction command (asan)
libjpeg-turbo>=3.0 dual mode + libtiff -Djpeg=ON under clang-20 ASan+UBSan (same toolchain as verify.sh LIBTIFF-0002); compile pocs/libtiff/LIBTIFF-0003/encode_poc.c against build-jpeg12 libtiff + turbo3 libjpeg
./enc 16 /tmp/out.tif  (even width -> heap OOB read) ; ./enc 17 /tmp/out.tif (odd -> misaligned load UB)
# expect: AddressSanitizer: heap-buffer-overflow READ of size 2 in JPEGEncodeRaw at tif_jpeg.c:2496 (buffer sized TIFFStripSize); on odd width, UBSan misaligned load of J12SAMPLE at tif_jpeg.c:2496

Classification

Targetlibtiff
ComponentJPEG codec
Locationlibtiff/tif_jpeg.c · JPEGEncodeRaw:2486/2496 (read); bytesperclumpline 2447-2453
Entry point TIFFWriteEncodedStrip public API
TIFFWriteEncodedStrip → JPEGEncodeRaw
Reached from the public TIFFWriteEncodedStrip / TIFFWriteScanline when writing a 12-bit subsampled YCbCr JPEG with the DEFAULT (RAW) JPEGColorMode — i.e. an app supplying raw pre-subsampled sample data (attacker-influenced when transcoding untrusted 12-bit images that way). Requires libtiff built vs libjpeg-turbo >= 3.0 dual mode (HAVE_JPEGTURBO_DUAL_MODE_8_12), the modern distro default. tiffcp/tiff2rgba set JPEGCOLORMODE_RGB (→ non-raw JPEGEncode) and are unaffected.
Vuln classoob-read
CVE
CVSS6.5 (CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:L)
Discovered2026-07-02

Verification

Evidence ✓ Disclosure ready (real-world verified)
Verified throughlibtiff TIFFWriteEncodedStrip (public API) built against libjpeg-turbo 3.1.4.1 dual-mode, clang-20 ASan+UBSan
Harness fired✅ yes
Protocol2.0
Sanitizerasan
Crash typeheap-buffer-overflow (READ, 2 bytes) + misaligned load (odd width)
ReproTIFFOpen w; set width(=16 even or 17 odd) length=16 bps=12 spp=3 photometric=YCbCr compression=JPEG subsampling=2,2 rowsperstrip=16; buf=calloc(TIFFStripSize); TIFFWriteEncodedStrip(0,buf,TIFFStripSize) -> ASan heap-buffer-overflow READ at tif_jpeg.c:2496 (all tested widths 8/16/24/32/48). Odd widths additionally trip UBSan misaligned load.

Reproduce / self-verify

# build
libjpeg-turbo>=3.0 dual mode + libtiff -Djpeg=ON under clang-20 ASan+UBSan (same toolchain as verify.sh LIBTIFF-0002); compile pocs/libtiff/LIBTIFF-0003/encode_poc.c against build-jpeg12 libtiff + turbo3 libjpeg
# run
./enc 16 /tmp/out.tif  (even width -> heap OOB read) ; ./enc 17 /tmp/out.tif (odd -> misaligned load UB)
# expect (asan):
AddressSanitizer: heap-buffer-overflow READ of size 2 in JPEGEncodeRaw at tif_jpeg.c:2496 (buffer sized TIFFStripSize); on odd width, UBSan misaligned load of J12SAMPLE at tif_jpeg.c:2496

cost: medium (build libjpeg-turbo 3.x dual mode; tiny input)

Disclosure

Reported tolibtiff maintainers (F. Warmerdam, B. Friesenhahn; cc E. Rouault) — private coordinated-disclosure email
Reported2026-07-03
Vendor ack
Embargo until
Public2026-07-04
Patched in

PoC: pocs/libtiff/LIBTIFF-0003/encode_poc.c

Writeup

Summary

JPEGEncodeRaw() packs the caller’s raw subsampled samples into libjpeg’s downsampled buffers. It reads the input as inptr = (TIFF_JSAMPLE *)buf + clumpoffset (tif_jpeg.c:2479) and consumes samples_per_clump * clumps_per_line samples per clumpline (reads at 2486 / 2496, inptr += samples_per_clump). In the 12-bit build TIFF_JSAMPLE is a 2-byte J12SAMPLE, so it reads 2 * samples_per_clump * clumps_per_line bytes per clumpline.

But 12-bit TIFF sample data is stored bit-packed (12 bits/sample, 1.5 bytes) — the decode side packs it (tif_jpeg.c:1811-1828) — and the per-clumpline stride bytesperclumpline (tif_jpeg.c:2447-2453) is correctly computed as ceil(clumps * (h*v+2) * data_precision / 8) ≈ 1.5×, matching TIFFStripSize. The encoder therefore reads 2 bytes/sample from a 1.5-byte/sample buffer and runs ~0.5 * samples_per_clumpline bytes past the end of the TIFFStripSize-sized input strip on the final clumpline:

  • even width → heap-buffer-overflow READ (tif_jpeg.c:2496)
  • odd widthbuf += bytesperclumpline advances an odd number of bytes, so (J12SAMPLE*)buf is misaligned → UBSan misaligned-load UB

The over-read bytes are entropy-coded into the output JPEG, i.e. adjacent heap memory is disclosed into attacker-observable output.

Reproduction

pocs/libtiff/LIBTIFF-0003/encode_poc.c writes a 12-bit YCbCr 2×2 JPEG via TIFFWriteEncodedStrip, buffer sized to TIFFStripSize() (canonical usage), default RAW JPEGColorMode.

./enc 16 /tmp/out.tif
==ERROR: AddressSanitizer: heap-buffer-overflow READ of size 2
    #0 JPEGEncodeRaw        libtiff/tif_jpeg.c:2496
    #1 TIFFWriteEncodedStrip libtiff/tif_write.c:342
  allocated by: calloc(TIFFStripSize=576) in main
SUMMARY: heap-buffer-overflow tif_jpeg.c:2496 in JPEGEncodeRaw

Fires for every tested width (8/16/24/32/48). See repro.log.

Root cause

The 12-bit raw encode path treats a bit-packed 12-bit input buffer as an array of 2-byte J12SAMPLE. Either the input must be unpacked to 2-byte samples (and bytesperclumpline/TIFFStripSize sized to match), or the reads must honor the packed layout. As written, TIFFStripSize (1.5×, packed) disagrees with the encoder’s 2× read, so a caller trusting TIFFStripSize over-reads. This is the encode-side sibling of LIBTIFF-0002 (the 12-bit downsampled sizing mismatch).

Verification evidence

Reproduced end-to-end through the public TIFFWriteEncodedStrip API of libtiff 4.7.2 built against libjpeg-turbo 3.1.4.1 (dual 8/12-bit mode) under clang-20 ASan+UBSan: heap-buffer-overflow READ at tif_jpeg.c:2496 on a TIFFStripSize-sized buffer, plus misaligned-load UB for odd widths.

Impact

Apply impact-rubric: severity medium. Heap out-of-bounds read reachable from the public strip/tile write API when encoding 12-bit subsampled YCbCr JPEG in raw mode, with canonical buffer sizing (TIFFStripSize). Two consequences: (1) information disclosure — adjacent heap bytes are encoded into the output JPEG, observable by whoever receives the file; (2) potential crash on an unmapped page. Narrower than the decode-side write bug (LIBTIFF-0002): the raw-encode path requires an application that supplies pre-subsampled 12-bit data without JPEGCOLORMODE_RGB, and a 12-bit (turbo ≥3.0 dual-mode) libtiff. No OOB write and no integrity impact. The 12-bit raw-encode path is also functionally broken (mis-packs), so it is likely rarely exercised in the wild.