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