xref: /qemu/tests/image-fuzzer/qcow2/fuzz.py (revision d072cdf3)
1# Fuzzing functions for qcow2 fields
2#
3# Copyright (C) 2014 Maria Kustova <maria.k@catit.be>
4#
5# This program is free software: you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation, either version 2 of the License, or
8# (at your option) any later version.
9#
10# This program is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with this program.  If not, see <http://www.gnu.org/licenses/>.
17#
18
19import random
20
21
22UINT8 = 0xff
23UINT32 = 0xffffffff
24UINT64 = 0xffffffffffffffff
25# Most significant bit orders
26UINT32_M = 31
27UINT64_M = 63
28# Fuzz vectors
29UINT8_V = [0, 0x10, UINT8/4, UINT8/2 - 1, UINT8/2, UINT8/2 + 1, UINT8 - 1,
30           UINT8]
31UINT32_V = [0, 0x100, 0x1000, 0x10000, 0x100000, UINT32/4, UINT32/2 - 1,
32            UINT32/2, UINT32/2 + 1, UINT32 - 1, UINT32]
33UINT64_V = UINT32_V + [0x1000000, 0x10000000, 0x100000000, UINT64/4,
34                       UINT64/2 - 1, UINT64/2, UINT64/2 + 1, UINT64 - 1,
35                       UINT64]
36STRING_V = ['%s%p%x%d', '.1024d', '%.2049d', '%p%p%p%p', '%x%x%x%x',
37            '%d%d%d%d', '%s%s%s%s', '%99999999999s', '%08x', '%%20d', '%%20n',
38            '%%20x', '%%20s', '%s%s%s%s%s%s%s%s%s%s', '%p%p%p%p%p%p%p%p%p%p',
39            '%#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%%',
40            '%s x 129', '%x x 257']
41
42
43def random_from_intervals(intervals):
44    """Select a random integer number from the list of specified intervals.
45
46    Each interval is a tuple of lower and upper limits of the interval. The
47    limits are included. Intervals in a list should not overlap.
48    """
49    total = reduce(lambda x, y: x + y[1] - y[0] + 1, intervals, 0)
50    r = random.randint(0, total - 1) + intervals[0][0]
51    for x in zip(intervals, intervals[1:]):
52        r = r + (r > x[0][1]) * (x[1][0] - x[0][1] - 1)
53    return r
54
55
56def random_bits(bit_ranges):
57    """Generate random binary mask with ones in the specified bit ranges.
58
59    Each bit_ranges is a list of tuples of lower and upper limits of bit
60    positions will be fuzzed. The limits are included. Random amount of bits
61    in range limits will be set to ones. The mask is returned in decimal
62    integer format.
63    """
64    bit_numbers = []
65    # Select random amount of random positions in bit_ranges
66    for rng in bit_ranges:
67        bit_numbers += random.sample(range(rng[0], rng[1] + 1),
68                                     random.randint(0, rng[1] - rng[0] + 1))
69    val = 0
70    # Set bits on selected positions to ones
71    for bit in bit_numbers:
72        val |= 1 << bit
73    return val
74
75
76def truncate_string(strings, length):
77    """Return strings truncated to specified length."""
78    if type(strings) == list:
79        return [s[:length] for s in strings]
80    else:
81        return strings[:length]
82
83
84def validator(current, pick, choices):
85    """Return a value not equal to the current selected by the pick
86    function from choices.
87    """
88    while True:
89        val = pick(choices)
90        if not val == current:
91            return val
92
93
94def int_validator(current, intervals):
95    """Return a random value from intervals not equal to the current.
96
97    This function is useful for selection from valid values except current one.
98    """
99    return validator(current, random_from_intervals, intervals)
100
101
102def bit_validator(current, bit_ranges):
103    """Return a random bit mask not equal to the current.
104
105    This function is useful for selection from valid values except current one.
106    """
107    return validator(current, random_bits, bit_ranges)
108
109
110def string_validator(current, strings):
111    """Return a random string value from the list not equal to the current.
112
113    This function is useful for selection from valid values except current one.
114    """
115    return validator(current, random.choice, strings)
116
117
118def selector(current, constraints, validate=int_validator):
119    """Select one value from all defined by constraints.
120
121    Each constraint produces one random value satisfying to it. The function
122    randomly selects one value satisfying at least one constraint (depending on
123    constraints overlaps).
124    """
125    def iter_validate(c):
126        """Apply validate() only to constraints represented as lists.
127
128        This auxiliary function replaces short circuit conditions not supported
129        in Python 2.4
130        """
131        if type(c) == list:
132            return validate(current, c)
133        else:
134            return c
135
136    fuzz_values = [iter_validate(c) for c in constraints]
137    # Remove current for cases it's implicitly specified in constraints
138    # Duplicate validator functionality to prevent decreasing of probability
139    # to get one of allowable values
140    # TODO: remove validators after implementation of intelligent selection
141    # of fields will be fuzzed
142    try:
143        fuzz_values.remove(current)
144    except ValueError:
145        pass
146    return random.choice(fuzz_values)
147
148
149def magic(current):
150    """Fuzz magic header field.
151
152    The function just returns the current magic value and provides uniformity
153    of calls for all fuzzing functions.
154    """
155    return current
156
157
158def version(current):
159    """Fuzz version header field."""
160    constraints = UINT32_V + [
161        [(2, 3)],  # correct values
162        [(0, 1), (4, UINT32)]
163    ]
164    return selector(current, constraints)
165
166
167def backing_file_offset(current):
168    """Fuzz backing file offset header field."""
169    constraints = UINT64_V
170    return selector(current, constraints)
171
172
173def backing_file_size(current):
174    """Fuzz backing file size header field."""
175    constraints = UINT32_V
176    return selector(current, constraints)
177
178
179def cluster_bits(current):
180    """Fuzz cluster bits header field."""
181    constraints = UINT32_V + [
182        [(9, 20)],  # correct values
183        [(0, 9), (20, UINT32)]
184    ]
185    return selector(current, constraints)
186
187
188def size(current):
189    """Fuzz image size header field."""
190    constraints = UINT64_V
191    return selector(current, constraints)
192
193
194def crypt_method(current):
195    """Fuzz crypt method header field."""
196    constraints = UINT32_V + [
197        1,
198        [(2, UINT32)]
199    ]
200    return selector(current, constraints)
201
202
203def l1_size(current):
204    """Fuzz L1 table size header field."""
205    constraints = UINT32_V
206    return selector(current, constraints)
207
208
209def l1_table_offset(current):
210    """Fuzz L1 table offset header field."""
211    constraints = UINT64_V
212    return selector(current, constraints)
213
214
215def refcount_table_offset(current):
216    """Fuzz refcount table offset header field."""
217    constraints = UINT64_V
218    return selector(current, constraints)
219
220
221def refcount_table_clusters(current):
222    """Fuzz refcount table clusters header field."""
223    constraints = UINT32_V
224    return selector(current, constraints)
225
226
227def nb_snapshots(current):
228    """Fuzz number of snapshots header field."""
229    constraints = UINT32_V
230    return selector(current, constraints)
231
232
233def snapshots_offset(current):
234    """Fuzz snapshots offset header field."""
235    constraints = UINT64_V
236    return selector(current, constraints)
237
238
239def incompatible_features(current):
240    """Fuzz incompatible features header field."""
241    constraints = [
242        [(0, 1)],  # allowable values
243        [(0, UINT64_M)]
244    ]
245    return selector(current, constraints, bit_validator)
246
247
248def compatible_features(current):
249    """Fuzz compatible features header field."""
250    constraints = [
251        [(0, UINT64_M)]
252    ]
253    return selector(current, constraints, bit_validator)
254
255
256def autoclear_features(current):
257    """Fuzz autoclear features header field."""
258    constraints = [
259        [(0, UINT64_M)]
260    ]
261    return selector(current, constraints, bit_validator)
262
263
264def refcount_order(current):
265    """Fuzz number of refcount order header field."""
266    constraints = UINT32_V
267    return selector(current, constraints)
268
269
270def header_length(current):
271    """Fuzz number of refcount order header field."""
272    constraints = UINT32_V + [
273        72,
274        104,
275        [(0, UINT32)]
276    ]
277    return selector(current, constraints)
278
279
280def bf_name(current):
281    """Fuzz the backing file name."""
282    constraints = [
283        truncate_string(STRING_V, len(current))
284    ]
285    return selector(current, constraints, string_validator)
286
287
288def ext_magic(current):
289    """Fuzz magic field of a header extension."""
290    constraints = UINT32_V
291    return selector(current, constraints)
292
293
294def ext_length(current):
295    """Fuzz length field of a header extension."""
296    constraints = UINT32_V
297    return selector(current, constraints)
298
299
300def bf_format(current):
301    """Fuzz backing file format in the corresponding header extension."""
302    constraints = [
303        truncate_string(STRING_V, len(current)),
304        truncate_string(STRING_V, (len(current) + 7) & ~7)  # Fuzz padding
305    ]
306    return selector(current, constraints, string_validator)
307
308
309def feature_type(current):
310    """Fuzz feature type field of a feature name table header extension."""
311    constraints = UINT8_V
312    return selector(current, constraints)
313
314
315def feature_bit_number(current):
316    """Fuzz bit number field of a feature name table header extension."""
317    constraints = UINT8_V
318    return selector(current, constraints)
319
320
321def feature_name(current):
322    """Fuzz feature name field of a feature name table header extension."""
323    constraints = [
324        truncate_string(STRING_V, len(current)),
325        truncate_string(STRING_V, 46)  # Fuzz padding (field length = 46)
326    ]
327    return selector(current, constraints, string_validator)
328
329
330def l1_entry(current):
331    """Fuzz an entry of the L1 table."""
332    constraints = UINT64_V
333    # Reserved bits are ignored
334    # Added a possibility when only flags are fuzzed
335    offset = 0x7fffffffffffffff & random.choice([selector(current,
336                                                          constraints),
337                                                 current])
338    is_cow = random.randint(0, 1)
339    return offset + (is_cow << UINT64_M)
340
341
342def l2_entry(current):
343    """Fuzz an entry of an L2 table."""
344    constraints = UINT64_V
345    # Reserved bits are ignored
346    # Add a possibility when only flags are fuzzed
347    offset = 0x3ffffffffffffffe & random.choice([selector(current,
348                                                          constraints),
349                                                 current])
350    is_compressed = random.randint(0, 1)
351    is_cow = random.randint(0, 1)
352    is_zero = random.randint(0, 1)
353    value = offset + (is_cow << UINT64_M) + \
354            (is_compressed << UINT64_M - 1) + is_zero
355    return value
356