## Ticket: CVE-2026-8813 — ExifReader memory-amplification DoS via crafted ICC `mluc` tag

**CVE**: CVE-2026-8813 | **CWE-1284** (Improper Validation of Specified Quantity in Input), **CWE-789** (Memory Allocation with Excessive Size Value)
**Severity**: High — CVSS 3.1 base 7.5 (AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H)
**Package**: `exifreader` (npm) | **Repository**: https://github.com/mattiasw/ExifReader

### Impact

`exifreader` is a widely used JavaScript library for extracting metadata
(EXIF, IPTC, XMP, ICC, ...) from images. When it parses an image's embedded
**ICC color profile**, it interprets ICC tags including the `mluc`
(multi-localized Unicode) tag type.

The `mluc` tag layout declares a **record count** and a **record size**, both
read from attacker-controlled bytes in the profile. Through version `4.38.1`
the parser does **not** validate these fields against the actual length of the
profile buffer. A crafted `mluc` tag that sets:

- a **very large record count**, and
- a **record size of zero**

makes the parsing loop **reprocess the same record over and over** and append
an output entry on every iteration. Because the record size is zero the cursor
never advances and the loop runs `count` times, appending without bound. The
result is **unbounded memory growth (memory amplification)**: a tiny crafted
image inflates into hundreds of megabytes / gigabytes of allocations until the
Node.js process is **OOM-killed**.

This is a remote, unauthenticated **denial of service**: any application that
parses an attacker-supplied image with ExifReader (upload endpoints,
thumbnailers, metadata extractors) can be taken down by a single crafted file.

### Affected / fixed versions

Affected: `exifreader` `< 4.39.0`.
Fixed: **`4.39.0`**.

Reproduce on a vulnerable build (**`4.38.1`**) and verify the fix on
**`4.39.0`**.

### Where to look

The fix ships in commit
[`c9d88b67e127b2dcc7b46e328df468257fb2dc30`](https://github.com/mattiasw/ExifReader/commit/c9d88b67e127b2dcc7b46e328df468257fb2dc30).
Inspect the patch to confirm the root cause and the bounds check that was
added to the ICC `mluc` parsing path:

```bash
git clone https://github.com/mattiasw/ExifReader.git
cd ExifReader && git show c9d88b67e127b2dcc7b46e328df468257fb2dc30
```

The change adds validation of the `mluc` record count / record size against
the profile buffer bounds, so a zero-size / oversized-count record can no
longer drive an unbounded append loop.

### Reproduction approach

No service, database, or browser is needed — the bug is observable purely
in-process with Node.js by parsing a crafted image buffer and measuring memory.

1. In a scratch directory, install the **vulnerable** build:
   `npm install exifreader@4.38.1`
2. Build a minimal crafted image carrying an embedded ICC profile whose tag
   table contains an `mluc`-typed tag with a **large record count** and a
   **record size of zero** (consult `git show` of the fix commit to match the
   exact offsets the patched check guards).
3. Parse the crafted image with ExifReader under a bounded memory cap so the
   unbounded growth terminates deterministically rather than hanging, e.g.:
   `node --max-old-space-size=512 poc.js` and/or wrap the call in a timeout.
   Capture process memory with `/usr/bin/time -v` (Maximum resident set size)
   or by sampling `process.memoryUsage().rss`.
4. Repeat with the **fixed** build: `npm install exifreader@4.39.0`, parsing
   the *same* crafted image under the *same* memory cap.

### Expected result

| Build | Parse crafted ICC `mluc` image | Observable memory behavior |
|-------|--------------------------------|----------------------------|
| `exifreader@4.38.1` (vulnerable) | unbounded mluc append loop | RSS balloons to hundreds of MB / GB → **OOM-killed / non-zero exit** under the memory cap, or a large measurable RSS delta |
| `exifreader@4.39.0` (fixed) | mluc bounds validated, loop bounded | parse completes fast, **exit 0**, RSS stays small (tens of MB) |

- **Vulnerable indicator**: parsing the crafted image with `4.38.1` exhausts
  memory — the process is OOM-killed (non-zero exit / `SIGKILL`) or shows
  multi-hundred-MB RSS growth versus a benign-parse baseline.
- **Fixed indicator**: parsing the same image with `4.39.0` completes normally
  within the same memory cap — exit 0, small bounded RSS.

The distinguishing signal is **bounded vs unbounded memory growth**, not a
plain parse error. Bound the vulnerable run with `--max-old-space-size` and/or
a timeout so the indicator is a clean, deterministic OOM/abort rather than a
sandbox hang.

### Expected artifacts

- `reproduction_steps.sh` — installs both versions, builds the crafted ICC
  `mluc` image, and runs the PoC under a memory cap for each, printing the
  exit status and peak RSS.
- `validation_verdict.json` — `verdict: confirmed`, with the vulnerable
  (OOM / unbounded growth) and fixed (bounded, exit 0) indicators captured.
- Logs capturing the parse output and memory measurements for `4.38.1` and
  `4.39.0`.
