From 4311c8dbbb04e2be40a651a27a7aeabe43c7f14f Mon Sep 17 00:00:00 2001 From: Eric Wieser Date: Thu, 24 May 2018 00:56:03 -0700 Subject: BUG: Prevent stackoverflow in conversion to datetime types Fixes gh-11154 --- numpy/core/include/numpy/npy_3kcompat.h | 8 ++++++++ numpy/core/src/multiarray/datetime.c | 24 ++++++++++++++---------- numpy/core/tests/test_datetime.py | 17 +++++++++++++++++ 3 files changed, 39 insertions(+), 10 deletions(-) (limited to 'numpy') diff --git a/numpy/core/include/numpy/npy_3kcompat.h b/numpy/core/include/numpy/npy_3kcompat.h index 56fbd99af..2d0ccd3b9 100644 --- a/numpy/core/include/numpy/npy_3kcompat.h +++ b/numpy/core/include/numpy/npy_3kcompat.h @@ -61,6 +61,14 @@ static NPY_INLINE int PyInt_Check(PyObject *op) { PySlice_GetIndicesEx((PySliceObject *)op, nop, start, end, step, slicelength) #endif +/* <2.7.11 and <3.4.4 have the wrong argument type for Py_EnterRecursiveCall */ +#if (PY_VERSION_HEX < 0x02070B00) || \ + ((0x03000000 <= PY_VERSION_HEX) && (PY_VERSION_HEX < 0x03040400)) + #define Npy_EnterRecursiveCall(x) Py_EnterRecursiveCall((char *)(x)) +#else + #define Npy_EnterRecursiveCall(x) Py_EnterRecursiveCall(x) +#endif + /* * PyString -> PyBytes */ diff --git a/numpy/core/src/multiarray/datetime.c b/numpy/core/src/multiarray/datetime.c index b026e5fae..d78eaa042 100644 --- a/numpy/core/src/multiarray/datetime.c +++ b/numpy/core/src/multiarray/datetime.c @@ -3718,19 +3718,21 @@ recursive_find_object_datetime64_type(PyObject *obj, } for (i = 0; i < len; ++i) { + int ret; PyObject *f = PySequence_GetItem(obj, i); if (f == NULL) { return -1; } - if (f == obj) { - Py_DECREF(f); - return 0; - } - if (recursive_find_object_datetime64_type(f, meta) < 0) { + if (Npy_EnterRecursiveCall(" in recursive_find_object_datetime64_type") != 0) { Py_DECREF(f); return -1; } + ret = recursive_find_object_datetime64_type(f, meta); + Py_LeaveRecursiveCall(); Py_DECREF(f); + if (ret < 0) { + return ret; + } } return 0; @@ -3820,19 +3822,21 @@ recursive_find_object_timedelta64_type(PyObject *obj, } for (i = 0; i < len; ++i) { + int ret; PyObject *f = PySequence_GetItem(obj, i); if (f == NULL) { return -1; } - if (f == obj) { - Py_DECREF(f); - return 0; - } - if (recursive_find_object_timedelta64_type(f, meta) < 0) { + if (Npy_EnterRecursiveCall(" in recursive_find_object_timedelta64_type") != 0) { Py_DECREF(f); return -1; } + ret = recursive_find_object_timedelta64_type(f, meta); + Py_LeaveRecursiveCall(); Py_DECREF(f); + if (ret < 0) { + return ret; + } } return 0; diff --git a/numpy/core/tests/test_datetime.py b/numpy/core/tests/test_datetime.py index 43e8a3325..ce635d947 100644 --- a/numpy/core/tests/test_datetime.py +++ b/numpy/core/tests/test_datetime.py @@ -17,6 +17,11 @@ try: except ImportError: _has_pytz = False +try: + RecursionError +except NameError: + RecursionError = RuntimeError # python < 3.5 + class TestDateTime(object): def test_datetime_dtype_creation(self): @@ -1979,6 +1984,18 @@ class TestDateTime(object): continue assert_raises(TypeError, np.isnat, np.zeros(10, t)) + def test_corecursive_input(self): + # construct a co-recursive list + a, b = [], [] + a.append(b) + b.append(a) + obj_arr = np.array([None]) + obj_arr[0] = a + + # gh-11154: This shouldn't cause a C stack overflow + assert_raises(RecursionError, obj_arr.astype, 'M8') + assert_raises(RecursionError, obj_arr.astype, 'm8') + class TestDateTimeData(object): -- cgit v1.2.1