From 7ff098b109612856768001b8503f613973b91878 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Sun, 1 Oct 2017 00:41:48 -0700 Subject: [PATCH 1/6] FIX: segfault on truncated png - be more paranoid in _read_png_data about failed reads and check for exceptions after both calls to it - in read_png_data kick the png error handling - only override the exception in the body of `setjmp` handler if there is not already one set closes #9256 --- src/_png.cpp | 35 +++++++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/src/_png.cpp b/src/_png.cpp index 06e4b87543f2..218201aa380a 100644 --- a/src/_png.cpp +++ b/src/_png.cpp @@ -408,13 +408,21 @@ static void _read_png_data(PyObject *py_file_obj, png_bytep data, png_size_t len Py_ssize_t bufflen; if (read_method) { result = PyObject_CallFunction(read_method, (char *)"i", length); - if (PyBytes_AsStringAndSize(result, &buffer, &bufflen) == 0) { - if (bufflen == (Py_ssize_t)length) { - memcpy(data, buffer, length); - } else { - PyErr_SetString(PyExc_IOError, "read past end of file"); - } - } + if (result) { + if (PyBytes_AsStringAndSize(result, &buffer, &bufflen) == 0) { + if (bufflen == (Py_ssize_t)length) { + memcpy(data, buffer, length); + } else { + PyErr_SetString(PyExc_IOError, "read past end of file"); + } + } else { + PyErr_SetString(PyExc_IOError, "Failed to copy buffer"); + } + } else { + PyErr_SetString(PyExc_IOError, "Failed to read file"); + } + + } Py_XDECREF(read_method); Py_XDECREF(result); @@ -424,6 +432,11 @@ static void read_png_data(png_structp png_ptr, png_bytep data, png_size_t length { PyObject *py_file_obj = (PyObject *)png_get_io_ptr(png_ptr); _read_png_data(py_file_obj, data, length); + if (PyErr_Occurred()) + { + png_error(png_ptr, "failed to read file"); + } + } static PyObject *_read_png(PyObject *filein, bool float_result) @@ -481,6 +494,9 @@ static PyObject *_read_png(PyObject *filein, bool float_result) } Py_XDECREF(read_method); _read_png_data(py_file, header, 8); + if (PyErr_Occurred()){ + goto exit; + } } if (png_sig_cmp(header, 0, 8)) { @@ -503,7 +519,10 @@ static PyObject *_read_png(PyObject *filein, bool float_result) } if (setjmp(png_jmpbuf(png_ptr))) { - PyErr_SetString(PyExc_RuntimeError, "Error setting jump"); + if (!PyErr_Occurred()) + { + PyErr_SetString(PyExc_RuntimeError, "Error setting jump"); + } goto exit; } From 89bea54c79e9c444c184fc063d55b57d5e1e10e2 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Sun, 1 Oct 2017 09:19:58 -0700 Subject: [PATCH 2/6] STY: tab -> space conversion in c code --- src/_png.cpp | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/_png.cpp b/src/_png.cpp index 218201aa380a..23a195381079 100644 --- a/src/_png.cpp +++ b/src/_png.cpp @@ -194,18 +194,18 @@ static PyObject *Py_write_png(PyObject *self, PyObject *args, PyObject *kwds) switch (channels) { case 1: - png_color_type = PNG_COLOR_TYPE_GRAY; - break; + png_color_type = PNG_COLOR_TYPE_GRAY; + break; case 3: - png_color_type = PNG_COLOR_TYPE_RGB; - break; + png_color_type = PNG_COLOR_TYPE_RGB; + break; case 4: - png_color_type = PNG_COLOR_TYPE_RGB_ALPHA; - break; + png_color_type = PNG_COLOR_TYPE_RGB_ALPHA; + break; default: PyErr_SetString(PyExc_ValueError, - "Buffer must be an NxMxD array with D in 1, 3, 4 " - "(grayscale, RGB, RGBA)"); + "Buffer must be an NxMxD array with D in 1, 3, 4 " + "(grayscale, RGB, RGBA)"); goto exit; } @@ -349,20 +349,20 @@ static PyObject *Py_write_png(PyObject *self, PyObject *args, PyObject *kwds) sig_bit.alpha = 0; switch (png_color_type) { case PNG_COLOR_TYPE_GRAY: - sig_bit.gray = 8; - sig_bit.red = 0; - sig_bit.green = 0; - sig_bit.blue = 0; - break; + sig_bit.gray = 8; + sig_bit.red = 0; + sig_bit.green = 0; + sig_bit.blue = 0; + break; case PNG_COLOR_TYPE_RGB_ALPHA: - sig_bit.alpha = 8; - // fall through + sig_bit.alpha = 8; + // fall through case PNG_COLOR_TYPE_RGB: - sig_bit.gray = 0; - sig_bit.red = 8; - sig_bit.green = 8; - sig_bit.blue = 8; - break; + sig_bit.gray = 0; + sig_bit.red = 8; + sig_bit.green = 8; + sig_bit.blue = 8; + break; default: PyErr_SetString(PyExc_RuntimeError, "internal error, bad png_color_type"); goto exit; From 07633f3752924a1e330b2946f74ea967821589fb Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Sun, 1 Oct 2017 09:22:11 -0700 Subject: [PATCH 3/6] STY: fix tab -> space and brace position --- src/_png.cpp | 44 +++++++++++++++++++++----------------------- 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/src/_png.cpp b/src/_png.cpp index 23a195381079..3e0c76c55ac3 100644 --- a/src/_png.cpp +++ b/src/_png.cpp @@ -408,19 +408,19 @@ static void _read_png_data(PyObject *py_file_obj, png_bytep data, png_size_t len Py_ssize_t bufflen; if (read_method) { result = PyObject_CallFunction(read_method, (char *)"i", length); - if (result) { - if (PyBytes_AsStringAndSize(result, &buffer, &bufflen) == 0) { - if (bufflen == (Py_ssize_t)length) { - memcpy(data, buffer, length); - } else { - PyErr_SetString(PyExc_IOError, "read past end of file"); - } - } else { - PyErr_SetString(PyExc_IOError, "Failed to copy buffer"); - } - } else { - PyErr_SetString(PyExc_IOError, "Failed to read file"); - } + if (result) { + if (PyBytes_AsStringAndSize(result, &buffer, &bufflen) == 0) { + if (bufflen == (Py_ssize_t)length) { + memcpy(data, buffer, length); + } else { + PyErr_SetString(PyExc_IOError, "read past end of file"); + } + } else { + PyErr_SetString(PyExc_IOError, "Failed to copy buffer"); + } + } else { + PyErr_SetString(PyExc_IOError, "Failed to read file"); + } } @@ -432,9 +432,8 @@ static void read_png_data(png_structp png_ptr, png_bytep data, png_size_t length { PyObject *py_file_obj = (PyObject *)png_get_io_ptr(png_ptr); _read_png_data(py_file_obj, data, length); - if (PyErr_Occurred()) - { - png_error(png_ptr, "failed to read file"); + if (PyErr_Occurred()) { + png_error(png_ptr, "failed to read file"); } } @@ -494,9 +493,9 @@ static PyObject *_read_png(PyObject *filein, bool float_result) } Py_XDECREF(read_method); _read_png_data(py_file, header, 8); - if (PyErr_Occurred()){ - goto exit; - } + if (PyErr_Occurred()) { + goto exit; + } } if (png_sig_cmp(header, 0, 8)) { @@ -519,10 +518,9 @@ static PyObject *_read_png(PyObject *filein, bool float_result) } if (setjmp(png_jmpbuf(png_ptr))) { - if (!PyErr_Occurred()) - { - PyErr_SetString(PyExc_RuntimeError, "Error setting jump"); - } + if (!PyErr_Occurred()) { + PyErr_SetString(PyExc_RuntimeError, "Error setting jump"); + } goto exit; } From a17787b93e763c04a6879f3df0907d83dba889a5 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Sun, 1 Oct 2017 20:52:03 -0700 Subject: [PATCH 4/6] TST: add test of truncated files and buffers --- lib/matplotlib/tests/test_png.py | 34 +++++++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/tests/test_png.py b/lib/matplotlib/tests/test_png.py index 9047eed846c7..9ad8364a02a1 100644 --- a/lib/matplotlib/tests/test_png.py +++ b/lib/matplotlib/tests/test_png.py @@ -2,16 +2,17 @@ unicode_literals) import six - +from six import BytesIO import glob import os - +import shutil import numpy as np +import pytest from matplotlib.testing.decorators import image_comparison from matplotlib import pyplot as plt import matplotlib.cm as cm - +import tempfile import sys on_win = (sys.platform == 'win32') @@ -46,3 +47,30 @@ def test_imread_png_uint16(): assert (img.dtype == np.uint16) assert np.sum(img.flatten()) == 134184960 + + +def test_truncated_file(): + d = tempfile.mkdtemp() + fname = os.path.join(d, 'test.png') + fname_t = os.path.join(d, 'test_truncated.png') + plt.savefig(fname) + with open(fname, 'rb') as fin: + buf = fin.read() + with open(fname_t, 'wb') as fout: + fout.write(buf[:20]) + + with pytest.raises(Exception): + plt.imread(fname_t) + + shutil.rmtree(d) + + +def test_truncated_buffer(): + b = BytesIO() + plt.savefig(b) + b.seek(0) + b2 = BytesIO(b.read(20)) + b2.seek(0) + + with pytest.raises(Exception): + plt.imread(b2) From a647756a244f524ea610a2f0dfc5191f4991890a Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Mon, 23 Oct 2017 15:22:55 -0400 Subject: [PATCH 5/6] TST: use pytest tmpdir fixture instead of tempdir + shutil --- lib/matplotlib/tests/test_png.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/lib/matplotlib/tests/test_png.py b/lib/matplotlib/tests/test_png.py index 9ad8364a02a1..9e5dfae06dae 100644 --- a/lib/matplotlib/tests/test_png.py +++ b/lib/matplotlib/tests/test_png.py @@ -5,14 +5,12 @@ from six import BytesIO import glob import os -import shutil import numpy as np import pytest from matplotlib.testing.decorators import image_comparison from matplotlib import pyplot as plt import matplotlib.cm as cm -import tempfile import sys on_win = (sys.platform == 'win32') @@ -49,10 +47,10 @@ def test_imread_png_uint16(): assert np.sum(img.flatten()) == 134184960 -def test_truncated_file(): - d = tempfile.mkdtemp() - fname = os.path.join(d, 'test.png') - fname_t = os.path.join(d, 'test_truncated.png') +def test_truncated_file(tmpdir): + d = tmpdir.mkdir('test') + fname = str(d.join('test.png')) + fname_t = str(d.join('test_truncated.png')) plt.savefig(fname) with open(fname, 'rb') as fin: buf = fin.read() @@ -62,8 +60,6 @@ def test_truncated_file(): with pytest.raises(Exception): plt.imread(fname_t) - shutil.rmtree(d) - def test_truncated_buffer(): b = BytesIO() From 6ebcb118266ccacf02b16872e14cfcc3cfe4eb33 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Mon, 23 Oct 2017 15:25:39 -0400 Subject: [PATCH 6/6] STY: lower case error messages --- src/_png.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/_png.cpp b/src/_png.cpp index 3e0c76c55ac3..5a6a46c37332 100644 --- a/src/_png.cpp +++ b/src/_png.cpp @@ -416,10 +416,10 @@ static void _read_png_data(PyObject *py_file_obj, png_bytep data, png_size_t len PyErr_SetString(PyExc_IOError, "read past end of file"); } } else { - PyErr_SetString(PyExc_IOError, "Failed to copy buffer"); + PyErr_SetString(PyExc_IOError, "failed to copy buffer"); } } else { - PyErr_SetString(PyExc_IOError, "Failed to read file"); + PyErr_SetString(PyExc_IOError, "failed to read file"); } @@ -519,7 +519,7 @@ static PyObject *_read_png(PyObject *filein, bool float_result) if (setjmp(png_jmpbuf(png_ptr))) { if (!PyErr_Occurred()) { - PyErr_SetString(PyExc_RuntimeError, "Error setting jump"); + PyErr_SetString(PyExc_RuntimeError, "error setting jump"); } goto exit; }