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

77 lines
3 KiB
Markdown

# 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):
```c
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.