python-afl-0.6.1/0000755000000000000000000000000013136647265013612 5ustar00rootroot00000000000000python-afl-0.6.1/py-afl-tmin0000755000000000000000000000041113065510072015653 0ustar00rootroot00000000000000#!/bin/sh export PYTHON_AFL_SIGNAL=${PYTHON_AFL_SIGNAL:-SIGUSR1} if ! command -v afl-tmin > /dev/null then cat >&2 < installed? EOF exit 127 fi exec afl-tmin "$@" # vim:ts=4 sts=4 sw=4 et python-afl-0.6.1/MANIFEST.in0000644000000000000000000000023313071147120015325 0ustar00rootroot00000000000000exclude *.c exclude README include *.pyx include LICENSE include MANIFEST.in include doc/* include private/* include py-afl-* recursive-include tests *.py python-afl-0.6.1/doc/0000755000000000000000000000000013136647265014357 5ustar00rootroot00000000000000python-afl-0.6.1/doc/LICENSE0000644000000000000000000000207413066016716015360 0ustar00rootroot00000000000000Copyright © 2013-2017 Jakub Wilk Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. python-afl-0.6.1/doc/README0000644000000000000000000000520413070751117015225 0ustar00rootroot00000000000000This is experimental module that enables `American fuzzy lop`_ fork server and instrumentation for pure-Python code. .. _American fuzzy lop: http://lcamtuf.coredump.cx/afl/ HOWTO ----- * Add this code (ideally, after all other modules are already imported) to the target program: .. code:: python import afl afl.init() * The instrumentation is currently implemented with a `trace function`_, which is called whenever a new local scope is entered. You might need to wrap the code of the main program in a function to get it instrumented correctly. .. _trace function: https://docs.python.org/2/library/sys.html#sys.settrace * Optionally, add this code at the end of the target program: .. code:: python os._exit(0) This should speed up fuzzing considerably, at the risk of not catching bugs that could happen during normal exit. * For persistent mode, wrap the tested code in this loop: .. code:: python while afl.loop(N): ... where ``N`` is the number of inputs to process before restarting. You shouldn't call ``afl.init()`` in this case. afl-fuzz ≥ 1.82b is required for this feature. * Use *py-afl-fuzz* instead of *afl-fuzz*:: $ py-afl-fuzz [options] -- /path/to/fuzzed/python/script [...] * The instrumentation is a bit slow at the moment, so you might want to enable the dumb mode (``-n``), while still leveraging the fork server. afl-fuzz ≥ 1.95b is required for this feature. Environment variables --------------------- The following environment variables affect *python-afl* behavior: ``PYTHON_AFL_SIGNAL`` If this variable is set, *python-afl* installs an exception hook that kills the current process with the selected signal. That way *afl-fuzz* can treat unhandled exceptions as crashes. By default, *py-afl-fuzz*, *py-afl-showmap*, *python-afl-cmin*, and *py-afl-tmin* set this variable to ``SIGUSR1``. You can set ``PYTHON_AFL_SIGNAL`` to another signal; or set it to ``0`` to disable the exception hook. ``PYTHON_AFL_PERSISTENT`` Persistent mode is enabled only if this variable is set. *py-afl-fuzz* sets this variable automatically, so there should normally no need to set it manually. Further reading --------------- * `Introduction to Fuzzing in Python with AFL `_ by Alex Gaynor * `AFL's README `_ Prerequisites ------------- To build the module, you will need: * Python 2.6+ or 3.2+ * Cython ≥ 0.19 (only at build time) *py-afl-fuzz* requires AFL proper to be installed. .. vim:ft=rst ts=3 sts=3 sw=3 et python-afl-0.6.1/doc/changelog0000644000000000000000000001062413136646574016236 0ustar00rootroot00000000000000python-afl (0.6.1) unstable; urgency=low * Improve the test suite. + Make the py-afl-cmin test pass when run in a subdirectory of /tmp. -- Jakub Wilk Fri, 28 Jul 2017 16:43:06 +0200 python-afl (0.6) unstable; urgency=low * Add py-afl-cmin. Thanks to @jabdoa2 for the bug report. https://github.com/jwilk/python-afl/issues/6 * Add py-afl-showmap and py-afl-tmin. Bare afl-showmap and afl-tmin were broken since 0.5. * Put license into a separate file. * Improve the test suite. * Update URLs in trophy-case. -- Jakub Wilk Wed, 05 Apr 2017 13:28:37 +0200 python-afl (0.5.5) unstable; urgency=low * Improve the test suite: + Kill stray processes that afl-fuzz might have left behind. Thanks to Daniel Stender for the bug report. https://bugs.debian.org/833675 https://github.com/jwilk/python-afl/issues/4 -- Jakub Wilk Tue, 16 Aug 2016 22:08:57 +0200 python-afl (0.5.4) unstable; urgency=low * Improve README: + Fix formatting. + Add “Further reading” links. + Document runtime and build-time dependencies. * Improve error handling. Thanks to @PhillipSz for the bug report. https://github.com/jwilk/python-afl/issues/1 * Improve the setup script: + Make the package installable with pip, even when Cython were missing. Thanks to @mrmagooey for the bug report. https://github.com/jwilk/python-afl/issues/3 + Add “Programming Language” classifiers. * Improve the test suite. -- Jakub Wilk Sat, 30 Jul 2016 16:43:52 +0200 python-afl (0.5.3) unstable; urgency=low * Fix compatibility with Cython 0.24. -- Jakub Wilk Thu, 07 Apr 2016 12:56:08 +0200 python-afl (0.5.2) unstable; urgency=low [ Jakub Wilk ] * Document that afl-fuzz ≥ 1.95b is required for the -n option. * Document that you might need to wrap code in a function to get it instrumented correctly. Thanks to Peter Dohm for the bug report. * Improve the test suite. [ Alex Gaynor ] * Fix the afl.loop() docstring. -- Jakub Wilk Sat, 13 Feb 2016 23:41:05 +0100 python-afl (0.5.1) unstable; urgency=low * Fix TypeError when built with Cython 0.23.2. -- Jakub Wilk Fri, 18 Sep 2015 11:12:12 +0200 python-afl (0.5) unstable; urgency=low * Fix deprecated call to afl.start() in README. * Make afl.start() emit DeprecationWarning. * Enable persistent mode only if PYTHON_AFL_PERSISTENT is set. Let py-afl-fuzz set this variable. * Don't install the exception hook, unless enabled with PYTHON_AFL_SIGNAL. Let py-afl-fuzz set this variable to SIGUSR1. -- Jakub Wilk Wed, 02 Sep 2015 11:12:42 +0200 python-afl (0.4) unstable; urgency=low * Rename afl.start() as afl.init(), for consistency with AFL >= 1.89b. The old name is still available, but it's deprecated. * Add new interface for persistent mode, afl.loop(). Remove the old interface, afl.persistent(). * Improve the test suite. -- Jakub Wilk Tue, 01 Sep 2015 16:28:06 +0200 python-afl (0.3) unstable; urgency=low * Implement persistent mode. * Add docstrings for the exported functions. * Add __version__. * Don't rely on the Python hash() function for computing code location identifiers. + Don't set PYTHONHASHSEED in py-afl-fuzz. + Remove the py-afl-showmap command. afl-showmap proper can be now used for Python code. + Remove the AflError class. It was only used for checking PYTHONHASHSEED. * Improve the setup script: + Check Cython version. * Implement a small test suite. -- Jakub Wilk Mon, 31 Aug 2015 16:56:12 +0200 python-afl (0.2.1) unstable; urgency=low * Make the setup script install the py-afl-fuzz and py-afl-showmap binaries. -- Jakub Wilk Tue, 14 Jul 2015 19:34:35 +0200 python-afl (0.2) unstable; urgency=low * Automatically disable instrumentation when the -n option is provided. Setting the PYTHON_AFL_DUMB environment variable is no longer needed. Thanks to Michal Zalewski for the hint how to implement this feature. * Update the module description to stress that it's dedicated for pure-Python code. -- Jakub Wilk Mon, 27 Apr 2015 19:31:08 +0200 python-afl (0.1) unstable; urgency=low * Initial release. -- Jakub Wilk Fri, 17 Apr 2015 00:15:16 +0200 python-afl-0.6.1/doc/trophy-case0000644000000000000000000000441513066710260016530 0ustar00rootroot00000000000000The bug-o-rama trophy case ========================== The following bugs were found with help of *python-afl*: i18nspector__ ------------- Multiple bugs in the plural expression parser: | https://github.com/jwilk/i18nspector/commit/c340dc28a1fe | https://github.com/jwilk/i18nspector/commit/74ac2b9e9882 | https://github.com/jwilk/i18nspector/commit/c9e4fd0efc13 | https://github.com/jwilk/i18nspector/commit/1febfc2bd612 | https://github.com/jwilk/i18nspector/commit/1d671f43497e | https://github.com/jwilk/i18nspector/commit/0767e9924ab1 | https://github.com/jwilk/i18nspector/commit/1f6993b34ca5 | https://github.com/jwilk/i18nspector/commit/6a76c4884d0b .. __: http://jwilk.net/software/i18nspector PyPDF2__ -------- | https://github.com/mstamy2/PyPDF2/issues/184 .. __: https://mstamy2.github.io/PyPDF2/ enzyme__ -------- | https://github.com/Diaoul/enzyme/issues/9 | https://github.com/Diaoul/enzyme/issues/10 | https://github.com/Diaoul/enzyme/issues/11 | https://github.com/Diaoul/enzyme/issues/12 | https://github.com/Diaoul/enzyme/issues/13 | https://github.com/Diaoul/enzyme/issues/14 | https://github.com/Diaoul/enzyme/issues/15 | https://github.com/Diaoul/enzyme/issues/16 | https://github.com/Diaoul/enzyme/issues/17 | https://github.com/Diaoul/enzyme/issues/18 | https://github.com/Diaoul/enzyme/issues/19 | https://github.com/Diaoul/enzyme/issues/20 | https://github.com/Diaoul/enzyme/issues/21 | https://github.com/Diaoul/enzyme/issues/22 .. __: https://github.com/Diaoul/enzyme PyASN.1__ --------- | https://github.com/pyca/cryptography/issues/1838 .. __: http://pyasn1.sourceforge.net/ gunicorn__ ---------- | https://github.com/benoitc/gunicorn/issues/1023 .. __: http://gunicorn.org/ dateutil__ ---------- | https://github.com/dateutil/dateutil/issues/82 .. __: https://pypi.python.org/pypi/python-dateutil regex__ ------- | https://bitbucket.org/mrabarnett/mrab-regex/issues/197/valueerror-in-regexcompile | https://bitbucket.org/mrabarnett/mrab-regex/issues/198/valueerror-in-regexcompile | https://bitbucket.org/mrabarnett/mrab-regex/issues/199/segfault-in-recompile | https://bitbucket.org/mrabarnett/mrab-regex/issues/200/attributeerror-in-regexcompile-with-latest .. __: https://pypi.python.org/pypi/regex .. vim:ft=rst ts=3 sts=3 sw=3 et python-afl-0.6.1/afl.pyx0000644000000000000000000001416213072234561015110 0ustar00rootroot00000000000000# Copyright © 2014-2016 Jakub Wilk # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the “Software”), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. #cython: autotestdict=False #cython: c_string_encoding=default ''' American fuzzy lop fork server and instrumentation for pure-Python code ''' __version__ = '0.6.1' cdef object os, signal, struct, sys, warnings import os import signal import struct import sys import warnings # These constants must be kept in sync with afl-fuzz: DEF SHM_ENV_VAR = b'__AFL_SHM_ID' DEF FORKSRV_FD = 198 DEF MAP_SIZE_POW2 = 16 DEF MAP_SIZE = 1 << MAP_SIZE_POW2 from cpython.exc cimport PyErr_SetFromErrno from libc cimport errno from libc.stddef cimport size_t from libc.stdint cimport uint32_t from libc.stdlib cimport getenv from libc.string cimport strlen cdef extern from 'sys/shm.h': unsigned char *shmat(int shmid, void *shmaddr, int shmflg) cdef unsigned char *afl_area = NULL cdef unsigned int prev_location = 0 cdef inline unsigned int lhash(const char *key, size_t offset): # 32-bit Fowler–Noll–Vo hash function cdef size_t len = strlen(key) cdef uint32_t h = 0x811C9DC5 while len > 0: h ^= key[0]; h *= 0x01000193 len -= 1 key += 1 while offset > 0: h ^= offset; h *= 0x01000193 offset >>= 8 return h def _hash(key, offset): # This function is not a part of public API. # It is provided only to facilitate testing. return lhash(key, offset) cdef object trace def trace(frame, event, arg): global prev_location cdef unsigned int location, offset location = ( lhash(frame.f_code.co_filename, frame.f_lineno) % MAP_SIZE ) offset = location ^ prev_location prev_location = location // 2 afl_area[offset] += 1 # TODO: make it configurable which modules are instrumented, and which are not return trace cdef int except_signal_id = 0 cdef object except_signal_name = os.getenv('PYTHON_AFL_SIGNAL') or '0' if except_signal_name.isdigit(): except_signal_id = int(except_signal_name) else: if except_signal_name[:3] != 'SIG': except_signal_name = 'SIG' + except_signal_name except_signal_id = getattr(signal, except_signal_name) cdef object excepthook def excepthook(tp, value, traceback): os.kill(os.getpid(), except_signal_id) cdef bint init_done = False cdef int _init(bint persistent_mode) except -1: global afl_area, init_done use_forkserver = True try: os.write(FORKSRV_FD + 1, b'\0\0\0\0') except OSError as exc: if exc.errno == errno.EBADF: use_forkserver = False else: raise if init_done: raise RuntimeError('AFL already initialized') init_done = True child_stopped = False child_pid = 0 while use_forkserver: [child_killed] = struct.unpack('I', os.read(FORKSRV_FD, 4)) if child_stopped and child_killed: os.waitpid(child_pid, 0) child_stopped = False if child_stopped: os.kill(child_pid, signal.SIGCONT) child_stopped = False else: child_pid = os.fork() if not child_pid: # child: break # parent: os.write(FORKSRV_FD + 1, struct.pack('I', child_pid)) (child_pid, status) = os.waitpid(child_pid, os.WUNTRACED if persistent_mode else 0) child_stopped = os.WIFSTOPPED(status) os.write(FORKSRV_FD + 1, struct.pack('I', status)) if use_forkserver: os.close(FORKSRV_FD) os.close(FORKSRV_FD + 1) if except_signal_id != 0: sys.excepthook = excepthook cdef const char * afl_shm_id = getenv(SHM_ENV_VAR) if afl_shm_id == NULL: return 0 afl_area = shmat(int(afl_shm_id), NULL, 0) if afl_area == -1: PyErr_SetFromErrno(OSError) sys.settrace(trace) return 0 def init(): ''' init() Start the fork server and enable instrumentation. This function should be called as late as possible, but before the input is read, and before any threads are started. ''' _init(persistent_mode=False) def start(): ''' deprecated alias for afl.init() ''' warnings.warn('afl.start() is deprecated, use afl.init() instead', DeprecationWarning) _init(persistent_mode=False) cdef bint persistent_allowed = False cdef unsigned long persistent_counter = 0 def loop(max=None): ''' while loop([max]): ... Start the fork server and enable instrumentation, then run the code inside the loop body in persistent mode. afl-fuzz >= 1.82b is required for this feature. ''' global persistent_allowed, persistent_counter if persistent_counter == 0: persistent_allowed = os.getenv('PYTHON_AFL_PERSISTENT') is not None _init(persistent_mode=persistent_allowed) persistent_counter = 1 return True cont = persistent_allowed and ( max is None or persistent_counter < max ) if cont: os.kill(os.getpid(), signal.SIGSTOP) persistent_counter += 1 return True else: return False __all__ = [ 'init', 'loop', ] # vim:ts=4 sts=4 sw=4 et python-afl-0.6.1/tests/0000755000000000000000000000000013136647265014754 5ustar00rootroot00000000000000python-afl-0.6.1/tests/target_persistent.py0000644000000000000000000000054213070775323021067 0ustar00rootroot00000000000000import sys import afl def main(): s = sys.stdin.read() if len(s) < 1: print('Hum?') sys.exit(1) s.encode('ASCII') if s[0] == '0': print('Looks like a zero to me!') else: print('A non-zero value? How quaint!') if __name__ == '__main__': while afl.loop(): main() # vim:ts=4 sts=4 sw=4 et python-afl-0.6.1/tests/tools.py0000644000000000000000000001613013136645461016463 0ustar00rootroot00000000000000# encoding=UTF-8 # Copyright © 2013-2017 Jakub Wilk # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the “Software”), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import contextlib import functools import os import re import shutil import subprocess as ipc import sys import tempfile import traceback import warnings import nose.tools from nose import SkipTest from nose.tools import ( assert_equal, assert_not_equal, assert_true, ) def noseimport(vmaj, vmin, name=None): def wrapper(f): if f.__module__ == 'unittest.case': return f if sys.version_info >= (vmaj, vmin): return getattr(nose.tools, name or f.__name__) return f return wrapper @noseimport(2, 7) class assert_raises(object): def __init__(self, exc_type): self._exc_type = exc_type self.exception = None def __enter__(self): return self def __exit__(self, exc_type, exc_value, tb): if exc_type is None: assert_true(False, '{0} not raised'.format(self._exc_type.__name__)) if not issubclass(exc_type, self._exc_type): return False if isinstance(exc_value, exc_type): pass # This branch is not always taken in Python 2.6: # https://bugs.python.org/issue7853 elif isinstance(exc_value, tuple): exc_value = exc_type(*exc_value) else: exc_value = exc_type(exc_value) self.exception = exc_value return True @noseimport(2, 7, 'assert_raises_regexp') @noseimport(3, 2) @contextlib.contextmanager def assert_raises_regex(exc_type, regex): with assert_raises(exc_type) as ecm: yield assert_regex(str(ecm.exception), regex) @noseimport(2, 7, 'assert_regexp_matches') @noseimport(3, 2) def assert_regex(text, regex): try: str_types = basestring except NameError: str_types = (str, bytes) if isinstance(regex, str_types): regex = re.compile(regex) if not regex.search(text): message = "Regex didn't match: {0!r} not found in {1!r}".format(regex.pattern, text) assert_true(False, msg=message) @noseimport(3, 2) @contextlib.contextmanager def assert_warns_regex(exc_type, regex): with warnings.catch_warnings(record=True) as wlog: warnings.simplefilter('always', exc_type) yield firstw = None for warning in wlog: w = warning.message if not isinstance(w, exc_type): continue if firstw is None: firstw = w if re.search(regex, str(w)): return if firstw is None: assert_true(False, msg='{exc} not triggered'.format(exc=exc_type.__name__)) else: assert_true(False, msg='{exc!r} does not match {re!r}'.format(exc=str(firstw), re=regex)) class IsolatedError(Exception): pass def _n_relevant_tb_levels(tb): n = 0 while tb and '__unittest' not in tb.tb_frame.f_globals: n += 1 tb = tb.tb_next return n def clean_environ(): for key in list(os.environ): if key.startswith('PYTHON_AFL_'): del os.environ[key] os.environ['AFL_SKIP_CPUFREQ'] = '1' os.environ['AFL_I_DONT_CARE_ABOUT_MISSING_CRASHES'] = '1' os.environ['AFL_NO_AFFINITY'] = '1' os.environ['AFL_ALLOW_TMP'] = '1' # AFL >= 2.48b os.environ['PWD'] = '//' + os.getcwd() # poor man's AFL_ALLOW_TMP for AFL << 2.48b def run(cmd, stdin='', xstatus=0): child = ipc.Popen( list(cmd), stdin=ipc.PIPE, stdout=ipc.PIPE, stderr=ipc.PIPE, preexec_fn=clean_environ, ) (stdout, stderr) = child.communicate(stdin) if child.returncode != xstatus: if str is not bytes: stderr = stderr.decode('ASCII', 'replace') print(stderr) raise ipc.CalledProcessError(child.returncode, cmd[0]) return (stdout, stderr) def fork_isolation(f): EXIT_EXCEPTION = 101 EXIT_SKIP_TEST = 102 exit = os._exit # pylint: disable=redefined-builtin,protected-access # sys.exit() can't be used here, because nose catches all exceptions, # including SystemExit @functools.wraps(f) def wrapper(*args, **kwargs): readfd, writefd = os.pipe() pid = os.fork() if pid == 0: # child: os.close(readfd) try: f(*args, **kwargs) except SkipTest as exc: s = str(exc) if not isinstance(s, bytes): s = s.encode('UTF-8') with os.fdopen(writefd, 'wb') as fp: fp.write(s) exit(EXIT_SKIP_TEST) except Exception: # pylint: disable=broad-except exctp, exc, tb = sys.exc_info() s = traceback.format_exception(exctp, exc, tb, _n_relevant_tb_levels(tb)) s = ''.join(s) if not isinstance(s, bytes): s = s.encode('UTF-8') del tb with os.fdopen(writefd, 'wb') as fp: fp.write(s) exit(EXIT_EXCEPTION) exit(0) else: # parent: os.close(writefd) with os.fdopen(readfd, 'rb') as fp: msg = fp.read() msg = msg if not isinstance(msg, str): msg = msg.decode('UTF-8') msg = msg.rstrip('\n') pid, status = os.waitpid(pid, 0) if status == (EXIT_EXCEPTION << 8): raise IsolatedError('\n\n' + msg) elif status == (EXIT_SKIP_TEST << 8): raise SkipTest(msg) elif status == 0 and msg == '': pass else: raise RuntimeError('unexpected isolated process status {0}'.format(status)) return wrapper @contextlib.contextmanager def tempdir(): d = tempfile.mkdtemp(prefix='python-afl.') try: yield d finally: shutil.rmtree(d) __all__ = [ 'SkipTest', 'assert_equal', 'assert_not_equal', 'assert_raises', 'assert_raises_regex', 'assert_regex', 'assert_true', 'assert_warns_regex', 'fork_isolation', 'run', 'tempdir', ] # vim:ts=4 sts=4 sw=4 et python-afl-0.6.1/tests/test_showmap.py0000644000000000000000000000411113070536456020034 0ustar00rootroot00000000000000# encoding=UTF-8 # Copyright © 2015-2017 Jakub Wilk # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the “Software”), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import os import sys from .tools import ( assert_equal, assert_not_equal, run, tempdir, ) here = os.path.dirname(__file__) target = here + '/target.py' def run_afl_showmap(stdin, xstdout=None, xstatus=0): with tempdir() as workdir: outpath = workdir + '/out' (stdout, stderr) = run( ['py-afl-showmap', '-o', outpath, sys.executable, target], stdin=stdin, xstatus=xstatus, ) del stderr # make pylint happy if xstdout is not None: assert_equal(stdout, xstdout) with open(outpath, 'r') as file: return file.read() def test_diff(): out1 = run_afl_showmap(b'0', xstdout=b'Looks like a zero to me!\n') out2 = run_afl_showmap(b'1', xstdout=b'A non-zero value? How quaint!\n') assert_not_equal(out1, out2) def test_exception(): out = run_afl_showmap(b'\xff', xstatus=2, ) assert_not_equal(out, b'') # vim:ts=4 sts=4 sw=4 et python-afl-0.6.1/tests/test_version.py0000644000000000000000000000315413071005022020026 0ustar00rootroot00000000000000# encoding=UTF-8 # Copyright © 2015-2017 Jakub Wilk # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the “Software”), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import os import afl from .tools import ( assert_equal ) here = os.path.dirname(__file__) docdir = here + '/../doc' def uopen(path): if str is not bytes: return open(path, 'rt', encoding='UTF-8') else: return open(path, 'rt') def test_changelog(): path = docdir + '/changelog' with uopen(path) as file: line = file.readline() changelog_version = line.split()[1].strip('()') assert_equal(changelog_version, afl.__version__) # vim:ts=4 sts=4 sw=4 et python-afl-0.6.1/tests/target.py0000644000000000000000000000052713070775323016612 0ustar00rootroot00000000000000import sys import afl def main(): s = sys.stdin.read() if len(s) < 1: print('Hum?') sys.exit(1) s.encode('ASCII') if s[0] == '0': print('Looks like a zero to me!') else: print('A non-zero value? How quaint!') if __name__ == '__main__': afl.init() main() # vim:ts=4 sts=4 sw=4 et python-afl-0.6.1/tests/test_fuzz.py0000644000000000000000000001332013070533202017340 0ustar00rootroot00000000000000# encoding=UTF-8 # Copyright © 2015-2017 Jakub Wilk # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the “Software”), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from __future__ import print_function import base64 import contextlib import distutils.version import glob import os import re import signal import subprocess as ipc import sys import time import warnings try: # Python >= 3.3 from shlex import quote as shell_quote except ImportError: # Python << 3.3 from pipes import quote as shell_quote from .tools import ( SkipTest, assert_true, clean_environ, tempdir, ) here = os.path.dirname(__file__) token = base64.b64encode(os.urandom(8)) if not isinstance(token, str): token = token.decode('ASCII') def get_afl_version(): child = ipc.Popen(['afl-fuzz'], stdout=ipc.PIPE) version = child.stdout.readline() child.stdout.close() child.wait() if str is not bytes: version = version.decode('ASCII') version = re.sub(r'\x1b\[[^m]+m', '', version) match = re.match(r'^afl-fuzz\s+([0-9.]+)b?\b', version) version = match.group(1) return distutils.version.StrictVersion(version) def sleep(n): time.sleep(n) return n def check_core_pattern(): with open('/proc/sys/kernel/core_pattern', 'rb') as file: pattern = file.read() if str is not bytes: pattern = pattern.decode('ASCII', 'replace') pattern = pattern.rstrip('\n') if pattern.startswith('|'): raise SkipTest('/proc/sys/kernel/core_pattern = ' + pattern) def _test_fuzz(workdir, target, dumb=False): input_dir = workdir + '/in' output_dir = workdir + '/out' os.mkdir(input_dir) os.mkdir(output_dir) with open(input_dir + '/in', 'w') as file: file.write('0') crash_dir = output_dir + '/crashes' queue_dir = output_dir + '/queue' have_crash = False have_paths = False n_paths = 0 with open('/dev/null', 'wb') as devnull: with open(workdir + '/stdout', 'wb') as stdout: cmdline = ['py-afl-fuzz', '-i', input_dir, '-o', output_dir, '--', sys.executable, target, token] if dumb: cmdline[1:1] = ['-n'] print('$ ' + ' '.join(shell_quote(arg) for arg in cmdline)) afl = ipc.Popen( cmdline, stdout=stdout, stdin=devnull, preexec_fn=clean_environ, ) try: timeout = 10 while timeout > 0: if afl.poll() is not None: break have_crash = len(glob.glob(crash_dir + '/id:*')) >= 1 n_paths = len(glob.glob(queue_dir + '/id:*')) have_paths = (n_paths == 1) if dumb else (n_paths >= 2) if have_crash and have_paths: break timeout -= sleep(0.1) if afl.returncode is None: afl.terminate() afl.wait() except: afl.kill() raise with open(workdir + '/stdout', 'rb') as file: stdout = file.read() if str is not bytes: stdout = stdout.decode('ASCII', 'replace') print(stdout) if not have_crash and '/proc/sys/kernel/core_pattern' in stdout: check_core_pattern() assert_true(have_crash, "target program didn't crash") assert_true(have_paths, 'target program produced {n} distinct paths'.format(n=n_paths)) @contextlib.contextmanager def stray_process_cleanup(): # afl-fuzz doesn't always kill the target process: # https://groups.google.com/d/topic/afl-users/E37s4YDti7o try: yield finally: ps = ipc.Popen(['ps', 'ax'], stdout=ipc.PIPE) strays = [] for line in ps.stdout: if not isinstance(line, str): line = line.decode('ASCII', 'replace') if token in line: strays += [line] if strays: warnings.warn('stray process{es} left behind:\n{ps}'.format( es=('' if len(strays) == 1 else 'es'), ps=''.join(strays) ), category=RuntimeWarning) for line in strays: pid = int(line.split()[0]) os.kill(pid, signal.SIGKILL) ps.wait() def test_fuzz(dumb=False): def t(target): with stray_process_cleanup(): with tempdir() as workdir: _test_fuzz( workdir=workdir, target=os.path.join(here, target), dumb=dumb, ) yield t, 'target.py' yield t, 'target_persistent.py' def test_fuzz_dumb(): if get_afl_version() < '1.95': def skip(): raise SkipTest('afl-fuzz >= 1.95b is required') else: skip = False for t in test_fuzz(dumb=True): yield skip or t # vim:ts=4 sts=4 sw=4 et python-afl-0.6.1/tests/test_loop.py0000644000000000000000000000455613071005022017321 0ustar00rootroot00000000000000# encoding=UTF-8 # Copyright © 2015-2017 Jakub Wilk # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the “Software”), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import os import signal import afl from .tools import ( assert_equal, assert_raises_regex, fork_isolation, ) def test_persistent(): _test_persistent(None) _test_persistent(1, 1) _test_persistent(1, max=1) _test_persistent(42, 42) _test_persistent(42, max=42) @fork_isolation def _test_persistent(n, *args, **kwargs): os.environ['PYTHON_AFL_PERSISTENT'] = '1' n_max = 1000 k = [0] def kill(pid, sig): assert_equal(pid, os.getpid()) assert_equal(sig, signal.SIGSTOP) k[0] += 1 os.kill = kill x = 0 while afl.loop(*args, **kwargs): x += 1 if x == n_max: break if n is None: n = n_max assert_equal(x, n) assert_equal(k[0], n - 1) def test_docile(): _test_docile() _test_docile(1) _test_docile(max=1) _test_docile(42) _test_docile(max=42) @fork_isolation def _test_docile(*args, **kwargs): os.environ.pop('PYTHON_AFL_PERSISTENT', None) x = 0 while afl.loop(*args, **kwargs): x += 1 assert_equal(x, 1) @fork_isolation def test_double_init(): afl.init() with assert_raises_regex(RuntimeError, '^AFL already initialized$'): while afl.loop(): pass # vim:ts=4 sts=4 sw=4 et python-afl-0.6.1/tests/test_import.py0000644000000000000000000000321013071152100017644 0ustar00rootroot00000000000000# encoding=UTF-8 # Copyright © 2015-2017 Jakub Wilk # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the “Software”), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import afl from .tools import ( assert_equal, ) exports = [ 'init', 'loop', ] deprecated = [ 'start', ] def wildcard_import(mod): ns = {} exec('from {mod} import *'.format(mod=mod), {}, ns) return ns def test_wildcard_import(): ns = wildcard_import('afl') assert_equal( sorted(ns.keys()), sorted(exports) ) def test_dir(): assert_equal( sorted(o for o in dir(afl) if not o.startswith('_')), sorted(exports + deprecated) ) # vim:ts=4 sts=4 sw=4 et python-afl-0.6.1/tests/test_tmin.py0000644000000000000000000000357413070750522017331 0ustar00rootroot00000000000000# encoding=UTF-8 # Copyright © 2015-2017 Jakub Wilk # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the “Software”), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import os import sys from .tools import ( assert_equal, run, tempdir, ) here = os.path.dirname(__file__) target = here + '/target.py' def run_afl_tmin(input, xoutput, xstatus=0): with tempdir() as workdir: inpath = workdir + '/in' with open(inpath, 'wb') as file: file.write(input) outpath = workdir + '/out' run( ['py-afl-tmin', '-i', inpath, '-o', outpath, '--', sys.executable, target], xstatus=xstatus, ) with open(outpath, 'rb') as file: output = file.read() assert_equal(output, xoutput) def test(): run_afl_tmin(b'0' * 6, b'0') run_afl_tmin(b'X' * 7, b'X') def test_exc(): run_afl_tmin(b'\xcf\x87', b'\x87') # vim:ts=4 sts=4 sw=4 et python-afl-0.6.1/tests/test_cmin.py0000644000000000000000000000502313070751144017300 0ustar00rootroot00000000000000# encoding=UTF-8 # Copyright © 2015-2017 Jakub Wilk # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the “Software”), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import os import sys from .tools import ( assert_equal, run, tempdir, ) here = os.path.dirname(__file__) target = here + '/target.py' def run_afl_cmin(input, xoutput, crashes_only=False): input = sorted(input) xoutput = sorted(xoutput) with tempdir() as workdir: indir = '//{dir}/in'.format(dir=workdir) outdir = '//{dir}/out'.format(dir=workdir) for dir in [indir, outdir]: os.mkdir(dir) for n, blob in enumerate(input): path = '{dir}/{n}'.format(dir=indir, n=n) with open(path, 'wb') as file: file.write(blob) cmdline = ['py-afl-cmin', '-i', indir, '-o', outdir, '--', sys.executable, target] if crashes_only: cmdline[1:1] = ['-C'] print(cmdline) run(cmdline) output = [] for n in os.listdir(outdir): path = '{dir}/{n}'.format(dir=outdir, n=n) with open(path, 'rb') as file: blob = file.read() output += [blob] output.sort() assert_equal(xoutput, output) def test(): run_afl_cmin([ b'0' * 6, b'0', b'X' * 7, b'1', b'\xcf\x87', ], [ b'0', b'1', ]) def test_crashes_only(): run_afl_cmin([ b'0' * 6, b'0', b'X' * 7, b'1', b'\xcf\x87', ], [ b'\xcf\x87', ], crashes_only=True) # vim:ts=4 sts=4 sw=4 et python-afl-0.6.1/tests/__init__.py0000644000000000000000000000000013065453571017047 0ustar00rootroot00000000000000python-afl-0.6.1/tests/test_hash.py0000644000000000000000000000300213071003537017265 0ustar00rootroot00000000000000# encoding=UTF-8 # Copyright © 2015 Jakub Wilk # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the “Software”), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import afl from .tools import ( assert_equal, ) def test_hash(): h = afl._hash # pylint: disable=protected-access assert_equal(h('', 0), 2166136261) assert_equal(h('', 42), 789356349) assert_equal(h('moo', 23), 3934561083) assert_equal(h('moo', 37), 3162790609) assert_equal(h('wół', 23), 2298935884) assert_equal(h('wół', 37), 3137816834) # vim:ts=4 sts=4 sw=4 et python-afl-0.6.1/tests/test_init.py0000644000000000000000000000317013071005022017302 0ustar00rootroot00000000000000# encoding=UTF-8 # Copyright © 2015-2017 Jakub Wilk # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the “Software”), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import re import afl from .tools import ( assert_raises_regex, assert_warns_regex, fork_isolation, ) @fork_isolation def test_deprecated_start(): msg = 'afl.start() is deprecated, use afl.init() instead' msg_re = '^{0}$'.format(re.escape(msg)) with assert_warns_regex(DeprecationWarning, msg_re): afl.start() @fork_isolation def test_double_init(): afl.init() with assert_raises_regex(RuntimeError, '^AFL already initialized$'): afl.init() # vim:ts=4 sts=4 sw=4 et python-afl-0.6.1/setup.py0000644000000000000000000001002413075714331015310 0ustar00rootroot00000000000000# encoding=UTF-8 # Copyright © 2014-2017 Jakub Wilk # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the “Software”), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. ''' *python-afl* is an experimental module that enables `American fuzzy lop`_ fork server and instrumentation for pure-Python code. .. _American fuzzy lop: http://lcamtuf.coredump.cx/afl/ ''' import glob import io import os import sys import distutils.core import distutils.version from distutils.command.sdist import sdist as distutils_sdist try: import distutils644 except ImportError: pass else: distutils644.install() b = b'' # Python >= 2.6 is required def get_version(): with io.open('doc/changelog', encoding='UTF-8') as file: line = file.readline() return line.split()[1].strip('()') classifiers = ''' Development Status :: 3 - Alpha Intended Audience :: Developers License :: OSI Approved :: MIT License Operating System :: POSIX Programming Language :: Cython Programming Language :: Python :: 2 Programming Language :: Python :: 3 Topic :: Software Development :: Quality Assurance Topic :: Software Development :: Testing '''.strip().splitlines() meta = dict( name='python-afl', version=get_version(), license='MIT', description='American fuzzy lop fork server and instrumentation for pure-Python code', long_description=__doc__.strip(), classifiers=classifiers, url='http://jwilk.net/software/python-afl', author='Jakub Wilk', author_email='jwilk@jwilk.net', ) if 'setuptools' in sys.modules and sys.argv[1] == 'egg_info': # We wouldn't normally want setuptools; but pip forces it upon us anyway, # so let's abuse it to instruct pip to install Cython if it's missing. distutils.core.setup( install_requires=['Cython>=0.19'], # Conceptually, “setup_requires” would make more sense than # “install_requires”, but the former is not supported by pip: # https://github.com/pypa/pip/issues/1820 **meta ) sys.exit(0) try: import Cython except ImportError: raise RuntimeError('Cython >= 0.19 is required') try: cython_version = Cython.__version__ except AttributeError: # Cython prior to 0.14 didn't have __version__. # Oh well. We don't support such old versions anyway. cython_version = '0' cython_version = distutils.version.LooseVersion(cython_version) if cython_version < '0.19': raise RuntimeError('Cython >= 0.19 is required') import Cython.Build # pylint: disable=wrong-import-position class cmd_sdist(distutils_sdist): def maybe_move_file(self, base_dir, src, dst): src = os.path.join(base_dir, src) dst = os.path.join(base_dir, dst) if os.path.exists(src): self.move_file(src, dst) def make_release_tree(self, base_dir, files): distutils_sdist.make_release_tree(self, base_dir, files) self.maybe_move_file(base_dir, 'LICENSE', 'doc/LICENSE') distutils.core.setup( ext_modules=Cython.Build.cythonize('afl.pyx'), scripts=glob.glob('py-afl-*'), cmdclass=dict(sdist=cmd_sdist), **meta ) # vim:ts=4 sts=4 sw=4 et python-afl-0.6.1/py-afl-showmap0000755000000000000000000000042213065510071016363 0ustar00rootroot00000000000000#!/bin/sh export PYTHON_AFL_SIGNAL=${PYTHON_AFL_SIGNAL:-SIGUSR1} if ! command -v afl-showmap > /dev/null then cat >&2 < installed? EOF exit 127 fi exec afl-showmap "$@" # vim:ts=4 sts=4 sw=4 et python-afl-0.6.1/py-afl-cmin0000755000000000000000000000044513070751117015644 0ustar00rootroot00000000000000#!/bin/sh export AFL_SKIP_BIN_CHECK=1 export PYTHON_AFL_SIGNAL=${PYTHON_AFL_SIGNAL:-SIGUSR1} if ! command -v afl-cmin > /dev/null then cat >&2 < installed? EOF exit 127 fi exec afl-cmin "$@" # vim:ts=4 sts=4 sw=4 et python-afl-0.6.1/py-afl-fuzz0000755000000000000000000000110613065510073015705 0ustar00rootroot00000000000000#!/bin/sh export AFL_SKIP_CHECKS=1 # AFL << 1.20b export AFL_SKIP_BIN_CHECK=1 # AFL >= 1.20b export AFL_DUMB_FORKSRV=1 if [ -n "$PYTHON_AFL_DUMB" ] then # shellcheck disable=SC2016 printf '%s: $PYTHON_AFL_DUMB is deprecated; use -n instead\n' "$(basename "$0")" >&2 set -- -n "$@" fi export PYTHON_AFL_SIGNAL=${PYTHON_AFL_SIGNAL:-SIGUSR1} export PYTHON_AFL_PERSISTENT=1 if ! command -v afl-fuzz > /dev/null then cat >&2 < installed? EOF exit 127 fi exec afl-fuzz "$@" # vim:ts=4 sts=4 sw=4 et python-afl-0.6.1/PKG-INFO0000644000000000000000000000162513136647265014713 0ustar00rootroot00000000000000Metadata-Version: 1.1 Name: python-afl Version: 0.6.1 Summary: American fuzzy lop fork server and instrumentation for pure-Python code Home-page: http://jwilk.net/software/python-afl Author: Jakub Wilk Author-email: jwilk@jwilk.net License: MIT Description: *python-afl* is an experimental module that enables `American fuzzy lop`_ fork server and instrumentation for pure-Python code. .. _American fuzzy lop: http://lcamtuf.coredump.cx/afl/ Platform: UNKNOWN Classifier: Development Status :: 3 - Alpha Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: MIT License Classifier: Operating System :: POSIX Classifier: Programming Language :: Cython Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 3 Classifier: Topic :: Software Development :: Quality Assurance Classifier: Topic :: Software Development :: Testing python-afl-0.6.1/private/0000755000000000000000000000000013136647265015264 5ustar00rootroot00000000000000python-afl-0.6.1/private/update-version0000755000000000000000000000026613121250156020142 0ustar00rootroot00000000000000#!/bin/sh version=${1:?"no version number provided"} set -e set -x dch -m -v "$version" -u low -c doc/changelog sed -i -E -e "s/^(__version__) = '[0-9.]+'$/\1 = '$version'/" afl.pyx python-afl-0.6.1/private/check-rst0000755000000000000000000000274013070537622017070 0ustar00rootroot00000000000000#!/bin/sh # Copyright © 2016-2017 Jakub Wilk # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the “Software”), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. here=$(dirname "$0") rst2xml=$(command -v rst2xml) \ || rst2xml=$(command -v rst2xml.py) \ || { printf 'rst2xml not found\n' >&2; exit 1; } options='--input-encoding=UTF-8 --output-encoding=UTF-8 --strict' if [ $# -eq 0 ] then grep -rwl 'ft[=]rst' "$here/.." else printf '%s\n' "$@" fi | xargs -L1 -t -I{} "$rst2xml" $options {} /dev/null # vim:ts=4 sts=4 sw=4 et python-afl-0.6.1/private/run-pydiatra0000755000000000000000000000237013073772740017630 0ustar00rootroot00000000000000#!/bin/sh # Copyright © 2016-2017 Jakub Wilk # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the “Software”), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. PYTHON=${PYTHON:-python} if [ $# -eq 0 ] then set -- $(find . -name '*.py') fi exec "$PYTHON" -m pydiatra "$@" # vim:ts=4 sts=4 sw=4 et python-afl-0.6.1/private/build-and-test0000755000000000000000000000403713071152052020011 0ustar00rootroot00000000000000#!/bin/sh # Copyright © 2015-2017 Jakub Wilk # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the “Software”), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. usage() { printf 'Usage: %s [--no-build] [pythonX.Y...]\n' "$0" } args=$(getopt -n "$0" -o 'hj:' --long 'help,jobs:,no-build' -- "$@") if [ $? -ne 0 ] then usage >&2 exit 1 fi eval set -- "$args" opt_jobs=$(nproc) || opt_jobs=1 opt_build=y while true do case "$1" in -h|--help) usage; exit 0;; -j|--jobs) opt_jobs=$2; shift 2;; --no-build) opt_build=; shift;; --) shift; break;; *) printf '%s: internal error (%s)\n' "$0" "$1" >&2; exit 1;; esac done set -e [ $# = 0 ] && set -- python [ -z $opt_build ] || printf '%s\n' "$@" \ | xargs -P"$opt_jobs" -t -I'{python}' env '{python}' setup.py build --build-lib 'build/{python}' cd tests nosetests=$(command -v nosetests) || { echo nosetests not found >&2; exit 1; } export PATH="$PWD/..:$PATH" printf '%s\n' "$@" \ | xargs -t -I'{python}' env PYTHONPATH="$PWD/../build/{python}" '{python}' "$nosetests" --verbose # vim:ts=4 sts=4 sw=4 et python-afl-0.6.1/private/run-pylint0000755000000000000000000000370413071005022017310 0ustar00rootroot00000000000000#!/bin/sh # Copyright © 2015-2017 Jakub Wilk # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the “Software”), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. set -e -u PYTHON=${PYTHON:-python} [ -n "${TRAVIS_PYTHON_VERSION:-}" ] && PYTHON=python "$PYTHON" -m pylint --version >/dev/null || exit 1 if [ $# -eq 0 ] then set -- setup.py tests/*.py fi if [ -n "${TRAVIS_PYTHON_VERSION:-}" ] then # https://github.com/PyCQA/pylint/issues/73 set -- --ignored-modules=distutils "$@" fi set -- --load-plugins=pylint.extensions.check_elif "$@" log=$(mktemp -t pylint.XXXXXX) "$PYTHON" -m pylint "$@" > "$log" || [ $? != 1 ] ! grep '^\w:' "$log" \ | grep -v -P ": redefined-builtin \\[.*\\] Redefining built-in '(file|dir|input)'$" \ | grep -v -P ": redundant-unittest-assert \\[.*\\] Redundant use of assertTrue with constant value False$" \ | grep -v -P ": superfluous-parens \\[.*\\] Unnecessary parens after u?'print' keyword$" \ | LC_ALL=C sort -k2 \ | grep '.' || exit 1 rm "$log" # vim:ts=4 sts=4 sw=4 et python-afl-0.6.1/private/run-pyflakes0000755000000000000000000000264413070772475017637 0ustar00rootroot00000000000000#!/bin/sh # Copyright © 2016-2017 Jakub Wilk # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the “Software”), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. PYTHON=${PYTHON:-python} pyflakes=$(command -v pyflakes) || { echo pyflakes not found >&2; exit 1; } if [ $# -eq 0 ] then set -- $(find . -name '*.py') fi # It would be tempting to use "python -m pyflakes" here, # but that doesn't work in Python 2.6. exec "$PYTHON" "$pyflakes" "$@" # vim:ts=4 sts=4 sw=4 et