Skip to content

Commit 232ba7a

Browse files
kk7dsJohnGarbutt
authored andcommitted
[stable-only][cve] Check VMDK create-type against an allowed list
NOTE(sbauza): Stable policy allows us to proactively merge a backport without waiting for the parent patch to be merged (exception to rule #4 in [1]. Marking [stable-only] in order to silence nova-tox-validate-backport [1] https://docs.openstack.org/project-team-guide/stable-branches.html#appropriate-fixes Conflicts vs victoria in: nova/conf/compute.py Related-Bug: #1996188 Change-Id: I5a399f1d3d702bfb76c067893e9c924904c8c360
1 parent 278248f commit 232ba7a

File tree

3 files changed

+86
-0
lines changed

3 files changed

+86
-0
lines changed

nova/conf/compute.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -956,6 +956,15 @@
956956
* Any integer >= 1 represents the maximum allowed. A value of 0 will cause the
957957
``nova-compute`` service to fail to start, as 0 disk devices is an invalid
958958
configuration that would prevent instances from being able to boot.
959+
"""),
960+
cfg.ListOpt('vmdk_allowed_types',
961+
default=['streamOptimized', 'monolithicSparse'],
962+
help="""
963+
A list of strings describing allowed VMDK "create-type" subformats
964+
that will be allowed. This is recommended to only include
965+
single-file-with-sparse-header variants to avoid potential host file
966+
exposure due to processing named extents. If this list is empty, then no
967+
form of VMDK image will be allowed.
959968
"""),
960969
]
961970

nova/tests/unit/virt/test_images.py

Lines changed: 46 additions & 0 deletions
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
import six
2022

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

nova/virt/images.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,34 @@ def get_info(context, image_href):
114114
return IMAGE_API.get(context, image_href)
115115

116116

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

164+
if fmt == 'vmdk':
165+
check_vmdk_image(image_href, data)
166+
136167
if fmt != "raw" and CONF.force_raw_images:
137168
staged = "%s.converted" % path
138169
LOG.debug("%s was %s, converting to raw", image_href, fmt)

0 commit comments

Comments
 (0)