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