Where: internal/imageprocessor/imageprocessor.go, targetDimensions, on next. Found while working on #282; the review of #284 measured the rest.
What is wrong: a size with one side 0 has that side computed from the source's proportions by integer division, and nothing keeps it at 1 or more. When it rounds down to 0, libvips is asked for a square:
For an 8192x1 source, 100x0 comes out 100x100 where its proportions call for 100x1.
An 8192x1 source at 1300x0 with the default fit makes libvips copy the whole scaled image into memory, one block of about 39 GB: a denial of service from one request on an allowlisted host.
Since #284, when the source's short side is even (an 8192x2 source at 100x0, 8192x4 at 100x0, 8192x10 at 500x0) the request answers 500.
Severity: high (a denial of service: AGENTS.md puts these at the top of TODO.md).
Definition of done:
A computed side that rounds down to 0 is 1, in every fit mode: an 8192x1 source at 100x0 comes out at 100x1, at 1300x0 at 1300x1 with a peak memory of a few hundred MiB at most, and an 8192x2 source at 100x0 at 100x1, never 500.
Test first; no test can allocate more than a few hundred MiB if the fix is reverted; make check green.
Package: internal/imageprocessor.
Model: opus-5-5
Where: `internal/imageprocessor/imageprocessor.go`, `targetDimensions`, on `next`. Found while working on https://git.eeqj.de/sneak/pixa/issues/282; the review of https://git.eeqj.de/sneak/pixa/pulls/284 measured the rest.
What is wrong: a size with one side `0` has that side computed from the source's proportions by integer division, and nothing keeps it at 1 or more. When it rounds down to 0, libvips is asked for a square:
- For an 8192x1 source, `100x0` comes out 100x100 where its proportions call for 100x1.
- An 8192x1 source at `1300x0` with the default fit makes libvips copy the whole scaled image into memory, one block of about 39 GB: a denial of service from one request on an allowlisted host.
- Since https://git.eeqj.de/sneak/pixa/pulls/284, when the source's short side is even (an 8192x2 source at `100x0`, 8192x4 at `100x0`, 8192x10 at `500x0`) the request answers 500.
Severity: high (a denial of service: `AGENTS.md` puts these at the top of `TODO.md`).
Definition of done:
1. A computed side that rounds down to 0 is 1, in every fit mode: an 8192x1 source at `100x0` comes out at 100x1, at `1300x0` at 1300x1 with a peak memory of a few hundred MiB at most, and an 8192x2 source at `100x0` at 100x1, never 500.
2. Test first; no test can allocate more than a few hundred MiB if the fix is reverted; `make check` green.
Package: `internal/imageprocessor`.
Model: opus-5-5
clawbot
self-assigned this 2026-10-15 23:48:48 +02:00
clawbot
changed title from A size with one side 0 whose computed side rounds down to 0 comes out square to A size with one side 0 whose computed side rounds down to 0 comes out square, can ask for tens of GB, or answers 5002026-10-16 02:23:31 +02:00
Plan: in targetDimensions, the side worked out from the source's proportions for a size with one side 0 is at least 1, whatever the fit. contain and inside then work out both sides again from the smaller of the two scales, so for a 1x8192 source at 0x100 the width rounds down to 0 there a second time and libvips answers with an error; there too each side is kept at 1 or more.
A new test in computed_side_internal_test.go, committed before the fix, runs an 8192x1 source at 100x0 and 1300x0 and an 8192x2 source at 100x0, and the same turned on their side, in every fit mode. It checks the size targetDimensions gives before it calls Process, so without the fix it fails at once instead of having libvips ask for about 39 GB.
With outside the image is scaled to cover the size, as README.md says, so an 8192x1 source at 100x0 (now 100x1) comes out at 8192x1, where next makes 100x1.
Model: opus-5-5
Plan: in `targetDimensions`, the side worked out from the source's proportions for a size with one side `0` is at least 1, whatever the fit. `contain` and `inside` then work out both sides again from the smaller of the two scales, so for a 1x8192 source at `0x100` the width rounds down to 0 there a second time and libvips answers with an error; there too each side is kept at 1 or more.
A new test in `computed_side_internal_test.go`, committed before the fix, runs an 8192x1 source at `100x0` and `1300x0` and an 8192x2 source at `100x0`, and the same turned on their side, in every fit mode. It checks the size `targetDimensions` gives before it calls `Process`, so without the fix it fails at once instead of having libvips ask for about 39 GB.
With `outside` the image is scaled to cover the size, as `README.md` says, so an 8192x1 source at `100x0` (now 100x1) comes out at 8192x1, where `next` makes 100x1.
Model: opus-5-5
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Where:
internal/imageprocessor/imageprocessor.go,targetDimensions, onnext. Found while working on #282; the review of #284 measured the rest.What is wrong: a size with one side
0has that side computed from the source's proportions by integer division, and nothing keeps it at 1 or more. When it rounds down to 0, libvips is asked for a square:100x0comes out 100x100 where its proportions call for 100x1.1300x0with the default fit makes libvips copy the whole scaled image into memory, one block of about 39 GB: a denial of service from one request on an allowlisted host.100x0, 8192x4 at100x0, 8192x10 at500x0) the request answers 500.Severity: high (a denial of service:
AGENTS.mdputs these at the top ofTODO.md).Definition of done:
100x0comes out at 100x1, at1300x0at 1300x1 with a peak memory of a few hundred MiB at most, and an 8192x2 source at100x0at 100x1, never 500.make checkgreen.Package:
internal/imageprocessor.Model: opus-5-5
A size with one side 0 whose computed side rounds down to 0 comes out squareto A size with one side 0 whose computed side rounds down to 0 comes out square, can ask for tens of GB, or answers 500Plan: in
targetDimensions, the side worked out from the source's proportions for a size with one side0is at least 1, whatever the fit.containandinsidethen work out both sides again from the smaller of the two scales, so for a 1x8192 source at0x100the width rounds down to 0 there a second time and libvips answers with an error; there too each side is kept at 1 or more.A new test in
computed_side_internal_test.go, committed before the fix, runs an 8192x1 source at100x0and1300x0and an 8192x2 source at100x0, and the same turned on their side, in every fit mode. It checks the sizetargetDimensionsgives before it callsProcess, so without the fix it fails at once instead of having libvips ask for about 39 GB.With
outsidethe image is scaled to cover the size, asREADME.mdsays, so an 8192x1 source at100x0(now 100x1) comes out at 8192x1, wherenextmakes 100x1.Model: opus-5-5
Built as planned above in #291; the peak memory for the 8192x1 source at
1300x0is in its body.Model: opus-5-5