From e263bb1e97ae8f84fb4f2ab5b0c4f529a2e5696d Mon Sep 17 00:00:00 2001 From: Ammar Askar Date: Fri, 28 Feb 2020 02:05:02 -0500 Subject: Fuzz struct.unpack and catch RecursionError in re.compile (GH-18679) --- .../fuzz_struct_unpack_corpus/hello_string | Bin 0 -> 9 bytes .../fuzz_struct_unpack_corpus/long_zero | Bin 0 -> 7 bytes .../fuzz_struct_unpack_corpus/varied_format_string | Bin 0 -> 22 bytes Modules/_xxtestfuzz/fuzz_tests.txt | 1 + Modules/_xxtestfuzz/fuzzer.c | 76 ++++++++++++++++++++- 5 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 Modules/_xxtestfuzz/fuzz_struct_unpack_corpus/hello_string create mode 100644 Modules/_xxtestfuzz/fuzz_struct_unpack_corpus/long_zero create mode 100644 Modules/_xxtestfuzz/fuzz_struct_unpack_corpus/varied_format_string diff --git a/Modules/_xxtestfuzz/fuzz_struct_unpack_corpus/hello_string b/Modules/_xxtestfuzz/fuzz_struct_unpack_corpus/hello_string new file mode 100644 index 0000000..92d47cd Binary files /dev/null and b/Modules/_xxtestfuzz/fuzz_struct_unpack_corpus/hello_string differ diff --git a/Modules/_xxtestfuzz/fuzz_struct_unpack_corpus/long_zero b/Modules/_xxtestfuzz/fuzz_struct_unpack_corpus/long_zero new file mode 100644 index 0000000..d952225 Binary files /dev/null and b/Modules/_xxtestfuzz/fuzz_struct_unpack_corpus/long_zero differ diff --git a/Modules/_xxtestfuzz/fuzz_struct_unpack_corpus/varied_format_string b/Modules/_xxtestfuzz/fuzz_struct_unpack_corpus/varied_format_string new file mode 100644 index 0000000..a150dc0 Binary files /dev/null and b/Modules/_xxtestfuzz/fuzz_struct_unpack_corpus/varied_format_string differ diff --git a/Modules/_xxtestfuzz/fuzz_tests.txt b/Modules/_xxtestfuzz/fuzz_tests.txt index 9d330a6..053b77b 100644 --- a/Modules/_xxtestfuzz/fuzz_tests.txt +++ b/Modules/_xxtestfuzz/fuzz_tests.txt @@ -5,3 +5,4 @@ fuzz_json_loads fuzz_sre_compile fuzz_sre_match fuzz_csv_reader +fuzz_struct_unpack diff --git a/Modules/_xxtestfuzz/fuzzer.c b/Modules/_xxtestfuzz/fuzzer.c index 74ba819..6bd2c3a 100644 --- a/Modules/_xxtestfuzz/fuzzer.c +++ b/Modules/_xxtestfuzz/fuzzer.c @@ -79,6 +79,69 @@ static int fuzz_builtin_unicode(const char* data, size_t size) { return 0; } + +PyObject* struct_unpack_method = NULL; +PyObject* struct_error = NULL; +/* Called by LLVMFuzzerTestOneInput for initialization */ +static int init_struct_unpack() { + /* Import struct.unpack */ + PyObject* struct_module = PyImport_ImportModule("struct"); + if (struct_module == NULL) { + return 0; + } + struct_error = PyObject_GetAttrString(struct_module, "error"); + if (struct_error == NULL) { + return 0; + } + struct_unpack_method = PyObject_GetAttrString(struct_module, "unpack"); + return struct_unpack_method != NULL; +} +/* Fuzz struct.unpack(x, y) */ +static int fuzz_struct_unpack(const char* data, size_t size) { + /* Everything up to the first null byte is considered the + format. Everything after is the buffer */ + const char* first_null = memchr(data, '\0', size); + if (first_null == NULL) { + return 0; + } + + size_t format_length = first_null - data; + size_t buffer_length = size - format_length - 1; + + PyObject* pattern = PyBytes_FromStringAndSize(data, format_length); + if (pattern == NULL) { + return 0; + } + PyObject* buffer = PyBytes_FromStringAndSize(first_null + 1, buffer_length); + if (buffer == NULL) { + Py_DECREF(pattern); + return 0; + } + + PyObject* unpacked = PyObject_CallFunctionObjArgs( + struct_unpack_method, pattern, buffer, NULL); + /* Ignore any overflow errors, these are easily triggered accidentally */ + if (unpacked == NULL && PyErr_ExceptionMatches(PyExc_OverflowError)) { + PyErr_Clear(); + } + /* The pascal format string will throw a negative size when passing 0 + like: struct.unpack('0p', b'') */ + if (unpacked == NULL && PyErr_ExceptionMatches(PyExc_SystemError)) { + PyErr_Clear(); + } + /* Ignore any struct.error exceptions, these can be caused by invalid + formats or incomplete buffers both of which are common. */ + if (unpacked == NULL && PyErr_ExceptionMatches(struct_error)) { + PyErr_Clear(); + } + + Py_XDECREF(unpacked); + Py_DECREF(pattern); + Py_DECREF(buffer); + return 0; +} + + #define MAX_JSON_TEST_SIZE 0x10000 PyObject* json_loads_method = NULL; @@ -190,9 +253,10 @@ static int fuzz_sre_compile(const char* data, size_t size) { PyErr_Clear(); } /* Ignore some common errors thrown by sre_parse: - Overflow, Assertion and Index */ + Overflow, Assertion, Recursion and Index */ if (compiled == NULL && (PyErr_ExceptionMatches(PyExc_OverflowError) || PyErr_ExceptionMatches(PyExc_AssertionError) || + PyErr_ExceptionMatches(PyExc_RecursionError) || PyErr_ExceptionMatches(PyExc_IndexError)) ) { PyErr_Clear(); @@ -378,6 +442,16 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { #if !defined(_Py_FUZZ_ONE) || defined(_Py_FUZZ_fuzz_builtin_unicode) rv |= _run_fuzz(data, size, fuzz_builtin_unicode); #endif +#if !defined(_Py_FUZZ_ONE) || defined(_Py_FUZZ_fuzz_struct_unpack) + static int STRUCT_UNPACK_INITIALIZED = 0; + if (!STRUCT_UNPACK_INITIALIZED && !init_struct_unpack()) { + PyErr_Print(); + abort(); + } else { + STRUCT_UNPACK_INITIALIZED = 1; + } + rv |= _run_fuzz(data, size, fuzz_struct_unpack); +#endif #if !defined(_Py_FUZZ_ONE) || defined(_Py_FUZZ_fuzz_json_loads) static int JSON_LOADS_INITIALIZED = 0; if (!JSON_LOADS_INITIALIZED && !init_json_loads()) { -- cgit v0.12