Skip to content

[stable-only][cve] Check VMDK create-type against an allowed list #20

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jan 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions nova/conf/compute.py
Original file line number Diff line number Diff line change
Expand Up @@ -1007,6 +1007,15 @@
* ``[scheduler]query_placement_for_image_type_support`` - enables
filtering computes based on supported image types, which is required
to be enabled for this to take effect.
"""),
cfg.ListOpt('vmdk_allowed_types',
default=['streamOptimized', 'monolithicSparse'],
help="""
A list of strings describing allowed VMDK "create-type" subformats
that will be allowed. This is recommended to only include
single-file-with-sparse-header variants to avoid potential host file
exposure due to processing named extents. If this list is empty, then no
form of VMDK image will be allowed.
"""),
]

Expand Down
46 changes: 46 additions & 0 deletions nova/tests/unit/virt/test_images.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

import mock
from oslo_concurrency import processutils
from oslo_serialization import jsonutils
from oslo_utils import imageutils

from nova.compute import utils as compute_utils
from nova import exception
Expand Down Expand Up @@ -135,3 +137,47 @@ def test_convert_image_without_direct_io_support(self, mock_execute,
'-O', 'out_format', '-f', 'in_format', 'source', 'dest')
mock_disk_op_sema.__enter__.assert_called_once()
self.assertTupleEqual(expected, mock_execute.call_args[0])

def test_convert_image_vmdk_allowed_list_checking(self):
info = {'format': 'vmdk',
'format-specific': {
'type': 'vmdk',
'data': {
'create-type': 'monolithicFlat',
}}}

# If the format is not in the allowed list, we should get an error
self.assertRaises(exception.ImageUnacceptable,
images.check_vmdk_image, 'foo',
imageutils.QemuImgInfo(jsonutils.dumps(info),
format='json'))

# With the format in the allowed list, no error
self.flags(vmdk_allowed_types=['streamOptimized', 'monolithicFlat',
'monolithicSparse'],
group='compute')
images.check_vmdk_image('foo',
imageutils.QemuImgInfo(jsonutils.dumps(info),
format='json'))

# With an empty list, allow nothing
self.flags(vmdk_allowed_types=[], group='compute')
self.assertRaises(exception.ImageUnacceptable,
images.check_vmdk_image, 'foo',
imageutils.QemuImgInfo(jsonutils.dumps(info),
format='json'))

@mock.patch.object(images, 'fetch')
@mock.patch('nova.privsep.qemu.unprivileged_qemu_img_info')
def test_fetch_checks_vmdk_rules(self, mock_info, mock_fetch):
info = {'format': 'vmdk',
'format-specific': {
'type': 'vmdk',
'data': {
'create-type': 'monolithicFlat',
}}}
mock_info.return_value = jsonutils.dumps(info)
with mock.patch('os.path.exists', return_value=True):
e = self.assertRaises(exception.ImageUnacceptable,
images.fetch_to_raw, None, 'foo', 'anypath')
self.assertIn('Invalid VMDK create-type specified', str(e))
31 changes: 31 additions & 0 deletions nova/virt/images.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,34 @@ def get_info(context, image_href):
return IMAGE_API.get(context, image_href)


def check_vmdk_image(image_id, data):
# Check some rules about VMDK files. Specifically we want to make
# sure that the "create-type" of the image is one that we allow.
# Some types of VMDK files can reference files outside the disk
# image and we do not want to allow those for obvious reasons.

types = CONF.compute.vmdk_allowed_types

if not len(types):
LOG.warning('Refusing to allow VMDK image as vmdk_allowed_'
'types is empty')
msg = _('Invalid VMDK create-type specified')
raise exception.ImageUnacceptable(image_id=image_id, reason=msg)

try:
create_type = data.format_specific['data']['create-type']
except KeyError:
msg = _('Unable to determine VMDK create-type')
raise exception.ImageUnacceptable(image_id=image_id, reason=msg)

if create_type not in CONF.compute.vmdk_allowed_types:
LOG.warning('Refusing to process VMDK file with create-type of %r '
'which is not in allowed set of: %s', create_type,
','.join(CONF.compute.vmdk_allowed_types))
msg = _('Invalid VMDK create-type specified')
raise exception.ImageUnacceptable(image_id=image_id, reason=msg)


def fetch_to_raw(context, image_href, path, trusted_certs=None):
path_tmp = "%s.part" % path
fetch(context, image_href, path_tmp, trusted_certs)
Expand All @@ -129,6 +157,9 @@ def fetch_to_raw(context, image_href, path, trusted_certs=None):
reason=(_("fmt=%(fmt)s backed by: %(backing_file)s") %
{'fmt': fmt, 'backing_file': backing_file}))

if fmt == 'vmdk':
check_vmdk_image(image_href, data)

if fmt != "raw" and CONF.force_raw_images:
staged = "%s.converted" % path
LOG.debug("%s was %s, converting to raw", image_href, fmt)
Expand Down