A thin source requested with fit=cover makes libvips allocate a huge uncropped image #282

Closed
opened 2026-10-15 23:23:59 +02:00 by clawbot · 1 comment
Collaborator

Where: internal/imageprocessor/imageprocessor.go, resize, the FitCover case (also the default fit): img.Thumbnail(width, height, vips.InterestingCentre), on next at f743930. Found by the review of #278.

What is wrong: for fit=cover, libvips scales the source to cover the requested size and copies the whole uncropped result into memory before it crops the centre. For a thin source that result is far larger than the output. An 8192x64 source requested at 8192x8192 makes libvips ask for one block of about 24 GiB; 8192x1024 at 8192x8192 peaks at about 1.6 GB and takes 21 seconds. An 8192x1 source at 8192x8192 fails inside libvips ("output coordinates out of range") and answers 500. The same 8192x64 source with fit=fill takes 2.6 seconds and 25 MB. This also breaks the per-image memory figure README.md "Running under upaas" gives.

How it fails: anyone who can request an image from an allowlisted host, or holds a signed or encrypted URL that allows a large size, asks for a thin image at 8192x8192. One request can make pixa run out of memory and be killed.

Severity: high (a denial of service: AGENTS.md puts these at the top of TODO.md).

Plan: for fit=cover, cut the centre of the source to the requested size's proportions first, then resize that to the requested size, so no image larger than the source or the output is ever made. The output is the same centre crop as now.

Definition of done:

  1. An 8192x64 and an 8192x1 source requested at 8192x8192 with fit=cover are served, each with a peak memory no larger than the output's and the source's decoded size together, measured and stated in the PR; no fit=cover request answers 500 for its shape.
  2. fit=cover output keeps its size and centre crop for ordinary sources; no existing test changes.
  3. Test first; make check green.

Package: internal/imageprocessor.

Model: opus-5-5

Where: `internal/imageprocessor/imageprocessor.go`, `resize`, the `FitCover` case (also the default fit): `img.Thumbnail(width, height, vips.InterestingCentre)`, on `next` at `f743930`. Found by the review of https://git.eeqj.de/sneak/pixa/pulls/278. What is wrong: for `fit=cover`, libvips scales the source to cover the requested size and copies the whole uncropped result into memory before it crops the centre. For a thin source that result is far larger than the output. An 8192x64 source requested at 8192x8192 makes libvips ask for one block of about 24 GiB; 8192x1024 at 8192x8192 peaks at about 1.6 GB and takes 21 seconds. An 8192x1 source at 8192x8192 fails inside libvips ("output coordinates out of range") and answers 500. The same 8192x64 source with `fit=fill` takes 2.6 seconds and 25 MB. This also breaks the per-image memory figure `README.md` "Running under upaas" gives. How it fails: anyone who can request an image from an allowlisted host, or holds a signed or encrypted URL that allows a large size, asks for a thin image at 8192x8192. One request can make pixa run out of memory and be killed. Severity: high (a denial of service: `AGENTS.md` puts these at the top of `TODO.md`). Plan: for `fit=cover`, cut the centre of the source to the requested size's proportions first, then resize that to the requested size, so no image larger than the source or the output is ever made. The output is the same centre crop as now. Definition of done: 1. An 8192x64 and an 8192x1 source requested at 8192x8192 with `fit=cover` are served, each with a peak memory no larger than the output's and the source's decoded size together, measured and stated in the PR; no `fit=cover` request answers 500 for its shape. 2. `fit=cover` output keeps its size and centre crop for ordinary sources; no existing test changes. 3. Test first; `make check` green. Package: `internal/imageprocessor`. Model: opus-5-5
clawbot self-assigned this 2026-10-15 23:24:11 +02:00
Author
Collaborator

#284 cuts the centre of the source to the requested size's proportions before resizing for fit=cover; its body gives the measured peak memory. A computed side that rounds down to 0, found on the way, is #283.

Model: opus-5-5

https://git.eeqj.de/sneak/pixa/pulls/284 cuts the centre of the source to the requested size's proportions before resizing for `fit=cover`; its body gives the measured peak memory. A computed side that rounds down to 0, found on the way, is https://git.eeqj.de/sneak/pixa/issues/283. Model: opus-5-5
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: sneak/pixa#282