xref: /openbmc/qemu/tests/image-fuzzer/qcow2/layout.py (revision e123232331305fce0c69a3fb170d3f5d8003abf4)
1*e1232323SMaria Kustova# Generator of fuzzed qcow2 images
2*e1232323SMaria Kustova#
3*e1232323SMaria Kustova# Copyright (C) 2014 Maria Kustova <maria.k@catit.be>
4*e1232323SMaria Kustova#
5*e1232323SMaria Kustova# This program is free software: you can redistribute it and/or modify
6*e1232323SMaria Kustova# it under the terms of the GNU General Public License as published by
7*e1232323SMaria Kustova# the Free Software Foundation, either version 2 of the License, or
8*e1232323SMaria Kustova# (at your option) any later version.
9*e1232323SMaria Kustova#
10*e1232323SMaria Kustova# This program is distributed in the hope that it will be useful,
11*e1232323SMaria Kustova# but WITHOUT ANY WARRANTY; without even the implied warranty of
12*e1232323SMaria Kustova# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13*e1232323SMaria Kustova# GNU General Public License for more details.
14*e1232323SMaria Kustova#
15*e1232323SMaria Kustova# You should have received a copy of the GNU General Public License
16*e1232323SMaria Kustova# along with this program.  If not, see <http://www.gnu.org/licenses/>.
17*e1232323SMaria Kustova#
18*e1232323SMaria Kustova
19*e1232323SMaria Kustovaimport random
20*e1232323SMaria Kustovaimport struct
21*e1232323SMaria Kustovaimport fuzz
22*e1232323SMaria Kustova
23*e1232323SMaria KustovaMAX_IMAGE_SIZE = 10 * (1 << 20)
24*e1232323SMaria Kustova# Standard sizes
25*e1232323SMaria KustovaUINT32_S = 4
26*e1232323SMaria KustovaUINT64_S = 8
27*e1232323SMaria Kustova
28*e1232323SMaria Kustova
29*e1232323SMaria Kustovaclass Field(object):
30*e1232323SMaria Kustova
31*e1232323SMaria Kustova    """Atomic image element (field).
32*e1232323SMaria Kustova
33*e1232323SMaria Kustova    The class represents an image field as quadruple of a data format
34*e1232323SMaria Kustova    of value necessary for its packing to binary form, an offset from
35*e1232323SMaria Kustova    the beginning of the image, a value and a name.
36*e1232323SMaria Kustova
37*e1232323SMaria Kustova    The field can be iterated as a list [format, offset, value].
38*e1232323SMaria Kustova    """
39*e1232323SMaria Kustova
40*e1232323SMaria Kustova    __slots__ = ('fmt', 'offset', 'value', 'name')
41*e1232323SMaria Kustova
42*e1232323SMaria Kustova    def __init__(self, fmt, offset, val, name):
43*e1232323SMaria Kustova        self.fmt = fmt
44*e1232323SMaria Kustova        self.offset = offset
45*e1232323SMaria Kustova        self.value = val
46*e1232323SMaria Kustova        self.name = name
47*e1232323SMaria Kustova
48*e1232323SMaria Kustova    def __iter__(self):
49*e1232323SMaria Kustova        return iter([self.fmt, self.offset, self.value])
50*e1232323SMaria Kustova
51*e1232323SMaria Kustova    def __repr__(self):
52*e1232323SMaria Kustova        return "Field(fmt='%s', offset=%d, value=%s, name=%s)" % \
53*e1232323SMaria Kustova            (self.fmt, self.offset, str(self.value), self.name)
54*e1232323SMaria Kustova
55*e1232323SMaria Kustova
56*e1232323SMaria Kustovaclass FieldsList(object):
57*e1232323SMaria Kustova
58*e1232323SMaria Kustova    """List of fields.
59*e1232323SMaria Kustova
60*e1232323SMaria Kustova    The class allows access to a field in the list by its name and joins
61*e1232323SMaria Kustova    several list in one via in-place addition.
62*e1232323SMaria Kustova    """
63*e1232323SMaria Kustova
64*e1232323SMaria Kustova    def __init__(self, meta_data=None):
65*e1232323SMaria Kustova        if meta_data is None:
66*e1232323SMaria Kustova            self.data = []
67*e1232323SMaria Kustova        else:
68*e1232323SMaria Kustova            self.data = [Field(f[0], f[1], f[2], f[3])
69*e1232323SMaria Kustova                         for f in meta_data]
70*e1232323SMaria Kustova
71*e1232323SMaria Kustova    def __getitem__(self, name):
72*e1232323SMaria Kustova        return [x for x in self.data if x.name == name]
73*e1232323SMaria Kustova
74*e1232323SMaria Kustova    def __iter__(self):
75*e1232323SMaria Kustova        return iter(self.data)
76*e1232323SMaria Kustova
77*e1232323SMaria Kustova    def __iadd__(self, other):
78*e1232323SMaria Kustova        self.data += other.data
79*e1232323SMaria Kustova        return self
80*e1232323SMaria Kustova
81*e1232323SMaria Kustova    def __len__(self):
82*e1232323SMaria Kustova        return len(self.data)
83*e1232323SMaria Kustova
84*e1232323SMaria Kustova
85*e1232323SMaria Kustovaclass Image(object):
86*e1232323SMaria Kustova
87*e1232323SMaria Kustova    """ Qcow2 image object.
88*e1232323SMaria Kustova
89*e1232323SMaria Kustova    This class allows to create qcow2 images with random valid structures and
90*e1232323SMaria Kustova    values, fuzz them via external qcow2.fuzz module and write the result to
91*e1232323SMaria Kustova    a file.
92*e1232323SMaria Kustova    """
93*e1232323SMaria Kustova
94*e1232323SMaria Kustova    @staticmethod
95*e1232323SMaria Kustova    def _size_params():
96*e1232323SMaria Kustova        """Generate a random image size aligned to a random correct
97*e1232323SMaria Kustova        cluster size.
98*e1232323SMaria Kustova        """
99*e1232323SMaria Kustova        cluster_bits = random.randrange(9, 21)
100*e1232323SMaria Kustova        cluster_size = 1 << cluster_bits
101*e1232323SMaria Kustova        img_size = random.randrange(0, MAX_IMAGE_SIZE + 1, cluster_size)
102*e1232323SMaria Kustova        return (cluster_bits, img_size)
103*e1232323SMaria Kustova
104*e1232323SMaria Kustova    @staticmethod
105*e1232323SMaria Kustova    def _header(cluster_bits, img_size, backing_file_name=None):
106*e1232323SMaria Kustova        """Generate a random valid header."""
107*e1232323SMaria Kustova        meta_header = [
108*e1232323SMaria Kustova            ['>4s', 0, "QFI\xfb", 'magic'],
109*e1232323SMaria Kustova            ['>I', 4, random.randint(2, 3), 'version'],
110*e1232323SMaria Kustova            ['>Q', 8, 0, 'backing_file_offset'],
111*e1232323SMaria Kustova            ['>I', 16, 0, 'backing_file_size'],
112*e1232323SMaria Kustova            ['>I', 20, cluster_bits, 'cluster_bits'],
113*e1232323SMaria Kustova            ['>Q', 24, img_size, 'size'],
114*e1232323SMaria Kustova            ['>I', 32, 0, 'crypt_method'],
115*e1232323SMaria Kustova            ['>I', 36, 0, 'l1_size'],
116*e1232323SMaria Kustova            ['>Q', 40, 0, 'l1_table_offset'],
117*e1232323SMaria Kustova            ['>Q', 48, 0, 'refcount_table_offset'],
118*e1232323SMaria Kustova            ['>I', 56, 0, 'refcount_table_clusters'],
119*e1232323SMaria Kustova            ['>I', 60, 0, 'nb_snapshots'],
120*e1232323SMaria Kustova            ['>Q', 64, 0, 'snapshots_offset'],
121*e1232323SMaria Kustova            ['>Q', 72, 0, 'incompatible_features'],
122*e1232323SMaria Kustova            ['>Q', 80, 0, 'compatible_features'],
123*e1232323SMaria Kustova            ['>Q', 88, 0, 'autoclear_features'],
124*e1232323SMaria Kustova            # Only refcount_order = 4 is supported by current (07.2014)
125*e1232323SMaria Kustova            # implementation of QEMU
126*e1232323SMaria Kustova            ['>I', 96, 4, 'refcount_order'],
127*e1232323SMaria Kustova            ['>I', 100, 0, 'header_length']
128*e1232323SMaria Kustova        ]
129*e1232323SMaria Kustova        v_header = FieldsList(meta_header)
130*e1232323SMaria Kustova
131*e1232323SMaria Kustova        if v_header['version'][0].value == 2:
132*e1232323SMaria Kustova            v_header['header_length'][0].value = 72
133*e1232323SMaria Kustova        else:
134*e1232323SMaria Kustova            v_header['incompatible_features'][0].value = random.getrandbits(2)
135*e1232323SMaria Kustova            v_header['compatible_features'][0].value = random.getrandbits(1)
136*e1232323SMaria Kustova            v_header['header_length'][0].value = 104
137*e1232323SMaria Kustova
138*e1232323SMaria Kustova        max_header_len = struct.calcsize(v_header['header_length'][0].fmt) + \
139*e1232323SMaria Kustova                         v_header['header_length'][0].offset
140*e1232323SMaria Kustova        end_of_extension_area_len = 2 * UINT32_S
141*e1232323SMaria Kustova        free_space = (1 << cluster_bits) - (max_header_len +
142*e1232323SMaria Kustova                                            end_of_extension_area_len)
143*e1232323SMaria Kustova        # If the backing file name specified and there is enough space for it
144*e1232323SMaria Kustova        # in the first cluster, then it's placed in the very end of the first
145*e1232323SMaria Kustova        # cluster.
146*e1232323SMaria Kustova        if (backing_file_name is not None) and \
147*e1232323SMaria Kustova           (free_space >= len(backing_file_name)):
148*e1232323SMaria Kustova            v_header['backing_file_size'][0].value = len(backing_file_name)
149*e1232323SMaria Kustova            v_header['backing_file_offset'][0].value = (1 << cluster_bits) - \
150*e1232323SMaria Kustova                                                       len(backing_file_name)
151*e1232323SMaria Kustova
152*e1232323SMaria Kustova        return v_header
153*e1232323SMaria Kustova
154*e1232323SMaria Kustova    @staticmethod
155*e1232323SMaria Kustova    def _backing_file_name(header, backing_file_name=None):
156*e1232323SMaria Kustova        """Add the name of the backing file at the offset specified
157*e1232323SMaria Kustova        in the header.
158*e1232323SMaria Kustova        """
159*e1232323SMaria Kustova        if (backing_file_name is not None) and \
160*e1232323SMaria Kustova           (not header['backing_file_offset'][0].value == 0):
161*e1232323SMaria Kustova            data_len = len(backing_file_name)
162*e1232323SMaria Kustova            data_fmt = '>' + str(data_len) + 's'
163*e1232323SMaria Kustova            data_field = FieldsList([
164*e1232323SMaria Kustova                [data_fmt, header['backing_file_offset'][0].value,
165*e1232323SMaria Kustova                 backing_file_name, 'bf_name']
166*e1232323SMaria Kustova            ])
167*e1232323SMaria Kustova        else:
168*e1232323SMaria Kustova            data_field = FieldsList()
169*e1232323SMaria Kustova
170*e1232323SMaria Kustova        return data_field
171*e1232323SMaria Kustova
172*e1232323SMaria Kustova    @staticmethod
173*e1232323SMaria Kustova    def _backing_file_format(header, backing_file_fmt=None):
174*e1232323SMaria Kustova        """Generate the header extension for the backing file
175*e1232323SMaria Kustova        format.
176*e1232323SMaria Kustova        """
177*e1232323SMaria Kustova        ext = FieldsList()
178*e1232323SMaria Kustova        offset = struct.calcsize(header['header_length'][0].fmt) + \
179*e1232323SMaria Kustova                 header['header_length'][0].offset
180*e1232323SMaria Kustova
181*e1232323SMaria Kustova        if backing_file_fmt is not None:
182*e1232323SMaria Kustova            # Calculation of the free space available in the first cluster
183*e1232323SMaria Kustova            end_of_extension_area_len = 2 * UINT32_S
184*e1232323SMaria Kustova            high_border = (header['backing_file_offset'][0].value or
185*e1232323SMaria Kustova                           ((1 << header['cluster_bits'][0].value) - 1)) - \
186*e1232323SMaria Kustova                end_of_extension_area_len
187*e1232323SMaria Kustova            free_space = high_border - offset
188*e1232323SMaria Kustova            ext_size = 2 * UINT32_S + ((len(backing_file_fmt) + 7) & ~7)
189*e1232323SMaria Kustova
190*e1232323SMaria Kustova            if free_space >= ext_size:
191*e1232323SMaria Kustova                ext_data_len = len(backing_file_fmt)
192*e1232323SMaria Kustova                ext_data_fmt = '>' + str(ext_data_len) + 's'
193*e1232323SMaria Kustova                ext_padding_len = 7 - (ext_data_len - 1) % 8
194*e1232323SMaria Kustova                ext = FieldsList([
195*e1232323SMaria Kustova                    ['>I', offset, 0xE2792ACA, 'ext_magic'],
196*e1232323SMaria Kustova                    ['>I', offset + UINT32_S, ext_data_len, 'ext_length'],
197*e1232323SMaria Kustova                    [ext_data_fmt, offset + UINT32_S * 2, backing_file_fmt,
198*e1232323SMaria Kustova                     'bf_format']
199*e1232323SMaria Kustova                ])
200*e1232323SMaria Kustova                offset = ext['bf_format'][0].offset + \
201*e1232323SMaria Kustova                         struct.calcsize(ext['bf_format'][0].fmt) + \
202*e1232323SMaria Kustova                         ext_padding_len
203*e1232323SMaria Kustova        return (ext, offset)
204*e1232323SMaria Kustova
205*e1232323SMaria Kustova    @staticmethod
206*e1232323SMaria Kustova    def _feature_name_table(header, offset):
207*e1232323SMaria Kustova        """Generate a random header extension for names of features used in
208*e1232323SMaria Kustova        the image.
209*e1232323SMaria Kustova        """
210*e1232323SMaria Kustova        def gen_feat_ids():
211*e1232323SMaria Kustova            """Return random feature type and feature bit."""
212*e1232323SMaria Kustova            return (random.randint(0, 2), random.randint(0, 63))
213*e1232323SMaria Kustova
214*e1232323SMaria Kustova        end_of_extension_area_len = 2 * UINT32_S
215*e1232323SMaria Kustova        high_border = (header['backing_file_offset'][0].value or
216*e1232323SMaria Kustova                       (1 << header['cluster_bits'][0].value) - 1) - \
217*e1232323SMaria Kustova            end_of_extension_area_len
218*e1232323SMaria Kustova        free_space = high_border - offset
219*e1232323SMaria Kustova        # Sum of sizes of 'magic' and 'length' header extension fields
220*e1232323SMaria Kustova        ext_header_len = 2 * UINT32_S
221*e1232323SMaria Kustova        fnt_entry_size = 6 * UINT64_S
222*e1232323SMaria Kustova        num_fnt_entries = min(10, (free_space - ext_header_len) /
223*e1232323SMaria Kustova                              fnt_entry_size)
224*e1232323SMaria Kustova        if not num_fnt_entries == 0:
225*e1232323SMaria Kustova            feature_tables = []
226*e1232323SMaria Kustova            feature_ids = []
227*e1232323SMaria Kustova            inner_offset = offset + ext_header_len
228*e1232323SMaria Kustova            feat_name = 'some cool feature'
229*e1232323SMaria Kustova            while len(feature_tables) < num_fnt_entries * 3:
230*e1232323SMaria Kustova                feat_type, feat_bit = gen_feat_ids()
231*e1232323SMaria Kustova                # Remove duplicates
232*e1232323SMaria Kustova                while (feat_type, feat_bit) in feature_ids:
233*e1232323SMaria Kustova                    feat_type, feat_bit = gen_feat_ids()
234*e1232323SMaria Kustova                feature_ids.append((feat_type, feat_bit))
235*e1232323SMaria Kustova                feat_fmt = '>' + str(len(feat_name)) + 's'
236*e1232323SMaria Kustova                feature_tables += [['B', inner_offset,
237*e1232323SMaria Kustova                                    feat_type, 'feature_type'],
238*e1232323SMaria Kustova                                   ['B', inner_offset + 1, feat_bit,
239*e1232323SMaria Kustova                                    'feature_bit_number'],
240*e1232323SMaria Kustova                                   [feat_fmt, inner_offset + 2,
241*e1232323SMaria Kustova                                    feat_name, 'feature_name']
242*e1232323SMaria Kustova                ]
243*e1232323SMaria Kustova                inner_offset += fnt_entry_size
244*e1232323SMaria Kustova            # No padding for the extension is necessary, because
245*e1232323SMaria Kustova            # the extension length is multiple of 8
246*e1232323SMaria Kustova            ext = FieldsList([
247*e1232323SMaria Kustova                ['>I', offset, 0x6803f857, 'ext_magic'],
248*e1232323SMaria Kustova                # One feature table contains 3 fields and takes 48 bytes
249*e1232323SMaria Kustova                ['>I', offset + UINT32_S, len(feature_tables) / 3 * 48,
250*e1232323SMaria Kustova                 'ext_length']
251*e1232323SMaria Kustova            ] + feature_tables)
252*e1232323SMaria Kustova            offset = inner_offset
253*e1232323SMaria Kustova        else:
254*e1232323SMaria Kustova            ext = FieldsList()
255*e1232323SMaria Kustova
256*e1232323SMaria Kustova        return (ext, offset)
257*e1232323SMaria Kustova
258*e1232323SMaria Kustova    @staticmethod
259*e1232323SMaria Kustova    def _end_of_extension_area(offset):
260*e1232323SMaria Kustova        """Generate a mandatory header extension marking end of header
261*e1232323SMaria Kustova        extensions.
262*e1232323SMaria Kustova        """
263*e1232323SMaria Kustova        ext = FieldsList([
264*e1232323SMaria Kustova            ['>I', offset, 0, 'ext_magic'],
265*e1232323SMaria Kustova            ['>I', offset + UINT32_S, 0, 'ext_length']
266*e1232323SMaria Kustova        ])
267*e1232323SMaria Kustova        return ext
268*e1232323SMaria Kustova
269*e1232323SMaria Kustova    def __init__(self, backing_file_name=None, backing_file_fmt=None):
270*e1232323SMaria Kustova        """Create a random valid qcow2 image with the correct inner structure
271*e1232323SMaria Kustova        and allowable values.
272*e1232323SMaria Kustova        """
273*e1232323SMaria Kustova        # Image size is saved as an attribute for the runner needs
274*e1232323SMaria Kustova        cluster_bits, self.image_size = self._size_params()
275*e1232323SMaria Kustova        # Saved as an attribute, because it's necessary for writing
276*e1232323SMaria Kustova        self.cluster_size = 1 << cluster_bits
277*e1232323SMaria Kustova        self.header = self._header(cluster_bits, self.image_size,
278*e1232323SMaria Kustova                                   backing_file_name)
279*e1232323SMaria Kustova        self.backing_file_name = self._backing_file_name(self.header,
280*e1232323SMaria Kustova                                                         backing_file_name)
281*e1232323SMaria Kustova        self.backing_file_format, \
282*e1232323SMaria Kustova            offset = self._backing_file_format(self.header,
283*e1232323SMaria Kustova                                               backing_file_fmt)
284*e1232323SMaria Kustova        self.feature_name_table, \
285*e1232323SMaria Kustova            offset = self._feature_name_table(self.header, offset)
286*e1232323SMaria Kustova        self.end_of_extension_area = self._end_of_extension_area(offset)
287*e1232323SMaria Kustova        # Container for entire image
288*e1232323SMaria Kustova        self.data = FieldsList()
289*e1232323SMaria Kustova        # Percentage of fields will be fuzzed
290*e1232323SMaria Kustova        self.bias = random.uniform(0.2, 0.5)
291*e1232323SMaria Kustova
292*e1232323SMaria Kustova    def __iter__(self):
293*e1232323SMaria Kustova        return iter([self.header,
294*e1232323SMaria Kustova                     self.backing_file_format,
295*e1232323SMaria Kustova                     self.feature_name_table,
296*e1232323SMaria Kustova                     self.end_of_extension_area,
297*e1232323SMaria Kustova                     self.backing_file_name])
298*e1232323SMaria Kustova
299*e1232323SMaria Kustova    def _join(self):
300*e1232323SMaria Kustova        """Join all image structure elements as header, tables, etc in one
301*e1232323SMaria Kustova        list of fields.
302*e1232323SMaria Kustova        """
303*e1232323SMaria Kustova        if len(self.data) == 0:
304*e1232323SMaria Kustova            for v in self:
305*e1232323SMaria Kustova                self.data += v
306*e1232323SMaria Kustova
307*e1232323SMaria Kustova    def fuzz(self, fields_to_fuzz=None):
308*e1232323SMaria Kustova        """Fuzz an image by corrupting values of a random subset of its fields.
309*e1232323SMaria Kustova
310*e1232323SMaria Kustova        Without parameters the method fuzzes an entire image.
311*e1232323SMaria Kustova        If 'fields_to_fuzz' is specified then only fields in this list will be
312*e1232323SMaria Kustova        fuzzed. 'fields_to_fuzz' can contain both individual fields and more
313*e1232323SMaria Kustova        general image elements as a header or tables.
314*e1232323SMaria Kustova        In the first case the field will be fuzzed always.
315*e1232323SMaria Kustova        In the second a random subset of fields will be selected and fuzzed.
316*e1232323SMaria Kustova        """
317*e1232323SMaria Kustova        def coin():
318*e1232323SMaria Kustova            """Return boolean value proportional to a portion of fields to be
319*e1232323SMaria Kustova            fuzzed.
320*e1232323SMaria Kustova            """
321*e1232323SMaria Kustova            return random.random() < self.bias
322*e1232323SMaria Kustova
323*e1232323SMaria Kustova        if fields_to_fuzz is None:
324*e1232323SMaria Kustova            self._join()
325*e1232323SMaria Kustova            for field in self.data:
326*e1232323SMaria Kustova                if coin():
327*e1232323SMaria Kustova                    field.value = getattr(fuzz, field.name)(field.value)
328*e1232323SMaria Kustova        else:
329*e1232323SMaria Kustova            for item in fields_to_fuzz:
330*e1232323SMaria Kustova                if len(item) == 1:
331*e1232323SMaria Kustova                    for field in getattr(self, item[0]):
332*e1232323SMaria Kustova                        if coin():
333*e1232323SMaria Kustova                            field.value = getattr(fuzz,
334*e1232323SMaria Kustova                                                  field.name)(field.value)
335*e1232323SMaria Kustova                else:
336*e1232323SMaria Kustova                    for field in getattr(self, item[0])[item[1]]:
337*e1232323SMaria Kustova                        try:
338*e1232323SMaria Kustova                            field.value = getattr(fuzz, field.name)(
339*e1232323SMaria Kustova                                field.value)
340*e1232323SMaria Kustova                        except AttributeError:
341*e1232323SMaria Kustova                            # Some fields can be skipped depending on
342*e1232323SMaria Kustova                            # references, e.g. FNT header extension is not
343*e1232323SMaria Kustova                            # generated for a feature mask header field
344*e1232323SMaria Kustova                            # equal to zero
345*e1232323SMaria Kustova                            pass
346*e1232323SMaria Kustova
347*e1232323SMaria Kustova    def write(self, filename):
348*e1232323SMaria Kustova        """Write an entire image to the file."""
349*e1232323SMaria Kustova        image_file = open(filename, 'w')
350*e1232323SMaria Kustova        self._join()
351*e1232323SMaria Kustova        for field in self.data:
352*e1232323SMaria Kustova            image_file.seek(field.offset)
353*e1232323SMaria Kustova            image_file.write(struct.pack(field.fmt, field.value))
354*e1232323SMaria Kustova        image_file.seek(0, 2)
355*e1232323SMaria Kustova        size = image_file.tell()
356*e1232323SMaria Kustova        rounded = (size + self.cluster_size - 1) & ~(self.cluster_size - 1)
357*e1232323SMaria Kustova        if rounded > size:
358*e1232323SMaria Kustova            image_file.seek(rounded - 1)
359*e1232323SMaria Kustova            image_file.write("\0")
360*e1232323SMaria Kustova        image_file.close()
361*e1232323SMaria Kustova
362*e1232323SMaria Kustova
363*e1232323SMaria Kustovadef create_image(test_img_path, backing_file_name=None, backing_file_fmt=None,
364*e1232323SMaria Kustova                 fields_to_fuzz=None):
365*e1232323SMaria Kustova    """Create a fuzzed image and write it to the specified file."""
366*e1232323SMaria Kustova    image = Image(backing_file_name, backing_file_fmt)
367*e1232323SMaria Kustova    image.fuzz(fields_to_fuzz)
368*e1232323SMaria Kustova    image.write(test_img_path)
369*e1232323SMaria Kustova    return image.image_size
370