blob: 08bfea6739a3c850030b983ea3477b14d3a7b0aa [file] [log] [blame]
 # Copyright 2016 The Chromium Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. import base64 import functools import itertools import os import random import re import string import sys import textwrap from . import utils def FuzzyInt(n): """Returns an integer derived from the input by one of several mutations.""" int_sizes = [8, 16, 32, 64, 128] mutations = [ lambda n: utils.UniformExpoInteger(0, sys.maxint.bit_length() + 1), lambda n: -utils.UniformExpoInteger(0, sys.maxint.bit_length()), lambda n: 2 ** random.choice(int_sizes) - 1, lambda n: 2 ** random.choice(int_sizes), lambda n: 0, lambda n: -n, lambda n: n + 1, lambda n: n - 1, lambda n: n + random.randint(-1024, 1024), ] return random.choice(mutations)(n) def FuzzyString(s): """Returns a string derived from the input by one of several mutations.""" # First try some mutations that try to recognize certain types of strings try: s.decode("utf-8") # These mutations only make sense for textual data except UnicodeDecodeError: pass else: chained_mutations = [ FuzzIntsInString, FuzzBase64InString, FuzzListInString, ] original = s for mutation in chained_mutations: s = mutation(s) # Stop if we've modified the string and our coin comes up heads if s != original and random.getrandbits(1): return s # If we're still here, apply a more generic mutation mutations = [ lambda s: "".join(random.choice(string.printable) for i in xrange(utils.UniformExpoInteger(0, 14))), lambda s: "".join(unichr(random.randint(0, sys.maxunicode)) for i in xrange(utils.UniformExpoInteger(0, 14))).encode("utf-8"), lambda s: os.urandom(utils.UniformExpoInteger(0, 14)), lambda s: s * utils.UniformExpoInteger(1, 5), lambda s: s + "A" * utils.UniformExpoInteger(0, 14), lambda s: "A" * utils.UniformExpoInteger(0, 14) + s, lambda s: s[:-random.randint(1, max(1, len(s) - 1))], lambda s: textwrap.fill(s, random.randint(1, max(1, len(s) - 1))), lambda s: "", ] return random.choice(mutations)(s) def FuzzIntsInString(s): """Returns a string where some integers have been fuzzed with FuzzyInt.""" def ReplaceInt(m): val = m.group() if random.getrandbits(1): # Flip a coin to decide whether to fuzz return val if not random.getrandbits(4): # Delete the integer 1/16th of the time return "" decimal = val.isdigit() # Assume decimal digits means a decimal number n = FuzzyInt(int(val) if decimal else int(val, 16)) return str(n) if decimal else "%x" % n return re.sub(r"\b[a-fA-F]*\d[0-9a-fA-F]*\b", ReplaceInt, s) def FuzzBase64InString(s): """Returns a string where Base64 components are fuzzed with FuzzyBuffer.""" def ReplaceBase64(m): fb = FuzzyBuffer(base64.b64decode(m.group())) fb.RandomMutation() return base64.b64encode(fb) # This only matches obvious Base64 words with trailing equals signs return re.sub(r"(?