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 2238eb193bSMaria Kustovafrom math import ceil 2338eb193bSMaria Kustovafrom os import urandom 24*94c83a24SMaria Kustovafrom itertools import chain 25e1232323SMaria Kustova 26e1232323SMaria KustovaMAX_IMAGE_SIZE = 10 * (1 << 20) 27e1232323SMaria Kustova# Standard sizes 28e1232323SMaria KustovaUINT32_S = 4 29e1232323SMaria KustovaUINT64_S = 8 30e1232323SMaria Kustova 31e1232323SMaria Kustova 32e1232323SMaria Kustovaclass Field(object): 33e1232323SMaria Kustova 34e1232323SMaria Kustova """Atomic image element (field). 35e1232323SMaria Kustova 36e1232323SMaria Kustova The class represents an image field as quadruple of a data format 37e1232323SMaria Kustova of value necessary for its packing to binary form, an offset from 38e1232323SMaria Kustova the beginning of the image, a value and a name. 39e1232323SMaria Kustova 40*94c83a24SMaria Kustova The field can be iterated as a list [format, offset, value, name]. 41e1232323SMaria Kustova """ 42e1232323SMaria Kustova 43e1232323SMaria Kustova __slots__ = ('fmt', 'offset', 'value', 'name') 44e1232323SMaria Kustova 45e1232323SMaria Kustova def __init__(self, fmt, offset, val, name): 46e1232323SMaria Kustova self.fmt = fmt 47e1232323SMaria Kustova self.offset = offset 48e1232323SMaria Kustova self.value = val 49e1232323SMaria Kustova self.name = name 50e1232323SMaria Kustova 51e1232323SMaria Kustova def __iter__(self): 52*94c83a24SMaria Kustova return iter([self.fmt, self.offset, self.value, self.name]) 53e1232323SMaria Kustova 54e1232323SMaria Kustova def __repr__(self): 55e1232323SMaria Kustova return "Field(fmt='%s', offset=%d, value=%s, name=%s)" % \ 56e1232323SMaria Kustova (self.fmt, self.offset, str(self.value), self.name) 57e1232323SMaria Kustova 58e1232323SMaria Kustova 59e1232323SMaria Kustovaclass FieldsList(object): 60e1232323SMaria Kustova 61e1232323SMaria Kustova """List of fields. 62e1232323SMaria Kustova 63*94c83a24SMaria Kustova The class allows access to a field in the list by its name. 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: 70*94c83a24SMaria Kustova self.data = [Field(*f) 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 __len__(self): 80e1232323SMaria Kustova return len(self.data) 81e1232323SMaria Kustova 82e1232323SMaria Kustova 83e1232323SMaria Kustovaclass Image(object): 84e1232323SMaria Kustova 85e1232323SMaria Kustova """ Qcow2 image object. 86e1232323SMaria Kustova 87e1232323SMaria Kustova This class allows to create qcow2 images with random valid structures and 88e1232323SMaria Kustova values, fuzz them via external qcow2.fuzz module and write the result to 89e1232323SMaria Kustova a file. 90e1232323SMaria Kustova """ 91e1232323SMaria Kustova 92*94c83a24SMaria Kustova def __init__(self, backing_file_name=None): 93*94c83a24SMaria Kustova """Create a random valid qcow2 image with the correct header and stored 94*94c83a24SMaria Kustova backing file name. 95*94c83a24SMaria Kustova """ 96*94c83a24SMaria Kustova cluster_bits, self.image_size = self._size_params() 97*94c83a24SMaria Kustova self.cluster_size = 1 << cluster_bits 98*94c83a24SMaria Kustova self.header = FieldsList() 99*94c83a24SMaria Kustova self.backing_file_name = FieldsList() 100*94c83a24SMaria Kustova self.backing_file_format = FieldsList() 101*94c83a24SMaria Kustova self.feature_name_table = FieldsList() 102*94c83a24SMaria Kustova self.end_of_extension_area = FieldsList() 103*94c83a24SMaria Kustova self.l2_tables = FieldsList() 104*94c83a24SMaria Kustova self.l1_table = FieldsList() 105*94c83a24SMaria Kustova self.ext_offset = 0 106*94c83a24SMaria Kustova self.create_header(cluster_bits, backing_file_name) 107*94c83a24SMaria Kustova self.set_backing_file_name(backing_file_name) 108*94c83a24SMaria Kustova self.data_clusters = self._alloc_data(self.image_size, 109*94c83a24SMaria Kustova self.cluster_size) 110*94c83a24SMaria Kustova # Percentage of fields will be fuzzed 111*94c83a24SMaria Kustova self.bias = random.uniform(0.2, 0.5) 112*94c83a24SMaria Kustova 113*94c83a24SMaria Kustova def __iter__(self): 114*94c83a24SMaria Kustova return chain(self.header, self.backing_file_format, 115*94c83a24SMaria Kustova self.feature_name_table, self.end_of_extension_area, 116*94c83a24SMaria Kustova self.backing_file_name, self.l1_table, self.l2_tables) 117*94c83a24SMaria Kustova 118*94c83a24SMaria Kustova def create_header(self, cluster_bits, backing_file_name=None): 119*94c83a24SMaria Kustova """Generate a random valid header.""" 120*94c83a24SMaria Kustova meta_header = [ 121*94c83a24SMaria Kustova ['>4s', 0, "QFI\xfb", 'magic'], 122*94c83a24SMaria Kustova ['>I', 4, random.randint(2, 3), 'version'], 123*94c83a24SMaria Kustova ['>Q', 8, 0, 'backing_file_offset'], 124*94c83a24SMaria Kustova ['>I', 16, 0, 'backing_file_size'], 125*94c83a24SMaria Kustova ['>I', 20, cluster_bits, 'cluster_bits'], 126*94c83a24SMaria Kustova ['>Q', 24, self.image_size, 'size'], 127*94c83a24SMaria Kustova ['>I', 32, 0, 'crypt_method'], 128*94c83a24SMaria Kustova ['>I', 36, 0, 'l1_size'], 129*94c83a24SMaria Kustova ['>Q', 40, 0, 'l1_table_offset'], 130*94c83a24SMaria Kustova ['>Q', 48, 0, 'refcount_table_offset'], 131*94c83a24SMaria Kustova ['>I', 56, 0, 'refcount_table_clusters'], 132*94c83a24SMaria Kustova ['>I', 60, 0, 'nb_snapshots'], 133*94c83a24SMaria Kustova ['>Q', 64, 0, 'snapshots_offset'], 134*94c83a24SMaria Kustova ['>Q', 72, 0, 'incompatible_features'], 135*94c83a24SMaria Kustova ['>Q', 80, 0, 'compatible_features'], 136*94c83a24SMaria Kustova ['>Q', 88, 0, 'autoclear_features'], 137*94c83a24SMaria Kustova # Only refcount_order = 4 is supported by current (07.2014) 138*94c83a24SMaria Kustova # implementation of QEMU 139*94c83a24SMaria Kustova ['>I', 96, 4, 'refcount_order'], 140*94c83a24SMaria Kustova ['>I', 100, 0, 'header_length'] 141*94c83a24SMaria Kustova ] 142*94c83a24SMaria Kustova self.header = FieldsList(meta_header) 143*94c83a24SMaria Kustova 144*94c83a24SMaria Kustova if self.header['version'][0].value == 2: 145*94c83a24SMaria Kustova self.header['header_length'][0].value = 72 146*94c83a24SMaria Kustova else: 147*94c83a24SMaria Kustova self.header['incompatible_features'][0].value = \ 148*94c83a24SMaria Kustova random.getrandbits(2) 149*94c83a24SMaria Kustova self.header['compatible_features'][0].value = random.getrandbits(1) 150*94c83a24SMaria Kustova self.header['header_length'][0].value = 104 151*94c83a24SMaria Kustova # Extensions start at the header last field offset and the field size 152*94c83a24SMaria Kustova self.ext_offset = struct.calcsize( 153*94c83a24SMaria Kustova self.header['header_length'][0].fmt) + \ 154*94c83a24SMaria Kustova self.header['header_length'][0].offset 155*94c83a24SMaria Kustova end_of_extension_area_len = 2 * UINT32_S 156*94c83a24SMaria Kustova free_space = self.cluster_size - self.ext_offset - \ 157*94c83a24SMaria Kustova end_of_extension_area_len 158*94c83a24SMaria Kustova # If the backing file name specified and there is enough space for it 159*94c83a24SMaria Kustova # in the first cluster, then it's placed in the very end of the first 160*94c83a24SMaria Kustova # cluster. 161*94c83a24SMaria Kustova if (backing_file_name is not None) and \ 162*94c83a24SMaria Kustova (free_space >= len(backing_file_name)): 163*94c83a24SMaria Kustova self.header['backing_file_size'][0].value = len(backing_file_name) 164*94c83a24SMaria Kustova self.header['backing_file_offset'][0].value = \ 165*94c83a24SMaria Kustova self.cluster_size - len(backing_file_name) 166*94c83a24SMaria Kustova 167*94c83a24SMaria Kustova def set_backing_file_name(self, backing_file_name=None): 168*94c83a24SMaria Kustova """Add the name of the backing file at the offset specified 169*94c83a24SMaria Kustova in the header. 170*94c83a24SMaria Kustova """ 171*94c83a24SMaria Kustova if (backing_file_name is not None) and \ 172*94c83a24SMaria Kustova (not self.header['backing_file_offset'][0].value == 0): 173*94c83a24SMaria Kustova data_len = len(backing_file_name) 174*94c83a24SMaria Kustova data_fmt = '>' + str(data_len) + 's' 175*94c83a24SMaria Kustova self.backing_file_name = FieldsList([ 176*94c83a24SMaria Kustova [data_fmt, self.header['backing_file_offset'][0].value, 177*94c83a24SMaria Kustova backing_file_name, 'bf_name'] 178*94c83a24SMaria Kustova ]) 179*94c83a24SMaria Kustova 180*94c83a24SMaria Kustova def set_backing_file_format(self, backing_file_fmt=None): 181*94c83a24SMaria Kustova """Generate the header extension for the backing file format.""" 182*94c83a24SMaria Kustova if backing_file_fmt is not None: 183*94c83a24SMaria Kustova # Calculation of the free space available in the first cluster 184*94c83a24SMaria Kustova end_of_extension_area_len = 2 * UINT32_S 185*94c83a24SMaria Kustova high_border = (self.header['backing_file_offset'][0].value or 186*94c83a24SMaria Kustova (self.cluster_size - 1)) - \ 187*94c83a24SMaria Kustova end_of_extension_area_len 188*94c83a24SMaria Kustova free_space = high_border - self.ext_offset 189*94c83a24SMaria Kustova ext_size = 2 * UINT32_S + ((len(backing_file_fmt) + 7) & ~7) 190*94c83a24SMaria Kustova 191*94c83a24SMaria Kustova if free_space >= ext_size: 192*94c83a24SMaria Kustova ext_data_len = len(backing_file_fmt) 193*94c83a24SMaria Kustova ext_data_fmt = '>' + str(ext_data_len) + 's' 194*94c83a24SMaria Kustova ext_padding_len = 7 - (ext_data_len - 1) % 8 195*94c83a24SMaria Kustova self.backing_file_format = FieldsList([ 196*94c83a24SMaria Kustova ['>I', self.ext_offset, 0xE2792ACA, 'ext_magic'], 197*94c83a24SMaria Kustova ['>I', self.ext_offset + UINT32_S, ext_data_len, 198*94c83a24SMaria Kustova 'ext_length'], 199*94c83a24SMaria Kustova [ext_data_fmt, self.ext_offset + UINT32_S * 2, 200*94c83a24SMaria Kustova backing_file_fmt, 'bf_format'] 201*94c83a24SMaria Kustova ]) 202*94c83a24SMaria Kustova self.ext_offset = \ 203*94c83a24SMaria Kustova struct.calcsize( 204*94c83a24SMaria Kustova self.backing_file_format['bf_format'][0].fmt) + \ 205*94c83a24SMaria Kustova ext_padding_len + \ 206*94c83a24SMaria Kustova self.backing_file_format['bf_format'][0].offset 207*94c83a24SMaria Kustova 208*94c83a24SMaria Kustova def create_feature_name_table(self): 209*94c83a24SMaria Kustova """Generate a random header extension for names of features used in 210*94c83a24SMaria Kustova the image. 211*94c83a24SMaria Kustova """ 212*94c83a24SMaria Kustova def gen_feat_ids(): 213*94c83a24SMaria Kustova """Return random feature type and feature bit.""" 214*94c83a24SMaria Kustova return (random.randint(0, 2), random.randint(0, 63)) 215*94c83a24SMaria Kustova 216*94c83a24SMaria Kustova end_of_extension_area_len = 2 * UINT32_S 217*94c83a24SMaria Kustova high_border = (self.header['backing_file_offset'][0].value or 218*94c83a24SMaria Kustova (self.cluster_size - 1)) - \ 219*94c83a24SMaria Kustova end_of_extension_area_len 220*94c83a24SMaria Kustova free_space = high_border - self.ext_offset 221*94c83a24SMaria Kustova # Sum of sizes of 'magic' and 'length' header extension fields 222*94c83a24SMaria Kustova ext_header_len = 2 * UINT32_S 223*94c83a24SMaria Kustova fnt_entry_size = 6 * UINT64_S 224*94c83a24SMaria Kustova num_fnt_entries = min(10, (free_space - ext_header_len) / 225*94c83a24SMaria Kustova fnt_entry_size) 226*94c83a24SMaria Kustova if not num_fnt_entries == 0: 227*94c83a24SMaria Kustova feature_tables = [] 228*94c83a24SMaria Kustova feature_ids = [] 229*94c83a24SMaria Kustova inner_offset = self.ext_offset + ext_header_len 230*94c83a24SMaria Kustova feat_name = 'some cool feature' 231*94c83a24SMaria Kustova while len(feature_tables) < num_fnt_entries * 3: 232*94c83a24SMaria Kustova feat_type, feat_bit = gen_feat_ids() 233*94c83a24SMaria Kustova # Remove duplicates 234*94c83a24SMaria Kustova while (feat_type, feat_bit) in feature_ids: 235*94c83a24SMaria Kustova feat_type, feat_bit = gen_feat_ids() 236*94c83a24SMaria Kustova feature_ids.append((feat_type, feat_bit)) 237*94c83a24SMaria Kustova feat_fmt = '>' + str(len(feat_name)) + 's' 238*94c83a24SMaria Kustova feature_tables += [['B', inner_offset, 239*94c83a24SMaria Kustova feat_type, 'feature_type'], 240*94c83a24SMaria Kustova ['B', inner_offset + 1, feat_bit, 241*94c83a24SMaria Kustova 'feature_bit_number'], 242*94c83a24SMaria Kustova [feat_fmt, inner_offset + 2, 243*94c83a24SMaria Kustova feat_name, 'feature_name'] 244*94c83a24SMaria Kustova ] 245*94c83a24SMaria Kustova inner_offset += fnt_entry_size 246*94c83a24SMaria Kustova # No padding for the extension is necessary, because 247*94c83a24SMaria Kustova # the extension length is multiple of 8 248*94c83a24SMaria Kustova self.feature_name_table = FieldsList([ 249*94c83a24SMaria Kustova ['>I', self.ext_offset, 0x6803f857, 'ext_magic'], 250*94c83a24SMaria Kustova # One feature table contains 3 fields and takes 48 bytes 251*94c83a24SMaria Kustova ['>I', self.ext_offset + UINT32_S, 252*94c83a24SMaria Kustova len(feature_tables) / 3 * 48, 'ext_length'] 253*94c83a24SMaria Kustova ] + feature_tables) 254*94c83a24SMaria Kustova self.ext_offset = inner_offset 255*94c83a24SMaria Kustova 256*94c83a24SMaria Kustova def set_end_of_extension_area(self): 257*94c83a24SMaria Kustova """Generate a mandatory header extension marking end of header 258*94c83a24SMaria Kustova extensions. 259*94c83a24SMaria Kustova """ 260*94c83a24SMaria Kustova self.end_of_extension_area = FieldsList([ 261*94c83a24SMaria Kustova ['>I', self.ext_offset, 0, 'ext_magic'], 262*94c83a24SMaria Kustova ['>I', self.ext_offset + UINT32_S, 0, 'ext_length'] 263*94c83a24SMaria Kustova ]) 264*94c83a24SMaria Kustova 265*94c83a24SMaria Kustova def create_l_structures(self): 266*94c83a24SMaria Kustova """Generate random valid L1 and L2 tables.""" 267*94c83a24SMaria Kustova def create_l2_entry(host, guest, l2_cluster): 268*94c83a24SMaria Kustova """Generate one L2 entry.""" 269*94c83a24SMaria Kustova offset = l2_cluster * self.cluster_size 270*94c83a24SMaria Kustova l2_size = self.cluster_size / UINT64_S 271*94c83a24SMaria Kustova entry_offset = offset + UINT64_S * (guest % l2_size) 272*94c83a24SMaria Kustova cluster_descriptor = host * self.cluster_size 273*94c83a24SMaria Kustova if not self.header['version'][0].value == 2: 274*94c83a24SMaria Kustova cluster_descriptor += random.randint(0, 1) 275*94c83a24SMaria Kustova # While snapshots are not supported, bit #63 = 1 276*94c83a24SMaria Kustova # Compressed clusters are not supported => bit #62 = 0 277*94c83a24SMaria Kustova entry_val = (1 << 63) + cluster_descriptor 278*94c83a24SMaria Kustova return ['>Q', entry_offset, entry_val, 'l2_entry'] 279*94c83a24SMaria Kustova 280*94c83a24SMaria Kustova def create_l1_entry(l2_cluster, l1_offset, guest): 281*94c83a24SMaria Kustova """Generate one L1 entry.""" 282*94c83a24SMaria Kustova l2_size = self.cluster_size / UINT64_S 283*94c83a24SMaria Kustova entry_offset = l1_offset + UINT64_S * (guest / l2_size) 284*94c83a24SMaria Kustova # While snapshots are not supported bit #63 = 1 285*94c83a24SMaria Kustova entry_val = (1 << 63) + l2_cluster * self.cluster_size 286*94c83a24SMaria Kustova return ['>Q', entry_offset, entry_val, 'l1_entry'] 287*94c83a24SMaria Kustova 288*94c83a24SMaria Kustova if len(self.data_clusters) == 0: 289*94c83a24SMaria Kustova # All metadata for an empty guest image needs 4 clusters: 290*94c83a24SMaria Kustova # header, rfc table, rfc block, L1 table. 291*94c83a24SMaria Kustova # Header takes cluster #0, other clusters ##1-3 can be used 292*94c83a24SMaria Kustova l1_offset = random.randint(1, 3) * self.cluster_size 293*94c83a24SMaria Kustova l1 = [['>Q', l1_offset, 0, 'l1_entry']] 294*94c83a24SMaria Kustova l2 = [] 295*94c83a24SMaria Kustova else: 296*94c83a24SMaria Kustova meta_data = self._get_metadata() 297*94c83a24SMaria Kustova guest_clusters = random.sample(range(self.image_size / 298*94c83a24SMaria Kustova self.cluster_size), 299*94c83a24SMaria Kustova len(self.data_clusters)) 300*94c83a24SMaria Kustova # Number of entries in a L1/L2 table 301*94c83a24SMaria Kustova l_size = self.cluster_size / UINT64_S 302*94c83a24SMaria Kustova # Number of clusters necessary for L1 table 303*94c83a24SMaria Kustova l1_size = int(ceil((max(guest_clusters) + 1) / float(l_size**2))) 304*94c83a24SMaria Kustova l1_start = self._get_adjacent_clusters(self.data_clusters | 305*94c83a24SMaria Kustova meta_data, l1_size) 306*94c83a24SMaria Kustova meta_data |= set(range(l1_start, l1_start + l1_size)) 307*94c83a24SMaria Kustova l1_offset = l1_start * self.cluster_size 308*94c83a24SMaria Kustova # Indices of L2 tables 309*94c83a24SMaria Kustova l2_ids = [] 310*94c83a24SMaria Kustova # Host clusters allocated for L2 tables 311*94c83a24SMaria Kustova l2_clusters = [] 312*94c83a24SMaria Kustova # L1 entries 313*94c83a24SMaria Kustova l1 = [] 314*94c83a24SMaria Kustova # L2 entries 315*94c83a24SMaria Kustova l2 = [] 316*94c83a24SMaria Kustova for host, guest in zip(self.data_clusters, guest_clusters): 317*94c83a24SMaria Kustova l2_id = guest / l_size 318*94c83a24SMaria Kustova if l2_id not in l2_ids: 319*94c83a24SMaria Kustova l2_ids.append(l2_id) 320*94c83a24SMaria Kustova l2_clusters.append(self._get_adjacent_clusters( 321*94c83a24SMaria Kustova self.data_clusters | meta_data | set(l2_clusters), 322*94c83a24SMaria Kustova 1)) 323*94c83a24SMaria Kustova l1.append(create_l1_entry(l2_clusters[-1], l1_offset, 324*94c83a24SMaria Kustova guest)) 325*94c83a24SMaria Kustova l2.append(create_l2_entry(host, guest, 326*94c83a24SMaria Kustova l2_clusters[l2_ids.index(l2_id)])) 327*94c83a24SMaria Kustova self.l2_tables = FieldsList(l2) 328*94c83a24SMaria Kustova self.l1_table = FieldsList(l1) 329*94c83a24SMaria Kustova self.header['l1_size'][0].value = int(ceil(UINT64_S * self.image_size / 330*94c83a24SMaria Kustova float(self.cluster_size**2))) 331*94c83a24SMaria Kustova self.header['l1_table_offset'][0].value = l1_offset 332*94c83a24SMaria Kustova 333*94c83a24SMaria Kustova def fuzz(self, fields_to_fuzz=None): 334*94c83a24SMaria Kustova """Fuzz an image by corrupting values of a random subset of its fields. 335*94c83a24SMaria Kustova 336*94c83a24SMaria Kustova Without parameters the method fuzzes an entire image. 337*94c83a24SMaria Kustova 338*94c83a24SMaria Kustova If 'fields_to_fuzz' is specified then only fields in this list will be 339*94c83a24SMaria Kustova fuzzed. 'fields_to_fuzz' can contain both individual fields and more 340*94c83a24SMaria Kustova general image elements as a header or tables. 341*94c83a24SMaria Kustova 342*94c83a24SMaria Kustova In the first case the field will be fuzzed always. 343*94c83a24SMaria Kustova In the second a random subset of fields will be selected and fuzzed. 344*94c83a24SMaria Kustova """ 345*94c83a24SMaria Kustova def coin(): 346*94c83a24SMaria Kustova """Return boolean value proportional to a portion of fields to be 347*94c83a24SMaria Kustova fuzzed. 348*94c83a24SMaria Kustova """ 349*94c83a24SMaria Kustova return random.random() < self.bias 350*94c83a24SMaria Kustova 351*94c83a24SMaria Kustova if fields_to_fuzz is None: 352*94c83a24SMaria Kustova for field in self: 353*94c83a24SMaria Kustova if coin(): 354*94c83a24SMaria Kustova field.value = getattr(fuzz, field.name)(field.value) 355*94c83a24SMaria Kustova else: 356*94c83a24SMaria Kustova for item in fields_to_fuzz: 357*94c83a24SMaria Kustova if len(item) == 1: 358*94c83a24SMaria Kustova for field in getattr(self, item[0]): 359*94c83a24SMaria Kustova if coin(): 360*94c83a24SMaria Kustova field.value = getattr(fuzz, 361*94c83a24SMaria Kustova field.name)(field.value) 362*94c83a24SMaria Kustova else: 363*94c83a24SMaria Kustova # If fields with the requested name were not generated 364*94c83a24SMaria Kustova # getattr(self, item[0])[item[1]] returns an empty list 365*94c83a24SMaria Kustova for field in getattr(self, item[0])[item[1]]: 366*94c83a24SMaria Kustova field.value = getattr(fuzz, field.name)(field.value) 367*94c83a24SMaria Kustova 368*94c83a24SMaria Kustova def write(self, filename): 369*94c83a24SMaria Kustova """Write an entire image to the file.""" 370*94c83a24SMaria Kustova image_file = open(filename, 'w') 371*94c83a24SMaria Kustova for field in self: 372*94c83a24SMaria Kustova image_file.seek(field.offset) 373*94c83a24SMaria Kustova image_file.write(struct.pack(field.fmt, field.value)) 374*94c83a24SMaria Kustova 375*94c83a24SMaria Kustova for cluster in sorted(self.data_clusters): 376*94c83a24SMaria Kustova image_file.seek(cluster * self.cluster_size) 377*94c83a24SMaria Kustova image_file.write(urandom(self.cluster_size)) 378*94c83a24SMaria Kustova 379*94c83a24SMaria Kustova # Align the real image size to the cluster size 380*94c83a24SMaria Kustova image_file.seek(0, 2) 381*94c83a24SMaria Kustova size = image_file.tell() 382*94c83a24SMaria Kustova rounded = (size + self.cluster_size - 1) & ~(self.cluster_size - 1) 383*94c83a24SMaria Kustova if rounded > size: 384*94c83a24SMaria Kustova image_file.seek(rounded - 1) 385*94c83a24SMaria Kustova image_file.write("\0") 386*94c83a24SMaria Kustova image_file.close() 387*94c83a24SMaria Kustova 388e1232323SMaria Kustova @staticmethod 389e1232323SMaria Kustova def _size_params(): 390e1232323SMaria Kustova """Generate a random image size aligned to a random correct 391e1232323SMaria Kustova cluster size. 392e1232323SMaria Kustova """ 393e1232323SMaria Kustova cluster_bits = random.randrange(9, 21) 394e1232323SMaria Kustova cluster_size = 1 << cluster_bits 395e1232323SMaria Kustova img_size = random.randrange(0, MAX_IMAGE_SIZE + 1, cluster_size) 396e1232323SMaria Kustova return (cluster_bits, img_size) 397e1232323SMaria Kustova 398e1232323SMaria Kustova @staticmethod 39938eb193bSMaria Kustova def _get_available_clusters(used, number): 40038eb193bSMaria Kustova """Return a set of indices of not allocated clusters. 40138eb193bSMaria Kustova 40238eb193bSMaria Kustova 'used' contains indices of currently allocated clusters. 40338eb193bSMaria Kustova All clusters that cannot be allocated between 'used' clusters will have 40438eb193bSMaria Kustova indices appended to the end of 'used'. 40538eb193bSMaria Kustova """ 40638eb193bSMaria Kustova append_id = max(used) + 1 40738eb193bSMaria Kustova free = set(range(1, append_id)) - used 40838eb193bSMaria Kustova if len(free) >= number: 40938eb193bSMaria Kustova return set(random.sample(free, number)) 41038eb193bSMaria Kustova else: 41138eb193bSMaria Kustova return free | set(range(append_id, append_id + number - len(free))) 41238eb193bSMaria Kustova 41338eb193bSMaria Kustova @staticmethod 41438eb193bSMaria Kustova def _get_adjacent_clusters(used, size): 41538eb193bSMaria Kustova """Return an index of the first cluster in the sequence of free ones. 41638eb193bSMaria Kustova 41738eb193bSMaria Kustova 'used' contains indices of currently allocated clusters. 'size' is the 41838eb193bSMaria Kustova length of the sequence of free clusters. 41938eb193bSMaria Kustova If the sequence of 'size' is not available between 'used' clusters, its 42038eb193bSMaria Kustova first index will be append to the end of 'used'. 42138eb193bSMaria Kustova """ 42238eb193bSMaria Kustova def get_cluster_id(lst, length): 42338eb193bSMaria Kustova """Return the first index of the sequence of the specified length 42438eb193bSMaria Kustova or None if the sequence cannot be inserted in the list. 42538eb193bSMaria Kustova """ 42638eb193bSMaria Kustova if len(lst) != 0: 42738eb193bSMaria Kustova pairs = [] 42838eb193bSMaria Kustova pair = (lst[0], 1) 42938eb193bSMaria Kustova for i in range(1, len(lst)): 43038eb193bSMaria Kustova if lst[i] == lst[i-1] + 1: 43138eb193bSMaria Kustova pair = (lst[i], pair[1] + 1) 43238eb193bSMaria Kustova else: 43338eb193bSMaria Kustova pairs.append(pair) 43438eb193bSMaria Kustova pair = (lst[i], 1) 43538eb193bSMaria Kustova pairs.append(pair) 43638eb193bSMaria Kustova random.shuffle(pairs) 43738eb193bSMaria Kustova for x, s in pairs: 43838eb193bSMaria Kustova if s >= length: 43938eb193bSMaria Kustova return x - length + 1 44038eb193bSMaria Kustova return None 44138eb193bSMaria Kustova 44238eb193bSMaria Kustova append_id = max(used) + 1 44338eb193bSMaria Kustova free = list(set(range(1, append_id)) - used) 44438eb193bSMaria Kustova idx = get_cluster_id(free, size) 44538eb193bSMaria Kustova if idx is None: 44638eb193bSMaria Kustova return append_id 44738eb193bSMaria Kustova else: 44838eb193bSMaria Kustova return idx 44938eb193bSMaria Kustova 45038eb193bSMaria Kustova @staticmethod 45138eb193bSMaria Kustova def _alloc_data(img_size, cluster_size): 45238eb193bSMaria Kustova """Return a set of random indices of clusters allocated for guest data. 45338eb193bSMaria Kustova """ 45438eb193bSMaria Kustova num_of_cls = img_size/cluster_size 45538eb193bSMaria Kustova return set(random.sample(range(1, num_of_cls + 1), 45638eb193bSMaria Kustova random.randint(0, num_of_cls))) 45738eb193bSMaria Kustova 458*94c83a24SMaria Kustova def _get_metadata(self): 459*94c83a24SMaria Kustova """Return indices of clusters allocated for image metadata.""" 460*94c83a24SMaria Kustova ids = set() 461*94c83a24SMaria Kustova for x in self: 462*94c83a24SMaria Kustova ids.add(x.offset/self.cluster_size) 463*94c83a24SMaria Kustova return ids 464e1232323SMaria Kustova 465e1232323SMaria Kustova 466e1232323SMaria Kustovadef create_image(test_img_path, backing_file_name=None, backing_file_fmt=None, 467e1232323SMaria Kustova fields_to_fuzz=None): 468e1232323SMaria Kustova """Create a fuzzed image and write it to the specified file.""" 469*94c83a24SMaria Kustova image = Image(backing_file_name) 470*94c83a24SMaria Kustova image.set_backing_file_format(backing_file_fmt) 471*94c83a24SMaria Kustova image.create_feature_name_table() 472*94c83a24SMaria Kustova image.set_end_of_extension_area() 473*94c83a24SMaria Kustova image.create_l_structures() 474e1232323SMaria Kustova image.fuzz(fields_to_fuzz) 475e1232323SMaria Kustova image.write(test_img_path) 476e1232323SMaria Kustova return image.image_size 477