|buildstatus|_
|coverage|_
About
=====
Use `libFuzzer`_ to fuzz test Python 3.6+ C extension modules.
Installation
============
`clang 8` or later is required.
.. code-block:: text
$ apt install clang
$ pip install pyfuzzer
Example Usage
=============
Hello world
-----------
Use the default mutator ``pyfuzzer.mutators.generic`` when testing the
module ``hello_world``.
.. code-block:: text
$ cd examples/hello_world
$ pyfuzzer run -l max_total_time=1 hello_world.c
<lots of libFuzzer output>
Print the function calls that found new code paths. This information
is usually useful when writing unit tests.
.. code-block:: text
$ pyfuzzer print_corpus
corpus/25409981b15b978c9fb5a5a2f4dab0c4b04e295f:
tell(b'') = 5
corpus/a8a4e6c9abfd3c6cba171579190702ddc1317df0:
tell(b'\xfd#') = b'Hello!'
corpus/80f87702ef9fbe4baf17095c79ff928b9fa1ea14:
tell(b'\x00') = True
corpus/be3d1b7df189727b2cecd6526aa8f24abbf6df10:
tell(b'\x00\xfd\x00') = 0
corpus/defd8787d638f271cd83362eafe7fdeed9fa4a8f:
tell(None) raises:
Traceback (most recent call last):
File "/home/erik/workspace/pyfuzzer/pyfuzzer/mutators/utils.py", line 35, in print_callable
res = obj(*args)
TypeError: expected bytes, NoneType found
See the `hello_world`_ for all files.
Hello world fatal error
-----------------------
Similar to the previous example, but triggers a fatal error when
``tell()`` is called with a bytes object longer than 2 bytes as its
first argument.
.. code-block:: text
$ cd examples/hello_world_fatal_error
$ pyfuzzer run hello_world.c
...
Fatal Python error: deallocating None
Current thread 0x00007f7ca99c2780 (most recent call first):
...
Print the function call that caused the crash. Just as expected, the
first argument is clearly longer than 2 bytes.
.. code-block:: text
$ pyfuzzer print_crashes
crash-1013ed88cd71fd14407b2bdbc17b95d7bc317c21:
tell(b'\n\xbf+') = None
See the `hello_world_fatal_error`_ for all files.
Custom mutator
--------------
Use the custom mutator ``hello_world_mutator`` when testing the module
``hello_world``.
Testing with a custom mutator is often more efficient than using a
generic one.
.. code-block:: text
$ cd examples/hello_world_custom_mutator
$ pyfuzzer run -l max_total_time=1 -m hello_world_mutator.py hello_world.c
...
See the `hello_world_custom_mutator`_ for all files.
Mutators
========
A mutator module uses data from `libFuzzer`_ to test a module. A
mutator module must implement the function ``setup(module)``, where
``module`` is the module under test. It shall return a mutator
instance that implements the methods ``test_one_input(self, data)``
and ``test_one_input_print(self, data)``, where ``data`` is the data
generated by `libFuzzer`_ (as a bytes object).
``test_one_input(self, data)`` performs the actual fuzz testing, while
``test_one_input_print(self, data)`` prints corpus and crashes.
A minimal mutator fuzz testing a CRC-32 algorithm could look like
below. It simply calls ``crc_32()`` with ``data`` as its only
argument.
.. code-block:: python
from pyfuzzer.mutators.generic import print_callable
class Mutator:
def __init__(self, module):
self._module = module
def test_one_input(self, data):
return module.crc_32(data)
def test_one_input_print(self, data):
print_callable(self._module.crc_32, [data])
def setup(module):
return Mutator(module)
Ideas
=====
- Add support to fuzz test pure Python modules by generating C code
using Cython.
.. |buildstatus| image:: https://travis-ci.org/eerimoq/pyfuzzer.svg
.. _buildstatus: https://travis-ci.org/eerimoq/pyfuzzer
.. |coverage| image:: https://coveralls.io/repos/github/eerimoq/pyfuzzer/badge.svg?branch=master
.. _coverage: https://coveralls.io/github/eerimoq/pyfuzzer
.. _libFuzzer: https://llvm.org/docs/LibFuzzer.html
.. _hello_world: https://github.com/eerimoq/pyfuzzer/tree/master/examples/hello_world
.. _hello_world_fatal_error: https://github.com/eerimoq/pyfuzzer/tree/master/examples/hello_world_fatal_error
.. _hello_world_custom_mutator: https://github.com/eerimoq/pyfuzzer/tree/master/examples/hello_world_custom_mutator
Raw data
{
"_id": null,
"home_page": "https://github.com/eerimoq/pyfuzzer",
"name": "pyfuzzer",
"maintainer": "",
"docs_url": null,
"requires_python": "",
"maintainer_email": "",
"keywords": "fuzz,fuzzying,test",
"author": "Erik Moqvist",
"author_email": "erik.moqvist@gmail.com",
"download_url": "https://files.pythonhosted.org/packages/fb/b8/07c19c09ed768a586986547d281709e84c73fc14087db124649ee0f08d05/pyfuzzer-0.19.0.tar.gz",
"platform": "",
"description": "|buildstatus|_\n|coverage|_\n\nAbout\n=====\n\nUse `libFuzzer`_ to fuzz test Python 3.6+ C extension modules.\n\nInstallation\n============\n\n`clang 8` or later is required.\n\n.. code-block:: text\n\n $ apt install clang\n $ pip install pyfuzzer\n\nExample Usage\n=============\n\nHello world\n-----------\n\nUse the default mutator ``pyfuzzer.mutators.generic`` when testing the\nmodule ``hello_world``.\n\n.. code-block:: text\n\n $ cd examples/hello_world\n $ pyfuzzer run -l max_total_time=1 hello_world.c\n <lots of libFuzzer output>\n\nPrint the function calls that found new code paths. This information\nis usually useful when writing unit tests.\n\n.. code-block:: text\n\n $ pyfuzzer print_corpus\n corpus/25409981b15b978c9fb5a5a2f4dab0c4b04e295f:\n tell(b'') = 5\n corpus/a8a4e6c9abfd3c6cba171579190702ddc1317df0:\n tell(b'\\xfd#') = b'Hello!'\n corpus/80f87702ef9fbe4baf17095c79ff928b9fa1ea14:\n tell(b'\\x00') = True\n corpus/be3d1b7df189727b2cecd6526aa8f24abbf6df10:\n tell(b'\\x00\\xfd\\x00') = 0\n corpus/defd8787d638f271cd83362eafe7fdeed9fa4a8f:\n tell(None) raises:\n Traceback (most recent call last):\n File \"/home/erik/workspace/pyfuzzer/pyfuzzer/mutators/utils.py\", line 35, in print_callable\n res = obj(*args)\n TypeError: expected bytes, NoneType found\n\nSee the `hello_world`_ for all files.\n\nHello world fatal error\n-----------------------\n\nSimilar to the previous example, but triggers a fatal error when\n``tell()`` is called with a bytes object longer than 2 bytes as its\nfirst argument.\n\n.. code-block:: text\n\n $ cd examples/hello_world_fatal_error\n $ pyfuzzer run hello_world.c\n ...\n Fatal Python error: deallocating None\n\n Current thread 0x00007f7ca99c2780 (most recent call first):\n ...\n\nPrint the function call that caused the crash. Just as expected, the\nfirst argument is clearly longer than 2 bytes.\n\n.. code-block:: text\n\n $ pyfuzzer print_crashes\n crash-1013ed88cd71fd14407b2bdbc17b95d7bc317c21:\n tell(b'\\n\\xbf+') = None\n\nSee the `hello_world_fatal_error`_ for all files.\n\nCustom mutator\n--------------\n\nUse the custom mutator ``hello_world_mutator`` when testing the module\n``hello_world``.\n\nTesting with a custom mutator is often more efficient than using a\ngeneric one.\n\n.. code-block:: text\n\n $ cd examples/hello_world_custom_mutator\n $ pyfuzzer run -l max_total_time=1 -m hello_world_mutator.py hello_world.c\n ...\n\nSee the `hello_world_custom_mutator`_ for all files.\n\nMutators\n========\n\nA mutator module uses data from `libFuzzer`_ to test a module. A\nmutator module must implement the function ``setup(module)``, where\n``module`` is the module under test. It shall return a mutator\ninstance that implements the methods ``test_one_input(self, data)``\nand ``test_one_input_print(self, data)``, where ``data`` is the data\ngenerated by `libFuzzer`_ (as a bytes object).\n\n``test_one_input(self, data)`` performs the actual fuzz testing, while\n``test_one_input_print(self, data)`` prints corpus and crashes.\n\nA minimal mutator fuzz testing a CRC-32 algorithm could look like\nbelow. It simply calls ``crc_32()`` with ``data`` as its only\nargument.\n\n.. code-block:: python\n\n from pyfuzzer.mutators.generic import print_callable\n\n class Mutator:\n\n def __init__(self, module):\n self._module = module\n\n def test_one_input(self, data):\n return module.crc_32(data)\n\n def test_one_input_print(self, data):\n print_callable(self._module.crc_32, [data])\n\n def setup(module):\n return Mutator(module)\n\nIdeas\n=====\n\n- Add support to fuzz test pure Python modules by generating C code\n using Cython.\n\n.. |buildstatus| image:: https://travis-ci.org/eerimoq/pyfuzzer.svg\n.. _buildstatus: https://travis-ci.org/eerimoq/pyfuzzer\n\n.. |coverage| image:: https://coveralls.io/repos/github/eerimoq/pyfuzzer/badge.svg?branch=master\n.. _coverage: https://coveralls.io/github/eerimoq/pyfuzzer\n\n.. _libFuzzer: https://llvm.org/docs/LibFuzzer.html\n\n.. _hello_world: https://github.com/eerimoq/pyfuzzer/tree/master/examples/hello_world\n\n.. _hello_world_fatal_error: https://github.com/eerimoq/pyfuzzer/tree/master/examples/hello_world_fatal_error\n\n.. _hello_world_custom_mutator: https://github.com/eerimoq/pyfuzzer/tree/master/examples/hello_world_custom_mutator",
"bugtrack_url": null,
"license": "MIT",
"summary": "Fuzz test Python modules with libFuzzer.",
"version": "0.19.0",
"split_keywords": [
"fuzz",
"fuzzying",
"test"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "fbb807c19c09ed768a586986547d281709e84c73fc14087db124649ee0f08d05",
"md5": "832acc5aaad0d83bfcc76e2651a032bc",
"sha256": "95d88b716066522ba3eb7dba0aa6ba43ea852a34a9d250cfbff01bd10bdff588"
},
"downloads": -1,
"filename": "pyfuzzer-0.19.0.tar.gz",
"has_sig": false,
"md5_digest": "832acc5aaad0d83bfcc76e2651a032bc",
"packagetype": "sdist",
"python_version": "source",
"requires_python": null,
"size": 12416,
"upload_time": "2020-01-06T18:15:17",
"upload_time_iso_8601": "2020-01-06T18:15:17.565996Z",
"url": "https://files.pythonhosted.org/packages/fb/b8/07c19c09ed768a586986547d281709e84c73fc14087db124649ee0f08d05/pyfuzzer-0.19.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2020-01-06 18:15:17",
"github": true,
"gitlab": false,
"bitbucket": false,
"github_user": "eerimoq",
"github_project": "pyfuzzer",
"travis_ci": true,
"coveralls": false,
"github_actions": false,
"requirements": [
{
"name": "Pygments",
"specs": []
}
],
"lcname": "pyfuzzer"
}