[Python-checkins] Fuzz struct.unpack and catch RecursionError in re.compile (GH-18679)

Ammar Askar webhook-mailer at python.org
Fri Feb 28 02:05:09 EST 2020


https://github.com/python/cpython/commit/e263bb1e97ae8f84fb4f2ab5b0c4f529a2e5696d
commit: e263bb1e97ae8f84fb4f2ab5b0c4f529a2e5696d
branch: master
author: Ammar Askar <ammar at ammaraskar.com>
committer: GitHub <noreply at github.com>
date: 2020-02-27T23:05:02-08:00
summary:

Fuzz struct.unpack and catch RecursionError in re.compile (GH-18679)

files:
A Modules/_xxtestfuzz/fuzz_struct_unpack_corpus/hello_string
A Modules/_xxtestfuzz/fuzz_struct_unpack_corpus/long_zero
A Modules/_xxtestfuzz/fuzz_struct_unpack_corpus/varied_format_string
M Modules/_xxtestfuzz/fuzz_tests.txt
M Modules/_xxtestfuzz/fuzzer.c

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 0000000000000..92d47cd358eef
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 0000000000000..d952225c3a6e0
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 0000000000000..a150dc087adfe
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 9d330a668ee88..053b77b41b111 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 74ba819b8b50b..6bd2c3aedccc9 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()) {



More information about the Python-checkins mailing list