About
Use libFuzzer to fuzz test Python 3.6+ C extension modules.
Ideas
- Use type annotations for less type errors in generic mutator.
- Add support to fuzz test pure Python modules by generating C code from them using Cython.
Installation
$ apt install clang
$ pip install pyfuzzer
Example Usage
Use the default generic mutator when testing the module hello_world.
$ cd examples/hello_world
$ pyfuzzer hello_world hello_world.c
clang -fprofile-instr-generate -fcoverage-mapping -g -fsanitize=fuzzer -fsanitize=signed-integer-overflow -fno-sanitize-recover=all -I/usr/include/python3.7m hello_world.c module.c /home/erik/workspace/pyfuzzer/pyfuzzer/pyfuzzer.c -Wl,-Bsymbolic-functions -Wl,-z,relro -lpython3.7m -o hello_world
rm -f hello_world.profraw
./hello_world -max_total_time=1 -max_len=4096
INFO: Seed: 1479758674
INFO: Loaded 1 modules (22 inline 8-bit counters): 22 [0x47446d, 0x474483),
INFO: Loaded 1 PC tables (22 PCs): 22 [0x4616e0,0x461840),
ModuleNotFoundError: No module named 'mutator'
INFO: A corpus is not provided, starting from an empty corpus
#2 INITED cov: 7 ft: 8 corp: 1/1b lim: 4 exec/s: 0 rss: 44Mb
#10 NEW cov: 8 ft: 9 corp: 2/3b lim: 4 exec/s: 0 rss: 44Mb L: 2/2 MS: 3 ChangeByte-ChangeBit-CopyPart-
#11 NEW cov: 9 ft: 10 corp: 3/6b lim: 4 exec/s: 0 rss: 44Mb L: 3/3 MS: 1 CopyPart-
#23 NEW cov: 10 ft: 11 corp: 4/10b lim: 4 exec/s: 0 rss: 44Mb L: 4/4 MS: 2 CopyPart-CrossOver-
#1044738 DONE cov: 10 ft: 11 corp: 4/10b lim: 4096 exec/s: 522369 rss: 44Mb
Done 1044738 runs in 2 second(s)
llvm-profdata merge -sparse hello_world.profraw -o hello_world.profdata
llvm-cov show hello_world -instr-profile=hello_world.profdata -ignore-filename-regex=\.h|pyfuzzer.c|module.c
1| |#include <Python.h>
2| |
3| |PyDoc_STRVAR(
4| | tell_doc,
5| | "tell(message)\n"
6| | "--\n"
7| | "Arguments: (message:bytes)\n");
8| |
9| |static PyObject *m_tell(PyObject *module_p, PyObject *message_p)
10| 1.04M|{
11| 1.04M| Py_ssize_t size;
12| 1.04M| char* buf_p;
13| 1.04M| int res;
14| 1.04M| PyObject *res_p;
15| 1.04M|
16| 1.04M| res = PyBytes_AsStringAndSize(message_p, &buf_p, &size);
17| 1.04M|
18| 1.04M| if (res != -1) {
19| 1.04M| switch (size) {
20| 1.04M|
21| 1.04M| case 0:
22| 47.6k| res_p = PyLong_FromLong(5);
23| 47.6k| break;
24| 1.04M|
25| 1.04M| case 1:
26| 107k| res_p = PyBool_FromLong(1);
27| 107k| break;
28| 1.04M|
29| 1.04M| case 2:
30| 114k| res_p = PyBytes_FromString("Hello!");
31| 114k| break;
32| 1.04M|
33| 1.04M| default:
34| 774k| res_p = PyLong_FromLong(0);
35| 774k| break;
36| 0| }
37| 0| } else {
38| 0| Py_INCREF(Py_None);
39| 0| res_p = Py_None;
40| 0| }
41| 1.04M|
42| 1.04M| return (res_p);
43| 1.04M|}
44| |
45| |static struct PyMethodDef methods[] = {
46| | { "tell", m_tell, METH_O, tell_doc},
47| | { NULL }
48| |};
49| |
50| |static PyModuleDef module = {
51| | PyModuleDef_HEAD_INIT,
52| | .m_name = "hello_world",
53| | .m_size = -1,
54| | .m_methods = methods
55| |};
56| |
57| |PyMODINIT_FUNC PyInit_hello_world(void)
58| 1|{
59| 1| return (PyModule_Create(&module));
60| 1|}
Use a custom mutator when testing the module hello_world.
Testing with a custom mutator is often more efficient then using a generic.
$ cd examples/hello_world
$ pyfuzzer -m mutator.py hello_world hello_world.c
...