LC-206-IMAGE-LIMITS: spike image decompression-bomb exposure, apply image::Limits if confirmed #284

Closed
opened 2026-05-31 04:38:00 +02:00 by longjacksonle · 0 comments

Problem (investigate-first)

No image decode path configures image::Limits (no set_limits / max_alloc anywhere in server/src). The byte caps that protect the decoders bound the input file size, not the decoded pixel-buffer allocation, so a small highly-compressed image (PNG/GIF decompression bomb) can expand to a very large RGBA buffer at decode time.

This is a UNIFORM gap, not asymmetric: all three untrusted-image entry points already route through the LC-206 safe helper (process_image_safely) and share the same unguarded decoder, so it is one finding across all three, not a sibling-missed-the-guard case.

Where

  • server/src/uploads/pipeline.rs: process_still (:137), process_gif (:160), preview_from_path (:206) call ImageReader::decode() / GifDecoder::into_frames() with no Limits.
  • Entry points (each byte-capped + semaphore-bounded + spawn_blocking panic boundary, but none pixel-capped):
    • uploads: server/src/routes/uploads.rs:274 (10 MiB byte cap)
    • avatar proxy: server/src/bridge_avatar.rs:150 (1 MiB stream cap)
    • email-ingress attachments: server/src/email_ingress/attachments.rs:155 (byte cap)

Failure mode / cost

A crafted image within the byte cap could drive a large memory spike at decode. Existing mitigations bound the blast radius: spawn_blocking keeps it off the runtime, and a concurrency semaphore caps parallel decodes. Whether a single decode (or semaphore-many concurrent ones) can exhaust memory past those bounds is NOT confirmed by reading; it needs a spike.

Proposed scope (investigate-first)

  1. Spike: feed the image decoders a known decompression-bomb corpus at the current byte caps and measure peak allocation per decode and at full semaphore concurrency.
  2. If it confirms a real spike: set image::Limits (a max_alloc bound) on every decode site in pipeline.rs, behind the same grep-banned safe helper so the cap cannot be bypassed.
  3. Test: an over-Limits image is rejected cleanly (not OOM, not panic).
  4. Fold the "image decoders need Limits for hostile input" gotcha into CLAUDE.md (the M2 item from the sweep).

Acceptance

Either a confirmed spike + Limits applied at every decode site with a test, OR a documented finding that the byte cap + semaphore + spawn_blocking already bound allocation acceptably (close as not-a-bug with the measurement recorded).

Anchored to LC-206 (image-decoder safe helper). Severity: correctness/security, but speculative until the spike confirms exploitability.

## Problem (investigate-first) No image decode path configures `image::Limits` (no `set_limits` / `max_alloc` anywhere in `server/src`). The byte caps that protect the decoders bound the *input file* size, not the decoded pixel-buffer allocation, so a small highly-compressed image (PNG/GIF decompression bomb) can expand to a very large RGBA buffer at decode time. This is a UNIFORM gap, not asymmetric: all three untrusted-image entry points already route through the LC-206 safe helper (`process_image_safely`) and share the same unguarded decoder, so it is one finding across all three, not a sibling-missed-the-guard case. ## Where - `server/src/uploads/pipeline.rs`: `process_still` (`:137`), `process_gif` (`:160`), `preview_from_path` (`:206`) call `ImageReader::decode()` / `GifDecoder::into_frames()` with no `Limits`. - Entry points (each byte-capped + semaphore-bounded + `spawn_blocking` panic boundary, but none pixel-capped): - uploads: `server/src/routes/uploads.rs:274` (10 MiB byte cap) - avatar proxy: `server/src/bridge_avatar.rs:150` (1 MiB stream cap) - email-ingress attachments: `server/src/email_ingress/attachments.rs:155` (byte cap) ## Failure mode / cost A crafted image within the byte cap could drive a large memory spike at decode. Existing mitigations bound the blast radius: `spawn_blocking` keeps it off the runtime, and a concurrency semaphore caps parallel decodes. Whether a single decode (or `semaphore`-many concurrent ones) can exhaust memory past those bounds is NOT confirmed by reading; it needs a spike. ## Proposed scope (investigate-first) 1. Spike: feed the `image` decoders a known decompression-bomb corpus at the current byte caps and measure peak allocation per decode and at full semaphore concurrency. 2. If it confirms a real spike: set `image::Limits` (a `max_alloc` bound) on every decode site in `pipeline.rs`, behind the same grep-banned safe helper so the cap cannot be bypassed. 3. Test: an over-`Limits` image is rejected cleanly (not OOM, not panic). 4. Fold the "image decoders need `Limits` for hostile input" gotcha into CLAUDE.md (the M2 item from the sweep). ## Acceptance Either a confirmed spike + `Limits` applied at every decode site with a test, OR a documented finding that the byte cap + semaphore + spawn_blocking already bound allocation acceptably (close as not-a-bug with the measurement recorded). Anchored to LC-206 (image-decoder safe helper). Severity: correctness/security, but speculative until the spike confirms exploitability.
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
psa-systems/lets-chat#284
No description provided.