@@ -54,7 +54,13 @@ def tearDown(self):
54
54
except Exception :
55
55
pass
56
56
57
- def _create_iso (self , image_size , subformat = 'iso-9660' ):
57
+ def _create_iso (self , image_size , subformat = '9660' ):
58
+ """Create an ISO file of the given size.
59
+
60
+ :param image_size: The size of the image to create in bytes
61
+ :param subformat: The subformat to use, if any
62
+ """
63
+
58
64
# these tests depend on mkisofs
59
65
# being installed and in the path,
60
66
# if it is not installed, skip
@@ -86,12 +92,22 @@ def _create_iso(self, image_size, subformat='iso-9660'):
86
92
'dd if=/dev/zero of=%s bs=1M count=%i' % (fn , size ),
87
93
shell = True )
88
94
subprocess .check_output (
89
- '%s -o %s - V "TEST" -J -r %s' % (base_cmd , fn , fn ),
95
+ '%s -V "TEST" -o %s %s' % (base_cmd , fn , fn ),
90
96
shell = True )
91
97
return fn
92
98
93
- def _create_img (self , fmt , size , subformat = None , options = None ,
94
- backing_file = None ):
99
+ def _create_img (
100
+ self , fmt , size , subformat = None , options = None ,
101
+ backing_file = None ):
102
+ """Create an image file of the given format and size.
103
+
104
+ :param fmt: The format to create
105
+ :param size: The size of the image to create in bytes
106
+ :param subformat: The subformat to use, if any
107
+ :param options: A dictionary of options to pass to the format
108
+ :param backing_file: The backing file to use, if any
109
+ """
110
+
95
111
if fmt == 'iso' :
96
112
return self ._create_iso (size , subformat )
97
113
@@ -177,6 +193,13 @@ def _test_format_at_block_size(self, format_name, img, block_size):
177
193
178
194
def _test_format_at_image_size (self , format_name , image_size ,
179
195
subformat = None ):
196
+ """Test the format inspector for the given format at the
197
+ given image size.
198
+
199
+ :param format_name: The format to test
200
+ :param image_size: The size of the image to create in bytes
201
+ :param subformat: The subformat to use, if any
202
+ """
180
203
img = self ._create_img (format_name , image_size , subformat = subformat )
181
204
182
205
# Some formats have internal alignment restrictions making this not
@@ -185,7 +208,15 @@ def _test_format_at_image_size(self, format_name, image_size,
185
208
186
209
# Read the format in various sizes, some of which will read whole
187
210
# sections in a single read, others will be completely unaligned, etc.
188
- for block_size in (64 * units .Ki , 512 , 17 , 1 * units .Mi ):
211
+ block_sizes = [64 * units .Ki , 1 * units .Mi ]
212
+ # ISO images have a 32KB system area at the beginning of the image
213
+ # as a result reading that in 17 or 512 byte blocks takes too long,
214
+ # causing the test to fail. The 64KiB block size is enough to read
215
+ # the system area and header in a single read. the 1MiB block size
216
+ # adds very little time to the test so we include it.
217
+ if format_name != 'iso' :
218
+ block_sizes .extend ([17 , 512 ])
219
+ for block_size in block_sizes :
189
220
fmt = self ._test_format_at_block_size (format_name , img , block_size )
190
221
self .assertTrue (fmt .format_match ,
191
222
'Failed to match %s at size %i block %i' % (
@@ -210,14 +241,63 @@ def test_qcow2(self):
210
241
self ._test_format ('qcow2' )
211
242
212
243
def test_iso_9660 (self ):
213
- # reproduce iso-9660 format regression
214
- self .assertRaises (
215
- TypeError , self ._test_format , 'iso' , subformat = 'iso-9660' )
216
-
217
- def test_udf (self ):
218
- # reproduce udf format regression
219
- self .assertRaises (
220
- TypeError , self ._test_format , 'iso' , subformat = 'udf' )
244
+ self ._test_format ('iso' , subformat = '9660' )
245
+
246
+ def test_iso_udf (self ):
247
+ self ._test_format ('iso' , subformat = 'udf' )
248
+
249
+ def _generate_bad_iso (self ):
250
+ # we want to emulate a malicious user who uploads a an
251
+ # ISO file has a qcow2 header in the system area
252
+ # of the ISO file
253
+ # we will create a qcow2 image and an ISO file
254
+ # and then copy the qcow2 header to the ISO file
255
+ # e.g.
256
+ # mkisofs -o orig.iso /etc/resolv.conf
257
+ # qemu-img create orig.qcow2 -f qcow2 64M
258
+ # dd if=orig.qcow2 of=outcome bs=32K count=1
259
+ # dd if=orig.iso of=outcome bs=32K skip=1 seek=1
260
+
261
+ qcow = self ._create_img ('qcow2' , 10 * units .Mi )
262
+ iso = self ._create_iso (64 * units .Mi , subformat = '9660' )
263
+ # first ensure the files are valid
264
+ iso_fmt = self ._test_format_at_block_size ('iso' , iso , 4 * units .Ki )
265
+ self .assertTrue (iso_fmt .format_match )
266
+ qcow_fmt = self ._test_format_at_block_size ('qcow2' , qcow , 4 * units .Ki )
267
+ self .assertTrue (qcow_fmt .format_match )
268
+ # now copy the qcow2 header to an ISO file
269
+ prefix = TEST_IMAGE_PREFIX
270
+ prefix += '-bad-'
271
+ fn = tempfile .mktemp (prefix = prefix , suffix = '.iso' )
272
+ self ._created_files .append (fn )
273
+ subprocess .check_output (
274
+ 'dd if=%s of=%s bs=32K count=1' % (qcow , fn ),
275
+ shell = True )
276
+ subprocess .check_output (
277
+ 'dd if=%s of=%s bs=32K skip=1 seek=1' % (iso , fn ),
278
+ shell = True )
279
+ return qcow , iso , fn
280
+
281
+ def test_bad_iso_qcow2 (self ):
282
+
283
+ _ , _ , fn = self ._generate_bad_iso ()
284
+
285
+ iso_check = self ._test_format_at_block_size ('iso' , fn , 4 * units .Ki )
286
+ qcow_check = self ._test_format_at_block_size ('qcow2' , fn , 4 * units .Ki )
287
+ # this system area of the ISO file is not considered part of the format
288
+ # the qcow2 header is in the system area of the ISO file
289
+ # so the ISO file is still valid
290
+ self .assertTrue (iso_check .format_match )
291
+ # the qcow2 header is in the system area of the ISO file
292
+ # but that will be parsed by the qcow2 format inspector
293
+ # and it will match
294
+ self .assertTrue (qcow_check .format_match )
295
+ # if we call format_inspector.detect_file_format it should detect
296
+ # and raise an exception because both match internally.
297
+ e = self .assertRaises (
298
+ format_inspector .ImageFormatError ,
299
+ format_inspector .detect_file_format , fn )
300
+ self .assertIn ('Multiple formats detected' , str (e ))
221
301
222
302
def test_vhd (self ):
223
303
self ._test_format ('vhd' )
0 commit comments