Some issues can be found only when a fuzzed image has a partial structure, e.g. has L1/L2 tables but no refcount ones. Generation of an entirely defined image limits these cases. Now the Image constructor creates only a header and a backing file name (if any), other image elements are generated in the 'create_image' API.
This patch series was created for the 'block-next' branch and based on the next series: [PATCH V2 0/3] image-fuzzer: Support L1/L2 tables in the qcow2 image generator Signed-off-by: Maria Kustova <mari...@catit.be> --- tests/image-fuzzer/qcow2/layout.py | 92 ++++++++++++++++++++------------------ 1 file changed, 49 insertions(+), 43 deletions(-) diff --git a/tests/image-fuzzer/qcow2/layout.py b/tests/image-fuzzer/qcow2/layout.py index 7839d2c..98673c4 100644 --- a/tests/image-fuzzer/qcow2/layout.py +++ b/tests/image-fuzzer/qcow2/layout.py @@ -156,6 +156,14 @@ class Image(object): return idx @staticmethod + def _get_cluster_ids(fields_list, cluster_size): + """Return indices of clusters allocated by fields of fields_list.""" + ids = set() + for x in fields_list: + ids.add(x.offset/cluster_size) + return ids + + @staticmethod def _alloc_data(img_size, cluster_size): """Return a set of random indices of clusters allocated for guest data. """ @@ -196,12 +204,12 @@ class Image(object): random.getrandbits(2) self.header['compatible_features'][0].value = random.getrandbits(1) self.header['header_length'][0].value = 104 - - max_header_len = struct.calcsize( + # Extensions starts at the header last field offset and the field size + self.ext_offset = struct.calcsize( self.header['header_length'][0].fmt) + \ self.header['header_length'][0].offset end_of_extension_area_len = 2 * UINT32_S - free_space = self.cluster_size - max_header_len - \ + free_space = self.cluster_size - self.ext_offset - \ end_of_extension_area_len # If the backing file name specified and there is enough space for it # in the first cluster, then it's placed in the very end of the first @@ -224,24 +232,18 @@ class Image(object): [data_fmt, self.header['backing_file_offset'][0].value, backing_file_name, 'bf_name'] ]) - else: - self.backing_file_name = FieldsList() def set_backing_file_format(self, backing_file_fmt=None): """Generate the header extension for the backing file format. """ - self.backing_file_format = FieldsList() - offset = struct.calcsize(self.header['header_length'][0].fmt) + \ - self.header['header_length'][0].offset - if backing_file_fmt is not None: # Calculation of the free space available in the first cluster end_of_extension_area_len = 2 * UINT32_S high_border = (self.header['backing_file_offset'][0].value or (self.cluster_size - 1)) - \ end_of_extension_area_len - free_space = high_border - offset + free_space = high_border - self.ext_offset ext_size = 2 * UINT32_S + ((len(backing_file_fmt) + 7) & ~7) if free_space >= ext_size: @@ -249,18 +251,19 @@ class Image(object): ext_data_fmt = '>' + str(ext_data_len) + 's' ext_padding_len = 7 - (ext_data_len - 1) % 8 self.backing_file_format = FieldsList([ - ['>I', offset, 0xE2792ACA, 'ext_magic'], - ['>I', offset + UINT32_S, ext_data_len, 'ext_length'], - [ext_data_fmt, offset + UINT32_S * 2, backing_file_fmt, - 'bf_format'] + ['>I', self.ext_offset, 0xE2792ACA, 'ext_magic'], + ['>I', self.ext_offset + UINT32_S, ext_data_len, + 'ext_length'], + [ext_data_fmt, self.ext_offset + UINT32_S * 2, + backing_file_fmt, 'bf_format'] ]) - offset = self.backing_file_format['bf_format'][0].offset + \ - struct.calcsize(self.backing_file_format[ - 'bf_format'][0].fmt) + ext_padding_len - - return offset + self.ext_offset = \ + struct.calcsize( + self.backing_file_format['bf_format'][0].fmt) + \ + ext_padding_len + \ + self.backing_file_format['bf_format'][0].offset - def create_feature_name_table(self, offset): + def create_feature_name_table(self): """Generate a random header extension for names of features used in the image. """ @@ -272,7 +275,7 @@ class Image(object): high_border = (self.header['backing_file_offset'][0].value or (self.cluster_size - 1)) - \ end_of_extension_area_len - free_space = high_border - offset + free_space = high_border - self.ext_offset # Sum of sizes of 'magic' and 'length' header extension fields ext_header_len = 2 * UINT32_S fnt_entry_size = 6 * UINT64_S @@ -281,7 +284,7 @@ class Image(object): if not num_fnt_entries == 0: feature_tables = [] feature_ids = [] - inner_offset = offset + ext_header_len + inner_offset = self.ext_offset + ext_header_len feat_name = 'some cool feature' while len(feature_tables) < num_fnt_entries * 3: feat_type, feat_bit = gen_feat_ids() @@ -301,24 +304,20 @@ class Image(object): # No padding for the extension is necessary, because # the extension length is multiple of 8 self.feature_name_table = FieldsList([ - ['>I', offset, 0x6803f857, 'ext_magic'], + ['>I', self.ext_offset, 0x6803f857, 'ext_magic'], # One feature table contains 3 fields and takes 48 bytes - ['>I', offset + UINT32_S, len(feature_tables) / 3 * 48, - 'ext_length'] + ['>I', self.ext_offset + UINT32_S, + len(feature_tables) / 3 * 48, 'ext_length'] ] + feature_tables) - offset = inner_offset - else: - self.feature_name_table = FieldsList() + self.ext_offset = inner_offset - return offset - - def set_end_of_extension_area(self, offset): + def set_end_of_extension_area(self): """Generate a mandatory header extension marking end of header extensions. """ self.end_of_extension_area = FieldsList([ - ['>I', offset, 0, 'ext_magic'], - ['>I', offset + UINT32_S, 0, 'ext_length'] + ['>I', self.ext_offset, 0, 'ext_magic'], + ['>I', self.ext_offset + UINT32_S, 0, 'ext_length'] ]) def create_l2_tables(self, meta_data=None): @@ -386,9 +385,8 @@ class Image(object): v_meta_data = set([0]) else: v_meta_data = set(meta_data) - l2_cluster_ids = set() - for x in self.l2_tables: - l2_cluster_ids.add(x.offset / self.cluster_size) + l2_cluster_ids = self._get_cluster_ids(self.l2_tables, + self.cluster_size) v_meta_data |= l2_cluster_ids # Numbers of active L1 entries l1_entries_ids = random.sample(range(max_l2_size), @@ -410,21 +408,24 @@ class Image(object): self.header['l1_size'][0].value = max_l2_size self.header['l1_table_offset'][0].value = l1_offset - def __init__(self, backing_file_name=None, backing_file_fmt=None): + def __init__(self, backing_file_name=None): """Create a random valid qcow2 image with the correct inner structure and allowable values. """ cluster_bits, self.image_size = self._size_params() self.cluster_size = 1 << cluster_bits + self.header = FieldsList() + self.backing_file_name = FieldsList() + self.backing_file_format = FieldsList() + self.feature_name_table = FieldsList() + self.end_of_extension_area = FieldsList() + self.l2_tables = FieldsList() + self.l1_table = FieldsList() + self.ext_offset = 0 self.create_header(cluster_bits, backing_file_name) self.set_backing_file_name(backing_file_name) - offset = self.set_backing_file_format(backing_file_fmt) - offset = self.create_feature_name_table(offset) - self.set_end_of_extension_area(offset) self.data_clusters = self._alloc_data(self.image_size, self.cluster_size) - self.create_l2_tables() - self.create_l1_table() # Container for entire image self.data = FieldsList() # Percentage of fields will be fuzzed @@ -512,7 +513,12 @@ class Image(object): def create_image(test_img_path, backing_file_name=None, backing_file_fmt=None, fields_to_fuzz=None): """Create a fuzzed image and write it to the specified file.""" - image = Image(backing_file_name, backing_file_fmt) + image = Image(backing_file_name) + image.set_backing_file_format(backing_file_fmt) + image.create_feature_name_table() + image.set_end_of_extension_area() + image.create_l2_tables() + image.create_l1_table() image.fuzz(fields_to_fuzz) image.write(test_img_path) return image.image_size -- 1.9.3