From f8438b036b775244bb6c2e11687e6ff3bd9c145e Mon Sep 17 00:00:00 2001 From: Devin Jeanpierre Date: Thu, 7 Sep 2017 14:24:51 -0700 Subject: [PATCH] Add fuzz test for ast.parse(). ast.parse() just calls compile() with PyCF_ONLY_AST to exit early, so we emulate that here\ with the call to the corresponding part of the C API. The code for compile() and Py_CompileStringObject() is duplicated, so hypothetically one could have a bug and not the other, but... nah. :) --- Modules/_xxtestfuzz/fuzz_tests.txt | 1 + Modules/_xxtestfuzz/fuzzer.c | 39 ++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/Modules/_xxtestfuzz/fuzz_tests.txt b/Modules/_xxtestfuzz/fuzz_tests.txt index 2e53bfdc71619c..5e7b74d66f0586 100644 --- a/Modules/_xxtestfuzz/fuzz_tests.txt +++ b/Modules/_xxtestfuzz/fuzz_tests.txt @@ -1,3 +1,4 @@ +fuzz_ast_parse fuzz_builtin_float fuzz_builtin_int fuzz_builtin_unicode diff --git a/Modules/_xxtestfuzz/fuzzer.c b/Modules/_xxtestfuzz/fuzzer.c index 36f721ee626164..aa9b297780bf13 100644 --- a/Modules/_xxtestfuzz/fuzzer.c +++ b/Modules/_xxtestfuzz/fuzzer.c @@ -14,6 +14,42 @@ #include #include +/* Get a null-terminated copy of some data. The caller owns the return value. */ +static char* copy_as_string(const char* data, size_t size) { + char* new_s = malloc(size + 1); + memcpy(new_s, data, size); + new_s[size] = 0; + return new_s; +} + +/* Fuzz Py_CompileStringExFlags as a proxy for ast.parse(str) */ +static int fuzz_ast_parse(const char* data, size_t size) { + char* null_terminated_data = copy_as_string(data, size); + PyCompilerFlags flags; + flags.cf_flags = PyCF_ONLY_AST; + PyObject* ast = Py_CompileStringExFlags( + null_terminated_data, + "", + Py_file_input, + &flags, + 0); + free(null_terminated_data); + + if (ast == NULL) { + /* SyntaxError (from gibberish) and MemoryError (from deeply nested + expressions) are to be expected. */ + if (PyErr_ExceptionMatches(PyExc_SyntaxError) || PyErr_ExceptionMatches(PyExc_MemoryError)) { + PyErr_Clear(); + return 0; + } else { + PyErr_Print(); + abort(); + } + } + Py_DECREF(ast); + return 0; +} + /* Fuzz PyFloat_FromString as a proxy for float(str). */ static int fuzz_builtin_float(const char* data, size_t size) { PyObject* s = PyBytes_FromStringAndSize(data, size); @@ -106,6 +142,9 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { int rv = 0; #define _Py_FUZZ_YES(test_name) (defined(_Py_FUZZ_##test_name) || !defined(_Py_FUZZ_ONE)) +#if !defined(_Py_FUZZ_ONE) || defined(_Py_FUZZ_fuzz_ast_parse) + rv |= _run_fuzz(data, size, fuzz_ast_parse); +#endif #if _Py_FUZZ_YES(fuzz_builtin_float) rv |= _run_fuzz(data, size, fuzz_builtin_float); #endif