1e1232323SMaria Kustova# Generator of fuzzed qcow2 images 2e1232323SMaria Kustova# 3e1232323SMaria Kustova# Copyright (C) 2014 Maria Kustova <maria.k@catit.be> 4e1232323SMaria Kustova# 5e1232323SMaria Kustova# This program is free software: you can redistribute it and/or modify 6e1232323SMaria Kustova# it under the terms of the GNU General Public License as published by 7e1232323SMaria Kustova# the Free Software Foundation, either version 2 of the License, or 8e1232323SMaria Kustova# (at your option) any later version. 9e1232323SMaria Kustova# 10e1232323SMaria Kustova# This program is distributed in the hope that it will be useful, 11e1232323SMaria Kustova# but WITHOUT ANY WARRANTY; without even the implied warranty of 12e1232323SMaria Kustova# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13e1232323SMaria Kustova# GNU General Public License for more details. 14e1232323SMaria Kustova# 15e1232323SMaria Kustova# You should have received a copy of the GNU General Public License 16e1232323SMaria Kustova# along with this program. If not, see <http://www.gnu.org/licenses/>. 17e1232323SMaria Kustova# 18e1232323SMaria Kustova 19e1232323SMaria Kustovaimport random 20e1232323SMaria Kustovaimport struct 21e1232323SMaria Kustovaimport fuzz 22*38eb193bSMaria Kustovafrom math import ceil 23*38eb193bSMaria Kustovafrom os import urandom 24e1232323SMaria Kustova 25e1232323SMaria KustovaMAX_IMAGE_SIZE = 10 * (1 << 20) 26e1232323SMaria Kustova# Standard sizes 27e1232323SMaria KustovaUINT32_S = 4 28e1232323SMaria KustovaUINT64_S = 8 29e1232323SMaria Kustova 30e1232323SMaria Kustova 31e1232323SMaria Kustovaclass Field(object): 32e1232323SMaria Kustova 33e1232323SMaria Kustova """Atomic image element (field). 34e1232323SMaria Kustova 35e1232323SMaria Kustova The class represents an image field as quadruple of a data format 36e1232323SMaria Kustova of value necessary for its packing to binary form, an offset from 37e1232323SMaria Kustova the beginning of the image, a value and a name. 38e1232323SMaria Kustova 39e1232323SMaria Kustova The field can be iterated as a list [format, offset, value]. 40e1232323SMaria Kustova """ 41e1232323SMaria Kustova 42e1232323SMaria Kustova __slots__ = ('fmt', 'offset', 'value', 'name') 43e1232323SMaria Kustova 44e1232323SMaria Kustova def __init__(self, fmt, offset, val, name): 45e1232323SMaria Kustova self.fmt = fmt 46e1232323SMaria Kustova self.offset = offset 47e1232323SMaria Kustova self.value = val 48e1232323SMaria Kustova self.name = name 49e1232323SMaria Kustova 50e1232323SMaria Kustova def __iter__(self): 51e1232323SMaria Kustova return iter([self.fmt, self.offset, self.value]) 52e1232323SMaria Kustova 53e1232323SMaria Kustova def __repr__(self): 54e1232323SMaria Kustova return "Field(fmt='%s', offset=%d, value=%s, name=%s)" % \ 55e1232323SMaria Kustova (self.fmt, self.offset, str(self.value), self.name) 56e1232323SMaria Kustova 57e1232323SMaria Kustova 58e1232323SMaria Kustovaclass FieldsList(object): 59e1232323SMaria Kustova 60e1232323SMaria Kustova """List of fields. 61e1232323SMaria Kustova 62e1232323SMaria Kustova The class allows access to a field in the list by its name and joins 63e1232323SMaria Kustova several list in one via in-place addition. 64e1232323SMaria Kustova """ 65e1232323SMaria Kustova 66e1232323SMaria Kustova def __init__(self, meta_data=None): 67e1232323SMaria Kustova if meta_data is None: 68e1232323SMaria Kustova self.data = [] 69e1232323SMaria Kustova else: 70e1232323SMaria Kustova self.data = [Field(f[0], f[1], f[2], f[3]) 71e1232323SMaria Kustova for f in meta_data] 72e1232323SMaria Kustova 73e1232323SMaria Kustova def __getitem__(self, name): 74e1232323SMaria Kustova return [x for x in self.data if x.name == name] 75e1232323SMaria Kustova 76e1232323SMaria Kustova def __iter__(self): 77e1232323SMaria Kustova return iter(self.data) 78e1232323SMaria Kustova 79e1232323SMaria Kustova def __iadd__(self, other): 80e1232323SMaria Kustova self.data += other.data 81e1232323SMaria Kustova return self 82e1232323SMaria Kustova 83e1232323SMaria Kustova def __len__(self): 84e1232323SMaria Kustova return len(self.data) 85e1232323SMaria Kustova 86e1232323SMaria Kustova 87e1232323SMaria Kustovaclass Image(object): 88e1232323SMaria Kustova 89e1232323SMaria Kustova """ Qcow2 image object. 90e1232323SMaria Kustova 91e1232323SMaria Kustova This class allows to create qcow2 images with random valid structures and 92e1232323SMaria Kustova values, fuzz them via external qcow2.fuzz module and write the result to 93e1232323SMaria Kustova a file. 94e1232323SMaria Kustova """ 95e1232323SMaria Kustova 96e1232323SMaria Kustova @staticmethod 97e1232323SMaria Kustova def _size_params(): 98e1232323SMaria Kustova """Generate a random image size aligned to a random correct 99e1232323SMaria Kustova cluster size. 100e1232323SMaria Kustova """ 101e1232323SMaria Kustova cluster_bits = random.randrange(9, 21) 102e1232323SMaria Kustova cluster_size = 1 << cluster_bits 103e1232323SMaria Kustova img_size = random.randrange(0, MAX_IMAGE_SIZE + 1, cluster_size) 104e1232323SMaria Kustova return (cluster_bits, img_size) 105e1232323SMaria Kustova 106e1232323SMaria Kustova @staticmethod 107*38eb193bSMaria Kustova def _get_available_clusters(used, number): 108*38eb193bSMaria Kustova """Return a set of indices of not allocated clusters. 109*38eb193bSMaria Kustova 110*38eb193bSMaria Kustova 'used' contains indices of currently allocated clusters. 111*38eb193bSMaria Kustova All clusters that cannot be allocated between 'used' clusters will have 112*38eb193bSMaria Kustova indices appended to the end of 'used'. 113*38eb193bSMaria Kustova """ 114*38eb193bSMaria Kustova append_id = max(used) + 1 115*38eb193bSMaria Kustova free = set(range(1, append_id)) - used 116*38eb193bSMaria Kustova if len(free) >= number: 117*38eb193bSMaria Kustova return set(random.sample(free, number)) 118*38eb193bSMaria Kustova else: 119*38eb193bSMaria Kustova return free | set(range(append_id, append_id + number - len(free))) 120*38eb193bSMaria Kustova 121*38eb193bSMaria Kustova @staticmethod 122*38eb193bSMaria Kustova def _get_adjacent_clusters(used, size): 123*38eb193bSMaria Kustova """Return an index of the first cluster in the sequence of free ones. 124*38eb193bSMaria Kustova 125*38eb193bSMaria Kustova 'used' contains indices of currently allocated clusters. 'size' is the 126*38eb193bSMaria Kustova length of the sequence of free clusters. 127*38eb193bSMaria Kustova If the sequence of 'size' is not available between 'used' clusters, its 128*38eb193bSMaria Kustova first index will be append to the end of 'used'. 129*38eb193bSMaria Kustova """ 130*38eb193bSMaria Kustova def get_cluster_id(lst, length): 131*38eb193bSMaria Kustova """Return the first index of the sequence of the specified length 132*38eb193bSMaria Kustova or None if the sequence cannot be inserted in the list. 133*38eb193bSMaria Kustova """ 134*38eb193bSMaria Kustova if len(lst) != 0: 135*38eb193bSMaria Kustova pairs = [] 136*38eb193bSMaria Kustova pair = (lst[0], 1) 137*38eb193bSMaria Kustova for i in range(1, len(lst)): 138*38eb193bSMaria Kustova if lst[i] == lst[i-1] + 1: 139*38eb193bSMaria Kustova pair = (lst[i], pair[1] + 1) 140*38eb193bSMaria Kustova else: 141*38eb193bSMaria Kustova pairs.append(pair) 142*38eb193bSMaria Kustova pair = (lst[i], 1) 143*38eb193bSMaria Kustova pairs.append(pair) 144*38eb193bSMaria Kustova random.shuffle(pairs) 145*38eb193bSMaria Kustova for x, s in pairs: 146*38eb193bSMaria Kustova if s >= length: 147*38eb193bSMaria Kustova return x - length + 1 148*38eb193bSMaria Kustova return None 149*38eb193bSMaria Kustova 150*38eb193bSMaria Kustova append_id = max(used) + 1 151*38eb193bSMaria Kustova free = list(set(range(1, append_id)) - used) 152*38eb193bSMaria Kustova idx = get_cluster_id(free, size) 153*38eb193bSMaria Kustova if idx is None: 154*38eb193bSMaria Kustova return append_id 155*38eb193bSMaria Kustova else: 156*38eb193bSMaria Kustova return idx 157*38eb193bSMaria Kustova 158*38eb193bSMaria Kustova @staticmethod 159*38eb193bSMaria Kustova def _alloc_data(img_size, cluster_size): 160*38eb193bSMaria Kustova """Return a set of random indices of clusters allocated for guest data. 161*38eb193bSMaria Kustova """ 162*38eb193bSMaria Kustova num_of_cls = img_size/cluster_size 163*38eb193bSMaria Kustova return set(random.sample(range(1, num_of_cls + 1), 164*38eb193bSMaria Kustova random.randint(0, num_of_cls))) 165*38eb193bSMaria Kustova 166*38eb193bSMaria Kustova def create_header(self, cluster_bits, backing_file_name=None): 167e1232323SMaria Kustova """Generate a random valid header.""" 168e1232323SMaria Kustova meta_header = [ 169e1232323SMaria Kustova ['>4s', 0, "QFI\xfb", 'magic'], 170e1232323SMaria Kustova ['>I', 4, random.randint(2, 3), 'version'], 171e1232323SMaria Kustova ['>Q', 8, 0, 'backing_file_offset'], 172e1232323SMaria Kustova ['>I', 16, 0, 'backing_file_size'], 173e1232323SMaria Kustova ['>I', 20, cluster_bits, 'cluster_bits'], 174*38eb193bSMaria Kustova ['>Q', 24, self.image_size, 'size'], 175e1232323SMaria Kustova ['>I', 32, 0, 'crypt_method'], 176e1232323SMaria Kustova ['>I', 36, 0, 'l1_size'], 177e1232323SMaria Kustova ['>Q', 40, 0, 'l1_table_offset'], 178e1232323SMaria Kustova ['>Q', 48, 0, 'refcount_table_offset'], 179e1232323SMaria Kustova ['>I', 56, 0, 'refcount_table_clusters'], 180e1232323SMaria Kustova ['>I', 60, 0, 'nb_snapshots'], 181e1232323SMaria Kustova ['>Q', 64, 0, 'snapshots_offset'], 182e1232323SMaria Kustova ['>Q', 72, 0, 'incompatible_features'], 183e1232323SMaria Kustova ['>Q', 80, 0, 'compatible_features'], 184e1232323SMaria Kustova ['>Q', 88, 0, 'autoclear_features'], 185e1232323SMaria Kustova # Only refcount_order = 4 is supported by current (07.2014) 186e1232323SMaria Kustova # implementation of QEMU 187e1232323SMaria Kustova ['>I', 96, 4, 'refcount_order'], 188e1232323SMaria Kustova ['>I', 100, 0, 'header_length'] 189e1232323SMaria Kustova ] 190*38eb193bSMaria Kustova self.header = FieldsList(meta_header) 191e1232323SMaria Kustova 192*38eb193bSMaria Kustova if self.header['version'][0].value == 2: 193*38eb193bSMaria Kustova self.header['header_length'][0].value = 72 194e1232323SMaria Kustova else: 195*38eb193bSMaria Kustova self.header['incompatible_features'][0].value = \ 196*38eb193bSMaria Kustova random.getrandbits(2) 197*38eb193bSMaria Kustova self.header['compatible_features'][0].value = random.getrandbits(1) 198*38eb193bSMaria Kustova self.header['header_length'][0].value = 104 199e1232323SMaria Kustova 200*38eb193bSMaria Kustova max_header_len = struct.calcsize( 201*38eb193bSMaria Kustova self.header['header_length'][0].fmt) + \ 202*38eb193bSMaria Kustova self.header['header_length'][0].offset 203e1232323SMaria Kustova end_of_extension_area_len = 2 * UINT32_S 204*38eb193bSMaria Kustova free_space = self.cluster_size - max_header_len - \ 205*38eb193bSMaria Kustova end_of_extension_area_len 206e1232323SMaria Kustova # If the backing file name specified and there is enough space for it 207e1232323SMaria Kustova # in the first cluster, then it's placed in the very end of the first 208e1232323SMaria Kustova # cluster. 209e1232323SMaria Kustova if (backing_file_name is not None) and \ 210e1232323SMaria Kustova (free_space >= len(backing_file_name)): 211*38eb193bSMaria Kustova self.header['backing_file_size'][0].value = len(backing_file_name) 212*38eb193bSMaria Kustova self.header['backing_file_offset'][0].value = \ 213*38eb193bSMaria Kustova self.cluster_size - len(backing_file_name) 214e1232323SMaria Kustova 215*38eb193bSMaria Kustova def set_backing_file_name(self, backing_file_name=None): 216e1232323SMaria Kustova """Add the name of the backing file at the offset specified 217e1232323SMaria Kustova in the header. 218e1232323SMaria Kustova """ 219e1232323SMaria Kustova if (backing_file_name is not None) and \ 220*38eb193bSMaria Kustova (not self.header['backing_file_offset'][0].value == 0): 221e1232323SMaria Kustova data_len = len(backing_file_name) 222e1232323SMaria Kustova data_fmt = '>' + str(data_len) + 's' 223*38eb193bSMaria Kustova self.backing_file_name = FieldsList([ 224*38eb193bSMaria Kustova [data_fmt, self.header['backing_file_offset'][0].value, 225e1232323SMaria Kustova backing_file_name, 'bf_name'] 226e1232323SMaria Kustova ]) 227e1232323SMaria Kustova else: 228*38eb193bSMaria Kustova self.backing_file_name = FieldsList() 229e1232323SMaria Kustova 230*38eb193bSMaria Kustova def set_backing_file_format(self, backing_file_fmt=None): 231e1232323SMaria Kustova """Generate the header extension for the backing file 232e1232323SMaria Kustova format. 233e1232323SMaria Kustova """ 234*38eb193bSMaria Kustova self.backing_file_format = FieldsList() 235*38eb193bSMaria Kustova offset = struct.calcsize(self.header['header_length'][0].fmt) + \ 236*38eb193bSMaria Kustova self.header['header_length'][0].offset 237e1232323SMaria Kustova 238e1232323SMaria Kustova if backing_file_fmt is not None: 239e1232323SMaria Kustova # Calculation of the free space available in the first cluster 240e1232323SMaria Kustova end_of_extension_area_len = 2 * UINT32_S 241*38eb193bSMaria Kustova high_border = (self.header['backing_file_offset'][0].value or 242*38eb193bSMaria Kustova (self.cluster_size - 1)) - \ 243e1232323SMaria Kustova end_of_extension_area_len 244e1232323SMaria Kustova free_space = high_border - offset 245e1232323SMaria Kustova ext_size = 2 * UINT32_S + ((len(backing_file_fmt) + 7) & ~7) 246e1232323SMaria Kustova 247e1232323SMaria Kustova if free_space >= ext_size: 248e1232323SMaria Kustova ext_data_len = len(backing_file_fmt) 249e1232323SMaria Kustova ext_data_fmt = '>' + str(ext_data_len) + 's' 250e1232323SMaria Kustova ext_padding_len = 7 - (ext_data_len - 1) % 8 251*38eb193bSMaria Kustova self.backing_file_format = FieldsList([ 252e1232323SMaria Kustova ['>I', offset, 0xE2792ACA, 'ext_magic'], 253e1232323SMaria Kustova ['>I', offset + UINT32_S, ext_data_len, 'ext_length'], 254e1232323SMaria Kustova [ext_data_fmt, offset + UINT32_S * 2, backing_file_fmt, 255e1232323SMaria Kustova 'bf_format'] 256e1232323SMaria Kustova ]) 257*38eb193bSMaria Kustova offset = self.backing_file_format['bf_format'][0].offset + \ 258*38eb193bSMaria Kustova struct.calcsize(self.backing_file_format[ 259*38eb193bSMaria Kustova 'bf_format'][0].fmt) + ext_padding_len 260e1232323SMaria Kustova 261*38eb193bSMaria Kustova return offset 262*38eb193bSMaria Kustova 263*38eb193bSMaria Kustova def create_feature_name_table(self, offset): 264e1232323SMaria Kustova """Generate a random header extension for names of features used in 265e1232323SMaria Kustova the image. 266e1232323SMaria Kustova """ 267e1232323SMaria Kustova def gen_feat_ids(): 268e1232323SMaria Kustova """Return random feature type and feature bit.""" 269e1232323SMaria Kustova return (random.randint(0, 2), random.randint(0, 63)) 270e1232323SMaria Kustova 271e1232323SMaria Kustova end_of_extension_area_len = 2 * UINT32_S 272*38eb193bSMaria Kustova high_border = (self.header['backing_file_offset'][0].value or 273*38eb193bSMaria Kustova (self.cluster_size - 1)) - \ 274e1232323SMaria Kustova end_of_extension_area_len 275e1232323SMaria Kustova free_space = high_border - offset 276e1232323SMaria Kustova # Sum of sizes of 'magic' and 'length' header extension fields 277e1232323SMaria Kustova ext_header_len = 2 * UINT32_S 278e1232323SMaria Kustova fnt_entry_size = 6 * UINT64_S 279e1232323SMaria Kustova num_fnt_entries = min(10, (free_space - ext_header_len) / 280e1232323SMaria Kustova fnt_entry_size) 281e1232323SMaria Kustova if not num_fnt_entries == 0: 282e1232323SMaria Kustova feature_tables = [] 283e1232323SMaria Kustova feature_ids = [] 284e1232323SMaria Kustova inner_offset = offset + ext_header_len 285e1232323SMaria Kustova feat_name = 'some cool feature' 286e1232323SMaria Kustova while len(feature_tables) < num_fnt_entries * 3: 287e1232323SMaria Kustova feat_type, feat_bit = gen_feat_ids() 288e1232323SMaria Kustova # Remove duplicates 289e1232323SMaria Kustova while (feat_type, feat_bit) in feature_ids: 290e1232323SMaria Kustova feat_type, feat_bit = gen_feat_ids() 291e1232323SMaria Kustova feature_ids.append((feat_type, feat_bit)) 292e1232323SMaria Kustova feat_fmt = '>' + str(len(feat_name)) + 's' 293e1232323SMaria Kustova feature_tables += [['B', inner_offset, 294e1232323SMaria Kustova feat_type, 'feature_type'], 295e1232323SMaria Kustova ['B', inner_offset + 1, feat_bit, 296e1232323SMaria Kustova 'feature_bit_number'], 297e1232323SMaria Kustova [feat_fmt, inner_offset + 2, 298e1232323SMaria Kustova feat_name, 'feature_name'] 299e1232323SMaria Kustova ] 300e1232323SMaria Kustova inner_offset += fnt_entry_size 301e1232323SMaria Kustova # No padding for the extension is necessary, because 302e1232323SMaria Kustova # the extension length is multiple of 8 303*38eb193bSMaria Kustova self.feature_name_table = FieldsList([ 304e1232323SMaria Kustova ['>I', offset, 0x6803f857, 'ext_magic'], 305e1232323SMaria Kustova # One feature table contains 3 fields and takes 48 bytes 306e1232323SMaria Kustova ['>I', offset + UINT32_S, len(feature_tables) / 3 * 48, 307e1232323SMaria Kustova 'ext_length'] 308e1232323SMaria Kustova ] + feature_tables) 309e1232323SMaria Kustova offset = inner_offset 310e1232323SMaria Kustova else: 311*38eb193bSMaria Kustova self.feature_name_table = FieldsList() 312e1232323SMaria Kustova 313*38eb193bSMaria Kustova return offset 314e1232323SMaria Kustova 315*38eb193bSMaria Kustova def set_end_of_extension_area(self, offset): 316e1232323SMaria Kustova """Generate a mandatory header extension marking end of header 317e1232323SMaria Kustova extensions. 318e1232323SMaria Kustova """ 319*38eb193bSMaria Kustova self.end_of_extension_area = FieldsList([ 320e1232323SMaria Kustova ['>I', offset, 0, 'ext_magic'], 321e1232323SMaria Kustova ['>I', offset + UINT32_S, 0, 'ext_length'] 322e1232323SMaria Kustova ]) 323*38eb193bSMaria Kustova 324*38eb193bSMaria Kustova def create_l_structures(self): 325*38eb193bSMaria Kustova """Generate random valid L1 and L2 tables.""" 326*38eb193bSMaria Kustova def create_l2_entry(host, guest, l2_cluster): 327*38eb193bSMaria Kustova """Generate one L2 entry.""" 328*38eb193bSMaria Kustova offset = l2_cluster * self.cluster_size 329*38eb193bSMaria Kustova l2_size = self.cluster_size / UINT64_S 330*38eb193bSMaria Kustova entry_offset = offset + UINT64_S * (guest % l2_size) 331*38eb193bSMaria Kustova cluster_descriptor = host * self.cluster_size 332*38eb193bSMaria Kustova if not self.header['version'][0].value == 2: 333*38eb193bSMaria Kustova cluster_descriptor += random.randint(0, 1) 334*38eb193bSMaria Kustova # While snapshots are not supported, bit #63 = 1 335*38eb193bSMaria Kustova # Compressed clusters are not supported => bit #62 = 0 336*38eb193bSMaria Kustova entry_val = (1 << 63) + cluster_descriptor 337*38eb193bSMaria Kustova return ['>Q', entry_offset, entry_val, 'l2_entry'] 338*38eb193bSMaria Kustova 339*38eb193bSMaria Kustova def create_l1_entry(l2_cluster, l1_offset, guest): 340*38eb193bSMaria Kustova """Generate one L1 entry.""" 341*38eb193bSMaria Kustova l2_size = self.cluster_size / UINT64_S 342*38eb193bSMaria Kustova entry_offset = l1_offset + UINT64_S * (guest / l2_size) 343*38eb193bSMaria Kustova # While snapshots are not supported bit #63 = 1 344*38eb193bSMaria Kustova entry_val = (1 << 63) + l2_cluster * self.cluster_size 345*38eb193bSMaria Kustova return ['>Q', entry_offset, entry_val, 'l1_entry'] 346*38eb193bSMaria Kustova 347*38eb193bSMaria Kustova if len(self.data_clusters) == 0: 348*38eb193bSMaria Kustova # All metadata for an empty guest image needs 4 clusters: 349*38eb193bSMaria Kustova # header, rfc table, rfc block, L1 table. 350*38eb193bSMaria Kustova # Header takes cluster #0, other clusters ##1-3 can be used 351*38eb193bSMaria Kustova l1_offset = random.randint(1, 3) * self.cluster_size 352*38eb193bSMaria Kustova l1 = [['>Q', l1_offset, 0, 'l1_entry']] 353*38eb193bSMaria Kustova l2 = [] 354*38eb193bSMaria Kustova else: 355*38eb193bSMaria Kustova meta_data = set([0]) 356*38eb193bSMaria Kustova guest_clusters = random.sample(range(self.image_size / 357*38eb193bSMaria Kustova self.cluster_size), 358*38eb193bSMaria Kustova len(self.data_clusters)) 359*38eb193bSMaria Kustova # Number of entries in a L1/L2 table 360*38eb193bSMaria Kustova l_size = self.cluster_size / UINT64_S 361*38eb193bSMaria Kustova # Number of clusters necessary for L1 table 362*38eb193bSMaria Kustova l1_size = int(ceil((max(guest_clusters) + 1) / float(l_size**2))) 363*38eb193bSMaria Kustova l1_start = self._get_adjacent_clusters(self.data_clusters | 364*38eb193bSMaria Kustova meta_data, l1_size) 365*38eb193bSMaria Kustova meta_data |= set(range(l1_start, l1_start + l1_size)) 366*38eb193bSMaria Kustova l1_offset = l1_start * self.cluster_size 367*38eb193bSMaria Kustova # Indices of L2 tables 368*38eb193bSMaria Kustova l2_ids = [] 369*38eb193bSMaria Kustova # Host clusters allocated for L2 tables 370*38eb193bSMaria Kustova l2_clusters = [] 371*38eb193bSMaria Kustova # L1 entries 372*38eb193bSMaria Kustova l1 = [] 373*38eb193bSMaria Kustova # L2 entries 374*38eb193bSMaria Kustova l2 = [] 375*38eb193bSMaria Kustova for host, guest in zip(self.data_clusters, guest_clusters): 376*38eb193bSMaria Kustova l2_id = guest / l_size 377*38eb193bSMaria Kustova if l2_id not in l2_ids: 378*38eb193bSMaria Kustova l2_ids.append(l2_id) 379*38eb193bSMaria Kustova l2_clusters.append(self._get_adjacent_clusters( 380*38eb193bSMaria Kustova self.data_clusters | meta_data | set(l2_clusters), 381*38eb193bSMaria Kustova 1)) 382*38eb193bSMaria Kustova l1.append(create_l1_entry(l2_clusters[-1], l1_offset, 383*38eb193bSMaria Kustova guest)) 384*38eb193bSMaria Kustova l2.append(create_l2_entry(host, guest, 385*38eb193bSMaria Kustova l2_clusters[l2_ids.index(l2_id)])) 386*38eb193bSMaria Kustova self.l2_tables = FieldsList(l2) 387*38eb193bSMaria Kustova self.l1_table = FieldsList(l1) 388*38eb193bSMaria Kustova self.header['l1_size'][0].value = int(ceil(UINT64_S * self.image_size / 389*38eb193bSMaria Kustova float(self.cluster_size**2))) 390*38eb193bSMaria Kustova self.header['l1_table_offset'][0].value = l1_offset 391e1232323SMaria Kustova 392e1232323SMaria Kustova def __init__(self, backing_file_name=None, backing_file_fmt=None): 393e1232323SMaria Kustova """Create a random valid qcow2 image with the correct inner structure 394e1232323SMaria Kustova and allowable values. 395e1232323SMaria Kustova """ 396e1232323SMaria Kustova cluster_bits, self.image_size = self._size_params() 397e1232323SMaria Kustova self.cluster_size = 1 << cluster_bits 398*38eb193bSMaria Kustova self.create_header(cluster_bits, backing_file_name) 399*38eb193bSMaria Kustova self.set_backing_file_name(backing_file_name) 400*38eb193bSMaria Kustova offset = self.set_backing_file_format(backing_file_fmt) 401*38eb193bSMaria Kustova offset = self.create_feature_name_table(offset) 402*38eb193bSMaria Kustova self.set_end_of_extension_area(offset) 403*38eb193bSMaria Kustova self.data_clusters = self._alloc_data(self.image_size, 404*38eb193bSMaria Kustova self.cluster_size) 405*38eb193bSMaria Kustova self.create_l_structures() 406e1232323SMaria Kustova # Container for entire image 407e1232323SMaria Kustova self.data = FieldsList() 408e1232323SMaria Kustova # Percentage of fields will be fuzzed 409e1232323SMaria Kustova self.bias = random.uniform(0.2, 0.5) 410e1232323SMaria Kustova 411e1232323SMaria Kustova def __iter__(self): 412e1232323SMaria Kustova return iter([self.header, 413e1232323SMaria Kustova self.backing_file_format, 414e1232323SMaria Kustova self.feature_name_table, 415e1232323SMaria Kustova self.end_of_extension_area, 416*38eb193bSMaria Kustova self.backing_file_name, 417*38eb193bSMaria Kustova self.l1_table, 418*38eb193bSMaria Kustova self.l2_tables]) 419e1232323SMaria Kustova 420e1232323SMaria Kustova def _join(self): 421e1232323SMaria Kustova """Join all image structure elements as header, tables, etc in one 422e1232323SMaria Kustova list of fields. 423e1232323SMaria Kustova """ 424e1232323SMaria Kustova if len(self.data) == 0: 425e1232323SMaria Kustova for v in self: 426e1232323SMaria Kustova self.data += v 427e1232323SMaria Kustova 428e1232323SMaria Kustova def fuzz(self, fields_to_fuzz=None): 429e1232323SMaria Kustova """Fuzz an image by corrupting values of a random subset of its fields. 430e1232323SMaria Kustova 431e1232323SMaria Kustova Without parameters the method fuzzes an entire image. 432e1232323SMaria Kustova If 'fields_to_fuzz' is specified then only fields in this list will be 433e1232323SMaria Kustova fuzzed. 'fields_to_fuzz' can contain both individual fields and more 434e1232323SMaria Kustova general image elements as a header or tables. 435e1232323SMaria Kustova In the first case the field will be fuzzed always. 436e1232323SMaria Kustova In the second a random subset of fields will be selected and fuzzed. 437e1232323SMaria Kustova """ 438e1232323SMaria Kustova def coin(): 439e1232323SMaria Kustova """Return boolean value proportional to a portion of fields to be 440e1232323SMaria Kustova fuzzed. 441e1232323SMaria Kustova """ 442e1232323SMaria Kustova return random.random() < self.bias 443e1232323SMaria Kustova 444e1232323SMaria Kustova if fields_to_fuzz is None: 445e1232323SMaria Kustova self._join() 446e1232323SMaria Kustova for field in self.data: 447e1232323SMaria Kustova if coin(): 448e1232323SMaria Kustova field.value = getattr(fuzz, field.name)(field.value) 449e1232323SMaria Kustova else: 450e1232323SMaria Kustova for item in fields_to_fuzz: 451e1232323SMaria Kustova if len(item) == 1: 452e1232323SMaria Kustova for field in getattr(self, item[0]): 453e1232323SMaria Kustova if coin(): 454e1232323SMaria Kustova field.value = getattr(fuzz, 455e1232323SMaria Kustova field.name)(field.value) 456e1232323SMaria Kustova else: 457e1232323SMaria Kustova for field in getattr(self, item[0])[item[1]]: 458e1232323SMaria Kustova try: 459e1232323SMaria Kustova field.value = getattr(fuzz, field.name)( 460e1232323SMaria Kustova field.value) 461e1232323SMaria Kustova except AttributeError: 462e1232323SMaria Kustova # Some fields can be skipped depending on 463*38eb193bSMaria Kustova # their prerequisites 464e1232323SMaria Kustova pass 465e1232323SMaria Kustova 466e1232323SMaria Kustova def write(self, filename): 467e1232323SMaria Kustova """Write an entire image to the file.""" 468e1232323SMaria Kustova image_file = open(filename, 'w') 469e1232323SMaria Kustova self._join() 470e1232323SMaria Kustova for field in self.data: 471e1232323SMaria Kustova image_file.seek(field.offset) 472e1232323SMaria Kustova image_file.write(struct.pack(field.fmt, field.value)) 473*38eb193bSMaria Kustova 474*38eb193bSMaria Kustova for cluster in sorted(self.data_clusters): 475*38eb193bSMaria Kustova image_file.seek(cluster * self.cluster_size) 476*38eb193bSMaria Kustova image_file.write(urandom(self.cluster_size)) 477*38eb193bSMaria Kustova 478*38eb193bSMaria Kustova # Align the real image size to the cluster size 479e1232323SMaria Kustova image_file.seek(0, 2) 480e1232323SMaria Kustova size = image_file.tell() 481e1232323SMaria Kustova rounded = (size + self.cluster_size - 1) & ~(self.cluster_size - 1) 482e1232323SMaria Kustova if rounded > size: 483e1232323SMaria Kustova image_file.seek(rounded - 1) 484e1232323SMaria Kustova image_file.write("\0") 485e1232323SMaria Kustova image_file.close() 486e1232323SMaria Kustova 487e1232323SMaria Kustova 488e1232323SMaria Kustovadef create_image(test_img_path, backing_file_name=None, backing_file_fmt=None, 489e1232323SMaria Kustova fields_to_fuzz=None): 490e1232323SMaria Kustova """Create a fuzzed image and write it to the specified file.""" 491e1232323SMaria Kustova image = Image(backing_file_name, backing_file_fmt) 492e1232323SMaria Kustova image.fuzz(fields_to_fuzz) 493e1232323SMaria Kustova image.write(test_img_path) 494e1232323SMaria Kustova return image.image_size 495