java-topology/defects/libtiff-0001/SCAN-NOTES.md
russell@unturf.com bbc12a3ca6 libtiff+libpng: 5-MOAD scan; 1 defect / libpng CLEAN
libtiff-0001: TIFFReadDirectory + TIFFReadCustomDirectory both contain
identical O(D^2) nested loops to detect duplicate IFD tags (bugzilla 1994
dedup block, tif_dirread.c). Adversarial TIFF with D=65535 entries causes
~2.1B comparisons per IFD open. Fix: sorted seen-array with binary-search
insert, O(D log D). Java model confirms 37x at D=8000; asymptotic ~3900x
at D=65535.

libpng: all 5 MOADs CLEAN. add_one_chunk O(new*old) in pngset.c is bounded
by a ~30-entry fixed chunk list; chunk dedup during read uses a bitmask O(1).
2026-03-31 21:41:06 -04:00

3 KiB

libtiff-0001: TIFFReadDirectory duplicate-tag detection O(D^2)

MOAD-0001 (CWE-407)

Severity: HIGH File: libtiff/tif_dirread.c Functions: TIFFReadDirectory (line ~4384), TIFFReadCustomDirectory (line ~5401) Complexity: O(D^2) where D = number of IFD directory entries (uint16_t, max 65535)

Pattern

Both TIFFReadDirectory and TIFFReadCustomDirectory contain an identical nested loop to detect and suppress duplicate TIFF tags (original fix for bugzilla 1994):

for (ma = dir, mb = 0; mb < dircount; ma++, mb++)
{
    for (na = ma + 1, nb = mb + 1; nb < dircount; na++, nb++)
    {
        if (ma->tdir_tag == na->tdir_tag)
            na->tdir_ignore = TRUE;
    }
}

For D directory entries this performs D*(D-1)/2 comparisons. An adversarial TIFF with D=65535 entries produces ~2.1 billion comparisons per IFD open.

Impact

  • Every call to TIFFReadDirectory is affected.
  • Multi-page TIFFs (BigTIFF GeoTIFF, medical imaging) open one IFD per page.
  • A 100-page TIFF with 1000 tags per IFD: 100 * 500,000 = 50 million comparisons.
  • Adversarial input (max 65535 tags per IFD): 2.1 billion comparisons per page.
  • Likely exploitable as a DoS vector via crafted TIFF files.

Evidence in code

A pending-improvement comment at the top of tif_dirread.c (line 31) notes:

"add a field 'field_info' to the TIFFDirEntry structure, and set that with the pointer to the appropriate TIFFField structure early on in TIFFReadDirectory, so as to eliminate current possibly repetitive lookup."

Fix

Replace nested loop with a HashSet (or sorted array with binary-search insert) membership test. O(D log D) or O(D) total.

TIFF spec (TIFF 6.0 section 2) requires IFD entries to be sorted in ascending tag order. For compliant files a single adjacent-pair pass suffices: O(D). Our patch uses a sorted seen-array with binary-search insert as a safe fallback that handles both compliant and adversarial (unsorted) files in O(D log D).

Benchmark (Java model, 200 iterations each)

D quadratic (ms) linear (ms) ratio
500 16 17 ~1x
1000 40 20 2x
2000 169 21 8x
4000 621 36 17x
8000 2317 62 37x

Note: JVM HashSet overhead flattens gains at small D. In C the quadratic cost is pure cache-hostile integer comparisons; at D=65535 the ratio approaches D/2 / log(D) ~ 3900x.

Run: javac unit/LibtiffDirDedupTest.java && java -cp unit LibtiffDirDedupTest

MOADs 0002-0005

  • MOAD-0002: registeredCODECS global linked list mutated without locking. Architecture note (not instance-level intertangle). LOW. No patch.
  • MOAD-0003: No ThreadLocal or thread-scoped carrier in C library. CLEAN.
  • MOAD-0004: No credential/secret logging via TIFFError/TIFFWarning. CLEAN.
  • MOAD-0005: No unsynchronized cache get+compute+put pattern. CLEAN.