Avoid overflow by not adding extents together (#9520)
This commit is contained in:
commit
58f9a1d166
BIN
Tests/images/psd-oob-write-overflow.psd
Normal file
BIN
Tests/images/psd-oob-write-overflow.psd
Normal file
Binary file not shown.
@ -1,12 +1,18 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
import warnings
|
||||
|
||||
import pytest
|
||||
|
||||
from PIL import Image, PsdImagePlugin
|
||||
|
||||
from .helper import assert_image_equal_tofile, assert_image_similar, hopper, is_pypy
|
||||
from .helper import (
|
||||
assert_image_equal_tofile,
|
||||
assert_image_similar,
|
||||
hopper,
|
||||
is_pypy,
|
||||
)
|
||||
|
||||
test_file = "Tests/images/hopper.psd"
|
||||
|
||||
@ -204,3 +210,17 @@ def test_bounds_crash(test_file: str) -> None:
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
im.load()
|
||||
|
||||
|
||||
def test_bounds_crash_overflow() -> None:
|
||||
with Image.open("Tests/images/psd-oob-write-overflow.psd") as im:
|
||||
assert isinstance(im, PsdImagePlugin.PsdImageFile)
|
||||
im.load()
|
||||
if sys.maxsize <= 2**32:
|
||||
with pytest.raises(OverflowError):
|
||||
im.seek(im.n_frames)
|
||||
else:
|
||||
im.seek(im.n_frames)
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
im.load()
|
||||
|
||||
@ -316,6 +316,26 @@ class TestPyDecoder(CodecsTest):
|
||||
with pytest.raises(ValueError):
|
||||
MockPyDecoder.last.set_as_raw(b"\x00")
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"extents",
|
||||
(
|
||||
(-10, yoff, xoff + xsize, yoff + ysize),
|
||||
(xoff, -10, xoff + xsize, yoff + ysize),
|
||||
(xoff, yoff, -10, yoff + ysize),
|
||||
(xoff, yoff, xoff + xsize, -10),
|
||||
(xoff, yoff, xoff + xsize + 100, yoff + ysize),
|
||||
(xoff, yoff, xoff + xsize, yoff + ysize + 100),
|
||||
),
|
||||
)
|
||||
def test_extents(self, extents: tuple[int, int, int, int]) -> None:
|
||||
buf = BytesIO(b"\x00" * 255)
|
||||
|
||||
im = MockImageFile(buf)
|
||||
im.tile = [ImageFile._Tile("MOCK", extents, 32, None)]
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
im.load()
|
||||
|
||||
def test_extents_none(self) -> None:
|
||||
buf = BytesIO(b"\x00" * 255)
|
||||
|
||||
@ -329,40 +349,6 @@ class TestPyDecoder(CodecsTest):
|
||||
assert MockPyDecoder.last.state.xsize == 200
|
||||
assert MockPyDecoder.last.state.ysize == 200
|
||||
|
||||
def test_negsize(self) -> None:
|
||||
buf = BytesIO(b"\x00" * 255)
|
||||
|
||||
im = MockImageFile(buf)
|
||||
im.tile = [ImageFile._Tile("MOCK", (xoff, yoff, -10, yoff + ysize), 32, None)]
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
im.load()
|
||||
|
||||
im.tile = [ImageFile._Tile("MOCK", (xoff, yoff, xoff + xsize, -10), 32, None)]
|
||||
with pytest.raises(ValueError):
|
||||
im.load()
|
||||
|
||||
def test_oversize(self) -> None:
|
||||
buf = BytesIO(b"\x00" * 255)
|
||||
|
||||
im = MockImageFile(buf)
|
||||
im.tile = [
|
||||
ImageFile._Tile(
|
||||
"MOCK", (xoff, yoff, xoff + xsize + 100, yoff + ysize), 32, None
|
||||
)
|
||||
]
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
im.load()
|
||||
|
||||
im.tile = [
|
||||
ImageFile._Tile(
|
||||
"MOCK", (xoff, yoff, xoff + xsize, yoff + ysize + 100), 32, None
|
||||
)
|
||||
]
|
||||
with pytest.raises(ValueError):
|
||||
im.load()
|
||||
|
||||
def test_decode(self) -> None:
|
||||
decoder = ImageFile.PyDecoder("")
|
||||
with pytest.raises(NotImplementedError):
|
||||
@ -392,6 +378,33 @@ class TestPyEncoder(CodecsTest):
|
||||
assert MockPyEncoder.last.state.xsize == xsize
|
||||
assert MockPyEncoder.last.state.ysize == ysize
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"extents",
|
||||
(
|
||||
(-10, yoff, xoff + xsize, yoff + ysize),
|
||||
(xoff, -10, xoff + xsize, yoff + ysize),
|
||||
(xoff, yoff, -10, yoff + ysize),
|
||||
(xoff, yoff, xoff + xsize, -10),
|
||||
(xoff, yoff, xoff + xsize + 100, yoff + ysize),
|
||||
(xoff, yoff, xoff + xsize, yoff + ysize + 100),
|
||||
),
|
||||
)
|
||||
def test_extents(self, extents: tuple[int, int, int, int]) -> None:
|
||||
buf = BytesIO(b"\x00" * 255)
|
||||
|
||||
im = MockImageFile(buf)
|
||||
|
||||
fp = BytesIO()
|
||||
MockPyEncoder.last = None
|
||||
with pytest.raises(ValueError):
|
||||
ImageFile._save(im, fp, [ImageFile._Tile("MOCK", extents, 0, "RGB")])
|
||||
last: MockPyEncoder | None = MockPyEncoder.last
|
||||
assert last
|
||||
assert last.cleanup_called
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
ImageFile._save(im, fp, [ImageFile._Tile("MOCK", extents, 0, "RGB")])
|
||||
|
||||
def test_extents_none(self) -> None:
|
||||
buf = BytesIO(b"\x00" * 255)
|
||||
|
||||
@ -407,58 +420,6 @@ class TestPyEncoder(CodecsTest):
|
||||
assert MockPyEncoder.last.state.xsize == 200
|
||||
assert MockPyEncoder.last.state.ysize == 200
|
||||
|
||||
def test_negsize(self) -> None:
|
||||
buf = BytesIO(b"\x00" * 255)
|
||||
|
||||
im = MockImageFile(buf)
|
||||
|
||||
fp = BytesIO()
|
||||
MockPyEncoder.last = None
|
||||
with pytest.raises(ValueError):
|
||||
ImageFile._save(
|
||||
im,
|
||||
fp,
|
||||
[ImageFile._Tile("MOCK", (xoff, yoff, -10, yoff + ysize), 0, "RGB")],
|
||||
)
|
||||
last: MockPyEncoder | None = MockPyEncoder.last
|
||||
assert last
|
||||
assert last.cleanup_called
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
ImageFile._save(
|
||||
im,
|
||||
fp,
|
||||
[ImageFile._Tile("MOCK", (xoff, yoff, xoff + xsize, -10), 0, "RGB")],
|
||||
)
|
||||
|
||||
def test_oversize(self) -> None:
|
||||
buf = BytesIO(b"\x00" * 255)
|
||||
|
||||
im = MockImageFile(buf)
|
||||
|
||||
fp = BytesIO()
|
||||
with pytest.raises(ValueError):
|
||||
ImageFile._save(
|
||||
im,
|
||||
fp,
|
||||
[
|
||||
ImageFile._Tile(
|
||||
"MOCK", (xoff, yoff, xoff + xsize + 100, yoff + ysize), 0, "RGB"
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
ImageFile._save(
|
||||
im,
|
||||
fp,
|
||||
[
|
||||
ImageFile._Tile(
|
||||
"MOCK", (xoff, yoff, xoff + xsize, yoff + ysize + 100), 0, "RGB"
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
def test_encode(self) -> None:
|
||||
encoder = ImageFile.PyEncoder("")
|
||||
with pytest.raises(NotImplementedError):
|
||||
|
||||
@ -806,6 +806,10 @@ class PyCodec:
|
||||
if extents:
|
||||
x0, y0, x1, y1 = extents
|
||||
|
||||
if x0 < 0 or y0 < 0 or x1 > self.im.size[0] or y1 > self.im.size[1]:
|
||||
msg = "Tile cannot extend outside image"
|
||||
raise ValueError(msg)
|
||||
|
||||
self.state.xoff = x0
|
||||
self.state.yoff = y0
|
||||
self.state.xsize = x1 - x0
|
||||
@ -817,13 +821,6 @@ class PyCodec:
|
||||
msg = "Size must be positive"
|
||||
raise ValueError(msg)
|
||||
|
||||
if (
|
||||
self.state.xsize + self.state.xoff > self.im.size[0]
|
||||
or self.state.ysize + self.state.yoff > self.im.size[1]
|
||||
):
|
||||
msg = "Tile cannot extend outside image"
|
||||
raise ValueError(msg)
|
||||
|
||||
|
||||
class PyDecoder(PyCodec):
|
||||
"""
|
||||
|
||||
13
src/decode.c
13
src/decode.c
@ -198,6 +198,12 @@ _setimage(ImagingDecoderObject *decoder, PyObject *args) {
|
||||
}
|
||||
}
|
||||
|
||||
if (x0 < 0 || y0 < 0 || x1 <= x0 || y1 <= y0 || x1 > (int)im->xsize ||
|
||||
y1 > (int)im->ysize) {
|
||||
PyErr_SetString(PyExc_ValueError, "tile cannot extend outside image");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
decoder->im = im;
|
||||
|
||||
state = &decoder->state;
|
||||
@ -208,13 +214,6 @@ _setimage(ImagingDecoderObject *decoder, PyObject *args) {
|
||||
state->xsize = x1 - x0;
|
||||
state->ysize = y1 - y0;
|
||||
|
||||
if (state->xoff < 0 || state->xsize <= 0 ||
|
||||
state->xsize + state->xoff > (int)im->xsize || state->yoff < 0 ||
|
||||
state->ysize <= 0 || state->ysize + state->yoff > (int)im->ysize) {
|
||||
PyErr_SetString(PyExc_ValueError, "tile cannot extend outside image");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Allocate memory buffer (if bits field is set) */
|
||||
if (state->bits > 0) {
|
||||
if (!state->bytes) {
|
||||
|
||||
12
src/encode.c
12
src/encode.c
@ -269,6 +269,11 @@ _setimage(ImagingEncoderObject *encoder, PyObject *args) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (x0 < 0 || y0 < 0 || x1 <= x0 || y1 <= y0 || x1 > im->xsize || y1 > im->ysize) {
|
||||
PyErr_SetString(PyExc_SystemError, "tile cannot extend outside image");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
encoder->im = im;
|
||||
|
||||
state = &encoder->state;
|
||||
@ -278,13 +283,6 @@ _setimage(ImagingEncoderObject *encoder, PyObject *args) {
|
||||
state->xsize = x1 - x0;
|
||||
state->ysize = y1 - y0;
|
||||
|
||||
if (state->xoff < 0 || state->xsize <= 0 ||
|
||||
state->xsize + state->xoff > im->xsize || state->yoff < 0 ||
|
||||
state->ysize <= 0 || state->ysize + state->yoff > im->ysize) {
|
||||
PyErr_SetString(PyExc_SystemError, "tile cannot extend outside image");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Allocate memory buffer (if bits field is set) */
|
||||
if (state->bits > 0) {
|
||||
if (state->xsize > ((INT_MAX / state->bits) - 7)) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user