diff --git a/tests/image-fuzzer/qcow2/fuzz.py b/tests/image-fuzzer/qcow2/fuzz.py new file mode 100644 index 0000000000..a53c84fc4e --- /dev/null +++ b/tests/image-fuzzer/qcow2/fuzz.py @@ -0,0 +1,327 @@ +# Fuzzing functions for qcow2 fields +# +# Copyright (C) 2014 Maria Kustova +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# + +import random + + +UINT8 = 0xff +UINT32 = 0xffffffff +UINT64 = 0xffffffffffffffff +# Most significant bit orders +UINT32_M = 31 +UINT64_M = 63 +# Fuzz vectors +UINT8_V = [0, 0x10, UINT8/4, UINT8/2 - 1, UINT8/2, UINT8/2 + 1, UINT8 - 1, + UINT8] +UINT32_V = [0, 0x100, 0x1000, 0x10000, 0x100000, UINT32/4, UINT32/2 - 1, + UINT32/2, UINT32/2 + 1, UINT32 - 1, UINT32] +UINT64_V = UINT32_V + [0x1000000, 0x10000000, 0x100000000, UINT64/4, + UINT64/2 - 1, UINT64/2, UINT64/2 + 1, UINT64 - 1, + UINT64] +STRING_V = ['%s%p%x%d', '.1024d', '%.2049d', '%p%p%p%p', '%x%x%x%x', + '%d%d%d%d', '%s%s%s%s', '%99999999999s', '%08x', '%%20d', '%%20n', + '%%20x', '%%20s', '%s%s%s%s%s%s%s%s%s%s', '%p%p%p%p%p%p%p%p%p%p', + '%#0123456x%08x%x%s%p%d%n%o%u%c%h%l%q%j%z%Z%t%i%e%g%f%a%C%S%08x%%', + '%s x 129', '%x x 257'] + + +def random_from_intervals(intervals): + """Select a random integer number from the list of specified intervals. + + Each interval is a tuple of lower and upper limits of the interval. The + limits are included. Intervals in a list should not overlap. + """ + total = reduce(lambda x, y: x + y[1] - y[0] + 1, intervals, 0) + r = random.randint(0, total - 1) + intervals[0][0] + for x in zip(intervals, intervals[1:]): + r = r + (r > x[0][1]) * (x[1][0] - x[0][1] - 1) + return r + + +def random_bits(bit_ranges): + """Generate random binary mask with ones in the specified bit ranges. + + Each bit_ranges is a list of tuples of lower and upper limits of bit + positions will be fuzzed. The limits are included. Random amount of bits + in range limits will be set to ones. The mask is returned in decimal + integer format. + """ + bit_numbers = [] + # Select random amount of random positions in bit_ranges + for rng in bit_ranges: + bit_numbers += random.sample(range(rng[0], rng[1] + 1), + random.randint(0, rng[1] - rng[0] + 1)) + val = 0 + # Set bits on selected positions to ones + for bit in bit_numbers: + val |= 1 << bit + return val + + +def truncate_string(strings, length): + """Return strings truncated to specified length.""" + if type(strings) == list: + return [s[:length] for s in strings] + else: + return strings[:length] + + +def validator(current, pick, choices): + """Return a value not equal to the current selected by the pick + function from choices. + """ + while True: + val = pick(choices) + if not val == current: + return val + + +def int_validator(current, intervals): + """Return a random value from intervals not equal to the current. + + This function is useful for selection from valid values except current one. + """ + return validator(current, random_from_intervals, intervals) + + +def bit_validator(current, bit_ranges): + """Return a random bit mask not equal to the current. + + This function is useful for selection from valid values except current one. + """ + return validator(current, random_bits, bit_ranges) + + +def string_validator(current, strings): + """Return a random string value from the list not equal to the current. + + This function is useful for selection from valid values except current one. + """ + return validator(current, random.choice, strings) + + +def selector(current, constraints, validate=int_validator): + """Select one value from all defined by constraints. + + Each constraint produces one random value satisfying to it. The function + randomly selects one value satisfying at least one constraint (depending on + constraints overlaps). + """ + def iter_validate(c): + """Apply validate() only to constraints represented as lists. + + This auxiliary function replaces short circuit conditions not supported + in Python 2.4 + """ + if type(c) == list: + return validate(current, c) + else: + return c + + fuzz_values = [iter_validate(c) for c in constraints] + # Remove current for cases it's implicitly specified in constraints + # Duplicate validator functionality to prevent decreasing of probability + # to get one of allowable values + # TODO: remove validators after implementation of intelligent selection + # of fields will be fuzzed + try: + fuzz_values.remove(current) + except ValueError: + pass + return random.choice(fuzz_values) + + +def magic(current): + """Fuzz magic header field. + + The function just returns the current magic value and provides uniformity + of calls for all fuzzing functions. + """ + return current + + +def version(current): + """Fuzz version header field.""" + constraints = UINT32_V + [ + [(2, 3)], # correct values + [(0, 1), (4, UINT32)] + ] + return selector(current, constraints) + + +def backing_file_offset(current): + """Fuzz backing file offset header field.""" + constraints = UINT64_V + return selector(current, constraints) + + +def backing_file_size(current): + """Fuzz backing file size header field.""" + constraints = UINT32_V + return selector(current, constraints) + + +def cluster_bits(current): + """Fuzz cluster bits header field.""" + constraints = UINT32_V + [ + [(9, 20)], # correct values + [(0, 9), (20, UINT32)] + ] + return selector(current, constraints) + + +def size(current): + """Fuzz image size header field.""" + constraints = UINT64_V + return selector(current, constraints) + + +def crypt_method(current): + """Fuzz crypt method header field.""" + constraints = UINT32_V + [ + 1, + [(2, UINT32)] + ] + return selector(current, constraints) + + +def l1_size(current): + """Fuzz L1 table size header field.""" + constraints = UINT32_V + return selector(current, constraints) + + +def l1_table_offset(current): + """Fuzz L1 table offset header field.""" + constraints = UINT64_V + return selector(current, constraints) + + +def refcount_table_offset(current): + """Fuzz refcount table offset header field.""" + constraints = UINT64_V + return selector(current, constraints) + + +def refcount_table_clusters(current): + """Fuzz refcount table clusters header field.""" + constraints = UINT32_V + return selector(current, constraints) + + +def nb_snapshots(current): + """Fuzz number of snapshots header field.""" + constraints = UINT32_V + return selector(current, constraints) + + +def snapshots_offset(current): + """Fuzz snapshots offset header field.""" + constraints = UINT64_V + return selector(current, constraints) + + +def incompatible_features(current): + """Fuzz incompatible features header field.""" + constraints = [ + [(0, 1)], # allowable values + [(0, UINT64_M)] + ] + return selector(current, constraints, bit_validator) + + +def compatible_features(current): + """Fuzz compatible features header field.""" + constraints = [ + [(0, UINT64_M)] + ] + return selector(current, constraints, bit_validator) + + +def autoclear_features(current): + """Fuzz autoclear features header field.""" + constraints = [ + [(0, UINT64_M)] + ] + return selector(current, constraints, bit_validator) + + +def refcount_order(current): + """Fuzz number of refcount order header field.""" + constraints = UINT32_V + return selector(current, constraints) + + +def header_length(current): + """Fuzz number of refcount order header field.""" + constraints = UINT32_V + [ + 72, + 104, + [(0, UINT32)] + ] + return selector(current, constraints) + + +def bf_name(current): + """Fuzz the backing file name.""" + constraints = [ + truncate_string(STRING_V, len(current)) + ] + return selector(current, constraints, string_validator) + + +def ext_magic(current): + """Fuzz magic field of a header extension.""" + constraints = UINT32_V + return selector(current, constraints) + + +def ext_length(current): + """Fuzz length field of a header extension.""" + constraints = UINT32_V + return selector(current, constraints) + + +def bf_format(current): + """Fuzz backing file format in the corresponding header extension.""" + constraints = [ + truncate_string(STRING_V, len(current)), + truncate_string(STRING_V, (len(current) + 7) & ~7) # Fuzz padding + ] + return selector(current, constraints, string_validator) + + +def feature_type(current): + """Fuzz feature type field of a feature name table header extension.""" + constraints = UINT8_V + return selector(current, constraints) + + +def feature_bit_number(current): + """Fuzz bit number field of a feature name table header extension.""" + constraints = UINT8_V + return selector(current, constraints) + + +def feature_name(current): + """Fuzz feature name field of a feature name table header extension.""" + constraints = [ + truncate_string(STRING_V, len(current)), + truncate_string(STRING_V, 46) # Fuzz padding (field length = 46) + ] + return selector(current, constraints, string_validator)