Skip to content

Commit 022b5b5

Browse files
authored
Merge pull request #10 from stackhpc/yoga-OSSA-2023-002
Enforce image safety during image_conversion
2 parents 385e70d + bf9ee39 commit 022b5b5

File tree

3 files changed

+82
-0
lines changed

3 files changed

+82
-0
lines changed

glance/async_/flows/plugins/image_conversion.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,29 @@ def _execute(self, action, file_path, **kwargs):
117117
virtual_size = metadata.get('virtual-size', 0)
118118
action.set_image_attribute(virtual_size=virtual_size)
119119

120+
if 'backing-filename' in metadata:
121+
LOG.warning('Refusing to process QCOW image with a backing file')
122+
raise RuntimeError(
123+
'QCOW images with backing files are not allowed')
124+
125+
if metadata.get('format') == 'vmdk':
126+
create_type = metadata.get(
127+
'format-specific', {}).get(
128+
'data', {}).get('create-type')
129+
allowed = CONF.image_format.vmdk_allowed_types
130+
if not create_type:
131+
raise RuntimeError(_('Unable to determine VMDK create-type'))
132+
if not len(allowed):
133+
LOG.warning(_('Refusing to process VMDK file as '
134+
'vmdk_allowed_types is empty'))
135+
raise RuntimeError(_('Image is a VMDK, but no VMDK createType '
136+
'is specified'))
137+
if create_type not in allowed:
138+
LOG.warning(_('Refusing to process VMDK file with create-type '
139+
'of %r which is not in allowed set of: %s'),
140+
create_type, ','.join(allowed))
141+
raise RuntimeError(_('Invalid VMDK create-type specified'))
142+
120143
if source_format == target_format:
121144
LOG.debug("Source is already in target format, "
122145
"not doing conversion for %s", self.image_id)

glance/common/config.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,18 @@
9999
"image attribute"),
100100
deprecated_opts=[cfg.DeprecatedOpt('disk_formats',
101101
group='DEFAULT')]),
102+
cfg.ListOpt('vmdk_allowed_types',
103+
default=['streamOptimized', 'monolithicSparse'],
104+
help=_("A list of strings describing allowed VMDK "
105+
"'create-type' subformats that will be allowed. "
106+
"This is recommended to only include "
107+
"single-file-with-sparse-header variants to avoid "
108+
"potential host file exposure due to processing named "
109+
"extents. If this list is empty, then no VDMK image "
110+
"types allowed. Note that this is currently only "
111+
"checked during image conversion (if enabled), and "
112+
"limits the types of VMDK images we will convert "
113+
"from.")),
102114
]
103115
task_opts = [
104116
cfg.IntOpt('task_time_to_live',

glance/tests/unit/async_/flows/plugins/test_image_conversion.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,53 @@ def test_image_convert_inspection_reports_error(self):
173173
# Make sure we did not update the image
174174
self.img_repo.save.assert_not_called()
175175

176+
def test_image_convert_invalid_qcow(self):
177+
data = {'format': 'qcow2',
178+
'backing-filename': '/etc/hosts'}
179+
180+
convert = self._setup_image_convert_info_fail()
181+
with mock.patch.object(processutils, 'execute') as exc_mock:
182+
exc_mock.return_value = json.dumps(data), ''
183+
e = self.assertRaises(RuntimeError,
184+
convert.execute, 'file:///test/path.qcow')
185+
self.assertEqual('QCOW images with backing files are not allowed',
186+
str(e))
187+
188+
def _test_image_convert_invalid_vmdk(self):
189+
data = {'format': 'vmdk',
190+
'format-specific': {
191+
'data': {
192+
'create-type': 'monolithicFlat',
193+
}}}
194+
195+
convert = self._setup_image_convert_info_fail()
196+
with mock.patch.object(processutils, 'execute') as exc_mock:
197+
exc_mock.return_value = json.dumps(data), ''
198+
convert.execute('file:///test/path.vmdk')
199+
200+
def test_image_convert_invalid_vmdk(self):
201+
e = self.assertRaises(RuntimeError,
202+
self._test_image_convert_invalid_vmdk)
203+
self.assertEqual('Invalid VMDK create-type specified', str(e))
204+
205+
def test_image_convert_valid_vmdk_no_types(self):
206+
with mock.patch.object(CONF.image_format, 'vmdk_allowed_types',
207+
new=[]):
208+
# We make it past the VMDK check and fail because our file
209+
# does not exist
210+
e = self.assertRaises(RuntimeError,
211+
self._test_image_convert_invalid_vmdk)
212+
self.assertEqual('Image is a VMDK, but no VMDK createType is '
213+
'specified', str(e))
214+
215+
def test_image_convert_valid_vmdk(self):
216+
with mock.patch.object(CONF.image_format, 'vmdk_allowed_types',
217+
new=['monolithicSparse', 'monolithicFlat']):
218+
# We make it past the VMDK check and fail because our file
219+
# does not exist
220+
self.assertRaises(FileNotFoundError,
221+
self._test_image_convert_invalid_vmdk)
222+
176223
def test_image_convert_fails(self):
177224
convert = self._setup_image_convert_info_fail()
178225
with mock.patch.object(processutils, 'execute') as exc_mock:

0 commit comments

Comments
 (0)