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.
This commit is contained in:
Ammar Askar 2021-10-06 19:22:09 -04:00 committed by GitHub
parent 745c9d9dfc
commit db72e58ea5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 57 additions and 0 deletions

View File

@ -6,3 +6,4 @@ fuzz_sre_compile
fuzz_sre_match
fuzz_csv_reader
fuzz_struct_unpack
fuzz_ast_literal_eval

View File

@ -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;
}