[Python-checkins] bpo-29505: Add fuzzer for ast.literal_eval (GH-28777)

miss-islington webhook-mailer at python.org
Wed Oct 6 19:22:20 EDT 2021


https://github.com/python/cpython/commit/db72e58ea5940c3942ede9f70cb897510b52fc36
commit: db72e58ea5940c3942ede9f70cb897510b52fc36
branch: main
author: Ammar Askar <ammar at ammaraskar.com>
committer: miss-islington <31488909+miss-islington at users.noreply.github.com>
date: 2021-10-06T16:22:09-07:00
summary:

bpo-29505: Add fuzzer for ast.literal_eval (GH-28777)



This supercedes https://github.com/python/cpython/pull/3437 and fuzzes the method we recommend for unsafe inputs, `ast.literal_eval`. This should exercise the tokenizer and parser.

files:
M Modules/_xxtestfuzz/fuzz_tests.txt
M Modules/_xxtestfuzz/fuzzer.c

diff --git a/Modules/_xxtestfuzz/fuzz_tests.txt b/Modules/_xxtestfuzz/fuzz_tests.txt
index 053b77b41b111..4e046ecf6d898 100644
--- a/Modules/_xxtestfuzz/fuzz_tests.txt
+++ b/Modules/_xxtestfuzz/fuzz_tests.txt
@@ -6,3 +6,4 @@ fuzz_sre_compile
 fuzz_sre_match
 fuzz_csv_reader
 fuzz_struct_unpack
+fuzz_ast_literal_eval
diff --git a/Modules/_xxtestfuzz/fuzzer.c b/Modules/_xxtestfuzz/fuzzer.c
index e1256f59cc9c2..366e81a54519a 100644
--- a/Modules/_xxtestfuzz/fuzzer.c
+++ b/Modules/_xxtestfuzz/fuzzer.c
@@ -393,6 +393,51 @@ static int fuzz_csv_reader(const char* data, size_t size) {
     return 0;
 }
 
+#define MAX_AST_LITERAL_EVAL_TEST_SIZE 0x10000
+PyObject* ast_literal_eval_method = NULL;
+/* Called by LLVMFuzzerTestOneInput for initialization */
+static int init_ast_literal_eval(void) {
+    PyObject* ast_module = PyImport_ImportModule("ast");
+    if (ast_module == NULL) {
+        return 0;
+    }
+    ast_literal_eval_method = PyObject_GetAttrString(ast_module, "literal_eval");
+    return ast_literal_eval_method != NULL;
+}
+/* Fuzz ast.literal_eval(x) */
+static int fuzz_ast_literal_eval(const char* data, size_t size) {
+    if (size > MAX_AST_LITERAL_EVAL_TEST_SIZE) {
+        return 0;
+    }
+    /* Ignore non null-terminated strings since ast can't handle
+       embedded nulls */
+    if (memchr(data, '\0', size) == NULL) {
+        return 0;
+    }
+
+    PyObject* s = PyUnicode_FromString(data);
+    /* Ignore exceptions until we have a valid string */
+    if (s == NULL) {
+        PyErr_Clear();
+        return 0;
+    }
+
+    PyObject* literal = PyObject_CallOneArg(ast_literal_eval_method, s);
+    /* Ignore some common errors thrown by ast.literal_eval */
+    if (literal == NULL && (PyErr_ExceptionMatches(PyExc_ValueError) ||
+                            PyErr_ExceptionMatches(PyExc_TypeError) ||
+                            PyErr_ExceptionMatches(PyExc_SyntaxError) ||
+                            PyErr_ExceptionMatches(PyExc_MemoryError) ||
+                            PyErr_ExceptionMatches(PyExc_RecursionError))
+    ) {
+        PyErr_Clear();
+    }
+
+    Py_XDECREF(literal);
+    Py_DECREF(s);
+    return 0;
+}
+
 /* Run fuzzer and abort on failure. */
 static int _run_fuzz(const uint8_t *data, size_t size, int(*fuzzer)(const char* , size_t)) {
     int rv = fuzzer((const char*) data, size);
@@ -507,6 +552,17 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
     }
 
     rv |= _run_fuzz(data, size, fuzz_csv_reader);
+#endif
+#if !defined(_Py_FUZZ_ONE) || defined(_Py_FUZZ_fuzz_ast_literal_eval)
+    static int AST_LITERAL_EVAL_INITIALIZED = 0;
+    if (!AST_LITERAL_EVAL_INITIALIZED && !init_ast_literal_eval()) {
+        PyErr_Print();
+        abort();
+    } else {
+        AST_LITERAL_EVAL_INITIALIZED = 1;
+    }
+
+    rv |= _run_fuzz(data, size, fuzz_ast_literal_eval);
 #endif
   return rv;
 }



More information about the Python-checkins mailing list