Repository navigation
Conversation
ImagingPcxDecode validated its input line against the packed-pixel formula (xsize * bits + 7) / 8, but the P;2L/P;4L unpackers consume planes * ((xsize + 7) / 8) bytes per line, with each bit plane padded to a byte (python-pillow#10186). A caller-supplied stride equal to the packed size passed the check while unpackP2L/unpackP4L read up to three bytes past the decode buffer. Validate the line against the same planes-aware, overflow-guarded expression now used by decode.c:_setimage and ImagingRawDecode, and key the band-interleave collapse on state->planes instead of the "bits == 2 || bits == 4" heuristic, so that packed L;2/L;4/P;2/P;4 rows are no longer de-interleaved when the stride carries padding. Tests extend python-pillow#10186's test_pXl_decoder_buffer_size matrix to the pcx decoder and pin the packed low-bit row layout with padded strides.
The interlaced bits < 8 path of ImagingZipDecode stages one bit-pixel at a time in a single byte and calls the shuffle with that one-byte buffer, while the P;2L/P;4L unpackers read planes padded bytes per call and would read past it. Reject the combination at decoder construction with "unrecognized raw mode", mirroring the putpalette rejection in python-pillow#10186. Non-interlaced planar raw modes keep working and are covered by regression tests.
|
@akx would you like to review this? |
|
Codecov shows the patch at 80%. The two uncovered lines are the new guards in PcxDecode.c that return IMAGING_CODEC_MEMORY — they only run when the image width is within a few pixels of the largest 32-bit value, and reaching them in a test would mean allocating a multi-gigabyte image. Is a test at that size welcome in the suite, or would you rather leave those two lines uncovered? Everything else in the diff is covered. (The three failing checks also fail on current main — the CodSpeed job (Python 3.15), run 40194988170, and the Docker jobs, run 40194988114 — so they don't look related to this change.) |
|
The MSYS2 failure is in the Install dependencies step (pacman) - both Build Pillow and Test Pillow were skipped, so the patch is not involved. Looks like runner/mirror flakiness. Could someone re-run the failed jobs when MSYS2 settles? Happy to push a retrigger commit instead if that is easier. |
Systematic hardening of the decoder-side row-size arithmetic, completing the cleanup that #10186 (392ee6f) started for the decoders whose line size is computed independently of
decode.c:_setimage. Production image-file paths are not affected:PcxImagePlugincomputes planar-correct strides itself ("Don't trust the passed in stride", CVE-2020-35653), and PNG never combines planar raw modes with the ZIP decoder — the surface in play is thefrombytes()/frombuffer()decoder API with caller-supplied geometry, the same API surface #10186 was written against (CVE-2026-112394 is cited here only as the maintainer's threat-model precedent for that surface, not as a severity reference).This is a cleanup of integer arithmetic that eliminates the duplicated packed-row-size design pattern:
ImagingPcxDecodestill validated its input line with the packed-pixel formula(xsize * bits + 7) / 8and a separatebits == 2 || bits == 4planar heuristic, while_setimageandImagingRawDecodewere converted in #10186 to a planes-aware, overflow-guarded expression. Every copy of that derivation is a potential gap; this patch makes each copy express the same formula.Changes (2 commits, with overflow guards and regression tests)
ImagingPcxDecodenow checks its input line againstplanes * ((xsize + 7) / 8)for planar raw modes (P;2L/P;4L) and the packed formula otherwise, both withINT_MAXoverflow guards (the same idiom asdecode.c:_setimageandRawDecode.c). The band-interleave collapse now keys onstate->planes— the single source of truth for "is this planar?" — instead of thebits == 2 || bits == 4heuristic. Truncated planar input now fails cleanly (ValueError: cannot decode image data) instead of reading up to 3 bytes past the decode buffer.bits < 8path stages one bit-pixel at a time in a single byte and calls the shuffle once per pixel, whileunpackP2L/unpackP4Lreadplanespadded bytes per call. The combination is now rejected at decoder construction (ValueError: unrecognized raw mode), mirroring theputpaletterejection in Use bit plane row size for P;2L and P;4L rawmodes #10186. Non-interlaced planar ZIP is unchanged and regression-tested.The tests complete #10186's
test_pXl_decoder_buffer_sizematrix (which coveredhexandpackbits) for the two decoders it didn't cover:pcxandzip.Behavior change
Behavior changes only for:
xsize * bitsmultiplication in the check itself);L;2/L;4/P;2/P;4rows decoded viafrombytes()with a padded stride — the oldbits == 2 || bits == 4collapse heuristic treated these packed rows as bit planes and de-interleaved them whenever the stride carried padding. The previous sizing was the buggy one here; the collapse now applies only to actual planar raw modes (state->planes).Tests/test_lib_pack.py::test_pcx_packed_low_bit_padded_rowpins the correct packed behavior for all four raw modes.Everything else decodes exactly as before. File-format decoding (2/4-plane PCX, PNG) is unchanged —
Tests/test_file_pcx.pyandTests/test_file_png.pypass.Verification