Skip to content

Commit e6fa8c0

Browse files
authored
Merge pull request #20 from stackhpc/xena-OSSA-2023-002
[stable-only][cve] Check VMDK create-type against an allowed list
2 parents ba5b7ed + 8b5b0f1 commit e6fa8c0

File tree

3 files changed

+86
-0
lines changed

3 files changed

+86
-0
lines changed

nova/conf/compute.py

+9
Original file line numberDiff line numberDiff line change
@@ -1007,6 +1007,15 @@
10071007
* ``[scheduler]query_placement_for_image_type_support`` - enables
10081008
filtering computes based on supported image types, which is required
10091009
to be enabled for this to take effect.
1010+
"""),
1011+
cfg.ListOpt('vmdk_allowed_types',
1012+
default=['streamOptimized', 'monolithicSparse'],
1013+
help="""
1014+
A list of strings describing allowed VMDK "create-type" subformats
1015+
that will be allowed. This is recommended to only include
1016+
single-file-with-sparse-header variants to avoid potential host file
1017+
exposure due to processing named extents. If this list is empty, then no
1018+
form of VMDK image will be allowed.
10101019
"""),
10111020
]
10121021

nova/tests/unit/virt/test_images.py

+46
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
import mock
1818
from oslo_concurrency import processutils
19+
from oslo_serialization import jsonutils
20+
from oslo_utils import imageutils
1921

2022
from nova.compute import utils as compute_utils
2123
from nova import exception
@@ -135,3 +137,47 @@ def test_convert_image_without_direct_io_support(self, mock_execute,
135137
'-O', 'out_format', '-f', 'in_format', 'source', 'dest')
136138
mock_disk_op_sema.__enter__.assert_called_once()
137139
self.assertTupleEqual(expected, mock_execute.call_args[0])
140+
141+
def test_convert_image_vmdk_allowed_list_checking(self):
142+
info = {'format': 'vmdk',
143+
'format-specific': {
144+
'type': 'vmdk',
145+
'data': {
146+
'create-type': 'monolithicFlat',
147+
}}}
148+
149+
# If the format is not in the allowed list, we should get an error
150+
self.assertRaises(exception.ImageUnacceptable,
151+
images.check_vmdk_image, 'foo',
152+
imageutils.QemuImgInfo(jsonutils.dumps(info),
153+
format='json'))
154+
155+
# With the format in the allowed list, no error
156+
self.flags(vmdk_allowed_types=['streamOptimized', 'monolithicFlat',
157+
'monolithicSparse'],
158+
group='compute')
159+
images.check_vmdk_image('foo',
160+
imageutils.QemuImgInfo(jsonutils.dumps(info),
161+
format='json'))
162+
163+
# With an empty list, allow nothing
164+
self.flags(vmdk_allowed_types=[], group='compute')
165+
self.assertRaises(exception.ImageUnacceptable,
166+
images.check_vmdk_image, 'foo',
167+
imageutils.QemuImgInfo(jsonutils.dumps(info),
168+
format='json'))
169+
170+
@mock.patch.object(images, 'fetch')
171+
@mock.patch('nova.privsep.qemu.unprivileged_qemu_img_info')
172+
def test_fetch_checks_vmdk_rules(self, mock_info, mock_fetch):
173+
info = {'format': 'vmdk',
174+
'format-specific': {
175+
'type': 'vmdk',
176+
'data': {
177+
'create-type': 'monolithicFlat',
178+
}}}
179+
mock_info.return_value = jsonutils.dumps(info)
180+
with mock.patch('os.path.exists', return_value=True):
181+
e = self.assertRaises(exception.ImageUnacceptable,
182+
images.fetch_to_raw, None, 'foo', 'anypath')
183+
self.assertIn('Invalid VMDK create-type specified', str(e))

nova/virt/images.py

+31
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,34 @@ def get_info(context, image_href):
110110
return IMAGE_API.get(context, image_href)
111111

112112

113+
def check_vmdk_image(image_id, data):
114+
# Check some rules about VMDK files. Specifically we want to make
115+
# sure that the "create-type" of the image is one that we allow.
116+
# Some types of VMDK files can reference files outside the disk
117+
# image and we do not want to allow those for obvious reasons.
118+
119+
types = CONF.compute.vmdk_allowed_types
120+
121+
if not len(types):
122+
LOG.warning('Refusing to allow VMDK image as vmdk_allowed_'
123+
'types is empty')
124+
msg = _('Invalid VMDK create-type specified')
125+
raise exception.ImageUnacceptable(image_id=image_id, reason=msg)
126+
127+
try:
128+
create_type = data.format_specific['data']['create-type']
129+
except KeyError:
130+
msg = _('Unable to determine VMDK create-type')
131+
raise exception.ImageUnacceptable(image_id=image_id, reason=msg)
132+
133+
if create_type not in CONF.compute.vmdk_allowed_types:
134+
LOG.warning('Refusing to process VMDK file with create-type of %r '
135+
'which is not in allowed set of: %s', create_type,
136+
','.join(CONF.compute.vmdk_allowed_types))
137+
msg = _('Invalid VMDK create-type specified')
138+
raise exception.ImageUnacceptable(image_id=image_id, reason=msg)
139+
140+
113141
def fetch_to_raw(context, image_href, path, trusted_certs=None):
114142
path_tmp = "%s.part" % path
115143
fetch(context, image_href, path_tmp, trusted_certs)
@@ -129,6 +157,9 @@ def fetch_to_raw(context, image_href, path, trusted_certs=None):
129157
reason=(_("fmt=%(fmt)s backed by: %(backing_file)s") %
130158
{'fmt': fmt, 'backing_file': backing_file}))
131159

160+
if fmt == 'vmdk':
161+
check_vmdk_image(image_href, data)
162+
132163
if fmt != "raw" and CONF.force_raw_images:
133164
staged = "%s.converted" % path
134165
LOG.debug("%s was %s, converting to raw", image_href, fmt)

0 commit comments

Comments
 (0)