CVE-2026-6306
Overview
Files Changed
third_party/libtiff/0034-tiff-jpeg-overflow.patchthird_party/libtiff/README.pdfiumthird_party/libtiff/tif_jpeg.c
Patch
From ca8a943c247c208fd7a9cd21b4de049f22b93070 Mon Sep 17 00:00:00 2001 From: Lei Zhang <[email protected]> Date: Fri, 27 Mar 2026 14:52:16 -0700 Subject: [PATCH] Patch an overflow in libtiff Apply fix [1] from upstream, which is not in the most recent versioned release. [1] https://gitlab.com/libtiff/libtiff/-/commit/0f726d9 Bug: 496907110 Change-Id: Ic8665879ebdd4445f473e9a1e156cfc42c294d51 Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/145550 Reviewed-by: Andy Phan <[email protected]> Commit-Queue: Lei Zhang <[email protected]> --- diff --git a/third_party/libtiff/0034-tiff-jpeg-overflow.patch b/third_party/libtiff/0034-tiff-jpeg-overflow.patch new file mode 100644 index 0000000..f03fa94 --- /dev/null +++ b/third_party/libtiff/0034-tiff-jpeg-overflow.patch @@ -0,0 +1,25 @@ +commit 0f726d9477a11e15eb67ca349c03907f6cfb82a9 +Author: Mikhail Khachaiants <[email protected]> +Date: Mon Dec 1 22:26:34 2025 +0200 + + tif_jpeg: reject mismatched JPEG data precision to avoid write overflow + + Ensure TIFF BitsPerSample matches both BITS_IN_JSAMPLE and the JPEG + header data_precision for JPEG-compressed images. This prevents + under-sized scanline buffers that can lead to write buffer overflows + in jdcolor.c/null_convert when decoding malformed inputs. + +diff --git a/libtiff/tif_jpeg.c b/libtiff/tif_jpeg.c +index aba5f99b..4d6370b5 100644 +--- a/libtiff/tif_jpeg.c ++++ b/libtiff/tif_jpeg.c +@@ -1282,7 +1282,8 @@ int TIFFJPEGIsFullStripRequired(TIFF *tif) + sp->cinfo.d.data_precision = td->td_bitspersample; + sp->cinfo.d.bits_in_jsample = td->td_bitspersample; + #else +- if (sp->cinfo.d.data_precision != td->td_bitspersample) ++ if (td->td_bitspersample != BITS_IN_JSAMPLE || ++ sp->cinfo.d.data_precision != td->td_bitspersample) + { + TIFFErrorExtR(tif, module, "Improper JPEG data precision"); + return (0); diff --git a/third_party/libtiff/README.pdfium b/third_party/libtiff/README.pdfium index 9953e76..e3f352d 100644 --- a/third_party/libtiff/README.pdfium +++ b/third_party/libtiff/README.pdfium @@ -19,3 +19,4 @@ 0028-nstrips-OOM.patch: return error for excess number of tiles/strips. 0031-safe_size_ingtStripContig.patch: return error if the size to read overflow from int32. 0033-avail-out-overflow.patch: signed comparison in PixarLogDecode(). +0034-tiff-jpeg-overflow.patch: reject mismatched JPEG data precision. diff --git a/third_party/libtiff/tif_jpeg.c b/third_party/libtiff/tif_jpeg.c index 5281457..a9764f0 100644 --- a/third_party/libtiff/tif_jpeg.c +++ b/third_party/libtiff/tif_jpeg.c @@ -1287,7 +1287,8 @@ sp->cinfo.d.data_precision = td->td_bitspersample; sp->cinfo.d.bits_in_jsample = td->td_bitspersample; #else - if (sp->cinfo.d.data_precision != td->td_bitspersample) + if (td->td_bitspersample != BITS_IN_JSAMPLE || + sp->cinfo.d.data_precision != td->td_bitspersample) { TIFFErrorExtR(tif, module, "Improper JPEG data precision"); return (0);
Original Bug Report
OOB write in PDFium TIFF decoder via lossless JPEG data precision mismatch
Summary
A heap buffer overflow write exists in PDFium’s TIFF image decoder when processing a TIFF file that uses JPEG compression with a lossless JPEG codestream (SOF3) whose data precision is less than 8 bits. The bundled libtiff calculates the strip buffer size based on the TIFF BitsPerSample tag (e.g. 2 bits per sample), while the bundled libjpeg-turbo, which has lossless JPEG support enabled in Chromium, outputs decoded samples at 8 bits each. This mismatch causes libjpeg-turbo to write 4x more data than the allocated buffer can hold, producing an attacker-controlled heap overflow in the renderer process. The bug affects all platforms and is reachable through XFA PDF forms when the PdfXfaSupport feature is enabled. No user interaction beyond opening a PDF is required.
Bisect
Introducing Commit: 4aa3b725c (PDFium)
- Date: 2025-10-23
- Author: Lei Zhang
- Review: Upgrade libtiff from 4.7.0 to 4.7.1
Root Cause
PDFium’s bundled libtiff validates JPEG data precision in JPEGPreDecode by comparing only the JPEG header’s data_precision against the TIFF tag td_bitspersample:
// third_party/pdfium/third_party/libtiff/tif_jpeg.c
if (sp->cinfo.d.data_precision != td->td_bitspersample)
{
TIFFErrorExtR(tif, module, "Improper JPEG data precision");
return (0);
}
This check passes when both values are 2, but it does not verify that the precision equals BITS_IN_JSAMPLE (8), which is the output sample width of libjpeg-turbo. The upstream libtiff fix 0f726d9 adds exactly this check, but the PDFium copy has not incorporated it.
Separately, Chromium’s bundled libjpeg-turbo 3.1.0 defines D_LOSSLESS_SUPPORTED in jmorecfg.h, enabling the lossless JPEG decoding path. This path accepts data_precision values from 2 through 16 in jdinput.c:
// third_party/libjpeg_turbo/src/jdinput.c
#ifdef D_LOSSLESS_SUPPORTED
if (cinfo->master->lossless) {
if (cinfo->data_precision < 2 || cinfo->data_precision > 16)
ERREXIT1(cinfo, JERR_BAD_PRECISION, cinfo->data_precision);
} else
The post-processing controller in jdpostct.c also accepts precision 2 through 8 when BITS_IN_JSAMPLE == 8:
// third_party/libjpeg_turbo/src/jdpostct.c
#if BITS_IN_JSAMPLE == 8
if (cinfo->data_precision > BITS_IN_JSAMPLE || cinfo->data_precision < 2)
#endif
ERREXIT1(cinfo, JERR_BAD_PRECISION, cinfo->data_precision);
A precision of 2 passes all of these checks. The lossless decoder then writes one JSAMPLE (8-bit unsigned char) per output sample through null_convert in jdcolor.c. Meanwhile, libtiff computes the strip buffer size via TIFFScanlineSize(tif), which packs samples at 2 bits each. For a 64-pixel wide, single-component image, the scanline buffer is ceil(64 * 2 / 8) = 16 bytes, but libjpeg-turbo writes 64 * 1 = 64 bytes per row. When JPEGDecode advances the buffer pointer by sp->bytesperline (16 bytes) after the first row and calls jpeg_read_scanlines again, the second row’s 64-byte write extends 48 bytes past the end of the 64-byte strip buffer.
Exploitability
The overflow size is directly controlled by the attacker through the TIFF image dimensions and the JPEG data precision value. For a width of W pixels and a precision of P bits, each scanline overflows by W - ceil(W * P / 8) bytes. Lowering the precision increases the ratio; at precision 2, the overflow is 75% of the image width per row, and additional rows multiply the total. The written bytes are the decoded lossless JPEG sample values, which the attacker encodes into the Huffman stream and therefore fully controls. Combined with the ability to choose arbitrary image dimensions to target specific heap bucket sizes, this gives an attacker a controlled, variable-length heap write primitive in the renderer process.
Reproduce
Tested on Chromium commit 46afa4acada327a9d297f780fc811554a9eecf99 on macOS arm64 and Ubuntu 22.04. The bug is platform-independent. No source modifications are required.
Check out the commit and configure an ASAN build:
is_asan = true
is_debug = false
dcheck_always_on = false
Then build:
autoninja -C out/asan chrome
Generate the malicious PDF using the attached Python script, or use the poc.pdf directly, then open it in Chrome with XFA support enabled:
python3 gen_poc.py
out/asan/Chromium.app/Contents/MacOS/Chromium --user-data-dir=./userdata --enable-features=PdfXfaSupport poc.pdf
The renderer process will abort with ERROR: AddressSanitizer: heap-buffer-overflow, WRITE of size 1, in null_convert (jdcolor.c). The full ASAN trace is in asan.txt.
ASAN output
=================================================================
==34815==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x6070001174a0 at pc 0x00035de48638 bp 0x00016b0ec230 sp 0x00016b0ec228
WRITE of size 1 at 0x6070001174a0 thread T0
==34815==WARNING: invalid path to external symbolizer!
==34815==WARNING: Failed to use and restart external symbolizer!
#0 0x00035de48634 in null_convert+0xfd8 (/Users/test/Desktop/src/chromium/src/out/asan/Chromium.app/Contents/Frameworks/Chromium Framework.framework/Versions/148.0.7759.0/Chromium Framework:arm64+0x13d78634)
#1 0x00035ddbd638 in sep_upsample+0x300 (/Users/test/Desktop/src/chromium/src/out/asan/Chromium.app/Contents/Frameworks/Chromium Framework.framework/Versions/148.0.7759.0/Chromium Framework:arm64+0x13ced638)
#2 0x00035de5aa7c in process_data_simple_main+0x138 (/Users/test/Desktop/src/chromium/src/out/asan/Chromium.app/Contents/Frameworks/Chromium Framework.framework/Versions/148.0.7759.0/Chromium Framework:arm64+0x13d8aa7c)
#3 0x00035ddb8d20 in chromium_jpeg_read_scanlines+0x324 (/Users/test/Desktop/src/chromium/src/out/asan/Chromium.app/Contents/Frameworks/Chromium Framework.framework/Versions/148.0.7759.0/Chromium Framework:arm64+0x13ce8d20)
#4 0x00035b3375f0 in TIFFjpeg_read_scanlines+0x34 (/Users/test/Desktop/src/chromium/src/out/asan/Chromium.app/Contents/Frameworks/Chromium Framework.framework/Versions/148.0.7759.0/Chromium Framework:arm64+0x112675f0)
#5 0x00035b332fac in JPEGDecode+0x1f8 (/Users/test/Desktop/src/chromium/src/out/asan/Chromium.app/Contents/Frameworks/Chromium Framework.framework/Versions/148.0.7759.0/Chromium Framework:arm64+0x11262fac)
......
The complete untruncated log is in the attached asan.txt.
References
- tif_jpeg.c JPEGPreDecode precision check
- jmorecfg.h D_LOSSLESS_SUPPORTED
- jdinput.c lossless precision acceptance
- jdpostct.c post-controller precision check
- tiff_decoder.cpp CTiffContext::Decode
- Upstream libtiff fix 0f726d9
Credit
Please use 86ac1f1587b71893ed2ad792cd7dde32 as the credit for this vulnerability. Thank you.
- https://gitlab.com/libtiff/libtiff/-/commit/0f726d9
- https://source.chromium.org/chromium/chromium/src/+/main:third_party/libjpeg_turbo/src/jdinput.c;l=62
- https://source.chromium.org/chromium/chromium/src/+/main:third_party/libjpeg_turbo/src/jdpostct.c;l=270
- https://source.chromium.org/chromium/chromium/src/+/main:third_party/libjpeg_turbo/src/jmorecfg.h;l=262
- https://source.chromium.org/chromium/chromium/src/+/main:third_party/pdfium/core/fxcodec/tiff/tiff_decoder.cpp;l=269
- https://source.chromium.org/chromium/chromium/src/+/main:third_party/pdfium/third_party/libtiff/tif_jpeg.c;l=1290