diff options
author | Simon Gibbons <simongibbons@gmail.com> | 2018-11-08 20:16:34 +0000 |
---|---|---|
committer | Simon Gibbons <simongibbons@gmail.com> | 2018-11-10 11:31:52 +0000 |
commit | fbdcb5b7b38a7063357f881397ccdc546f46ec5b (patch) | |
tree | fa26c05e7de5c7ee7ff6ca44cc1caa7c98bd603f /numpy/core | |
parent | d0e2f1ac0c0fb4aaf791c9082cff1d6b04545410 (diff) | |
download | numpy-fbdcb5b7b38a7063357f881397ccdc546f46ec5b.tar.gz |
BUG: Fix segfault when an error occurs in np.fromfile
There is a problem with the way in which we handle
errors which occur in the call to `PyArray_FromFile`
in `np.fromfile`.
The problem here is twofold.
1. The return value isn't checked, therefore if we reach
the fail block, we will attempt a DECREF on a NULL and
go down in flames.
2. The cleanup code on the filepointers (most notabily the
call to `npy_PyFile_DupClose2`) assumes that there is no
error set to work.
This PR addresses these issues
1. By adding a NULL check to the fail block to ensure we don't
attempt a DECREF on a NULL.
2. By saving the error state before attempting the cleanup code
on the file descriptor, and then restoring it after.
Fixes: #12300
Diffstat (limited to 'numpy/core')
-rw-r--r-- | numpy/core/include/numpy/npy_3kcompat.h | 30 | ||||
-rw-r--r-- | numpy/core/src/multiarray/multiarraymodule.c | 11 | ||||
-rw-r--r-- | numpy/core/tests/test_multiarray.py | 13 |
3 files changed, 53 insertions, 1 deletions
diff --git a/numpy/core/include/numpy/npy_3kcompat.h b/numpy/core/include/numpy/npy_3kcompat.h index 3721a560b..832bc0599 100644 --- a/numpy/core/include/numpy/npy_3kcompat.h +++ b/numpy/core/include/numpy/npy_3kcompat.h @@ -384,6 +384,36 @@ npy_PyFile_CloseFile(PyObject *file) } +/* This is a copy of _PyErr_ChainExceptions + */ +static NPY_INLINE void +npy_PyErr_ChainExceptions(PyObject *exc, PyObject *val, PyObject *tb) +{ + if (exc == NULL) + return; + + if (PyErr_Occurred()) { + /* only py3 supports this anyway */ + #ifdef NPY_PY3K + PyObject *exc2, *val2, *tb2; + PyErr_Fetch(&exc2, &val2, &tb2); + PyErr_NormalizeException(&exc, &val, &tb); + if (tb != NULL) { + PyException_SetTraceback(val, tb); + Py_DECREF(tb); + } + Py_DECREF(exc); + PyErr_NormalizeException(&exc2, &val2, &tb2); + PyException_SetContext(val2, val); + PyErr_Restore(exc2, val2, tb2); + #endif + } + else { + PyErr_Restore(exc, val, tb); + } +} + + /* This is a copy of _PyErr_ChainExceptions, with: * - a minimal implementation for python 2 * - __cause__ used instead of __context__ diff --git a/numpy/core/src/multiarray/multiarraymodule.c b/numpy/core/src/multiarray/multiarraymodule.c index 8db1a1951..9e42c115c 100644 --- a/numpy/core/src/multiarray/multiarraymodule.c +++ b/numpy/core/src/multiarray/multiarraymodule.c @@ -2044,6 +2044,7 @@ static PyObject * array_fromfile(PyObject *NPY_UNUSED(ignored), PyObject *args, PyObject *keywds) { PyObject *file = NULL, *ret; + PyObject *err_type = NULL, *err_value = NULL, *err_traceback = NULL; char *sep = ""; Py_ssize_t nin = -1; static char *kwlist[] = {"file", "dtype", "count", "sep", NULL}; @@ -2079,18 +2080,26 @@ array_fromfile(PyObject *NPY_UNUSED(ignored), PyObject *args, PyObject *keywds) } ret = PyArray_FromFile(fp, type, (npy_intp) nin, sep); + /* If an exception is thrown in the call to PyArray_FromFile + * we need to clear it, and restore it later to ensure that + * we can cleanup the duplicated file descriptor properly. + */ + PyErr_Fetch(&err_type, &err_value, &err_traceback); if (npy_PyFile_DupClose2(file, fp, orig_pos) < 0) { + npy_PyErr_ChainExceptions(err_type, err_value, err_traceback); goto fail; } if (own && npy_PyFile_CloseFile(file) < 0) { + npy_PyErr_ChainExceptions(err_type, err_value, err_traceback); goto fail; } + PyErr_Restore(err_type, err_value, err_traceback); Py_DECREF(file); return ret; fail: Py_DECREF(file); - Py_DECREF(ret); + Py_XDECREF(ret); return NULL; } diff --git a/numpy/core/tests/test_multiarray.py b/numpy/core/tests/test_multiarray.py index 4b2a38990..cee8ed35c 100644 --- a/numpy/core/tests/test_multiarray.py +++ b/numpy/core/tests/test_multiarray.py @@ -4541,6 +4541,19 @@ class TestIO(object): f.close() assert_equal(pos, 10, err_msg=err_msg) + def test_load_object_array_fromfile(self): + # gh-12300 + with open(self.filename, 'w') as f: + # Ensure we have a file with consistent contents + pass + + with open(self.filename, 'rb') as f: + assert_raises_regex(ValueError, "Cannot read into object array", + np.fromfile, f, dtype=object) + + assert_raises_regex(ValueError, "Cannot read into object array", + np.fromfile, self.filename, dtype=object) + def _check_from(self, s, value, **kw): if 'sep' not in kw: y = np.frombuffer(s, **kw) |