From 6bc37dcceca823998068167b49aec6def3112397 Mon Sep 17 00:00:00 2001 From: "Daniel P. Berrange" Date: Mon, 18 Apr 2016 16:32:19 +0000 Subject: [PATCH] virt: set address space & CPU time limits when running qemu-img This uses the new 'prlimit' parameter for oslo.concurrency execute method, to set an address space limit of 1GB and CPU time limit of 2 seconds, when running qemu-img. This is a re-implementation of the previously reverted commit commit da217205f53f9a38a573fb151898fbbeae41021d Author: Tristan Cacqueray Date: Wed Aug 5 17:17:04 2015 +0000 virt: Use preexec_fn to ulimit qemu-img info call NOTE (kchamart) [stable/liberty]: Add a check for the presence of 'ProcessLimits' attribute (which is only present in oslo.concurrency>=2.6.1; and a conditional check for 'prlimit' parameter in qemu_img_info() method. Upstream discussion[1][2] that led to merging this patch to stable/liberty branch. [1] http://lists.openstack.org/pipermail/openstack-dev/2016-September/104091.html [2] http://lists.openstack.org/pipermail/openstack-dev/2016-September/104303.html Closes-Bug: #1449062 Change-Id: I135b5242af1bfdcb0ea09a6fcda21fc03a6fbe7d (cherry picked from commit 068d851561addfefb2b812d91dc2011077cb6e1d) --- nova/tests/unit/virt/libvirt/test_driver.py | 7 +++-- nova/tests/unit/virt/libvirt/test_utils.py | 27 ++++++++++++------- nova/virt/images.py | 16 ++++++++++- ...y-limits-to-qemu-img-8813f7a333ebdf69.yaml | 8 ++++++ 4 files changed, 46 insertions(+), 12 deletions(-) create mode 100644 releasenotes/notes/apply-limits-to-qemu-img-8813f7a333ebdf69.yaml diff --git a/nova/tests/unit/virt/libvirt/test_driver.py b/nova/tests/unit/virt/libvirt/test_driver.py index daf4c128d947..fbb771785251 100644 --- a/nova/tests/unit/virt/libvirt/test_driver.py +++ b/nova/tests/unit/virt/libvirt/test_driver.py @@ -85,6 +85,7 @@ from nova.virt import fake from nova.virt import firewall as base_firewall from nova.virt import hardware from nova.virt.image import model as imgmodel +from nova.virt import images from nova.virt.libvirt import blockinfo from nova.virt.libvirt import config as vconfig from nova.virt.libvirt import driver as libvirt_driver @@ -7704,7 +7705,8 @@ class LibvirtConnTestCase(test.NoDBTestCase): self.mox.StubOutWithMock(utils, "execute") utils.execute('env', 'LC_ALL=C', 'LANG=C', 'qemu-img', 'info', - '/test/disk.local').AndReturn((ret, '')) + '/test/disk.local', prlimit=images.QEMU_IMG_LIMITS, + ).AndReturn((ret, '')) self.mox.ReplayAll() drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False) @@ -7812,7 +7814,8 @@ class LibvirtConnTestCase(test.NoDBTestCase): self.mox.StubOutWithMock(utils, "execute") utils.execute('env', 'LC_ALL=C', 'LANG=C', 'qemu-img', 'info', - '/test/disk.local').AndReturn((ret, '')) + '/test/disk.local', prlimit=images.QEMU_IMG_LIMITS, + ).AndReturn((ret, '')) self.mox.ReplayAll() conn_info = {'driver_volume_type': 'fake'} diff --git a/nova/tests/unit/virt/libvirt/test_utils.py b/nova/tests/unit/virt/libvirt/test_utils.py index 6f75a928f7bd..4cdde901e9d8 100644 --- a/nova/tests/unit/virt/libvirt/test_utils.py +++ b/nova/tests/unit/virt/libvirt/test_utils.py @@ -91,7 +91,8 @@ disk size: 96K mock_execute.return_value = (output, '') d_backing = libvirt_utils.get_disk_backing_file(path) mock_execute.assert_called_once_with('env', 'LC_ALL=C', 'LANG=C', - 'qemu-img', 'info', path) + 'qemu-img', 'info', path, + prlimit=images.QEMU_IMG_LIMITS) mock_exists.assert_called_once_with(path) self.assertIsNone(d_backing) @@ -99,7 +100,8 @@ disk size: 96K d_size = libvirt_utils.get_disk_size(path) self.assertEqual(expected_size, d_size) mock_execute.assert_called_once_with('env', 'LC_ALL=C', 'LANG=C', - 'qemu-img', 'info', path) + 'qemu-img', 'info', path, + prlimit=images.QEMU_IMG_LIMITS) @mock.patch('os.path.exists', return_value=True) def test_disk_size(self, mock_exists): @@ -145,7 +147,8 @@ blah BLAH: bb mock_execute.return_value = (example_output, '') image_info = images.qemu_img_info(path) mock_execute.assert_called_once_with('env', 'LC_ALL=C', 'LANG=C', - 'qemu-img', 'info', path) + 'qemu-img', 'info', path, + prlimit=images.QEMU_IMG_LIMITS) mock_exists.assert_called_once_with(path) self.assertEqual('disk.config', image_info.image) self.assertEqual('raw', image_info.file_format) @@ -167,7 +170,8 @@ backing file: /var/lib/nova/a328c7998805951a_2 mock_execute.return_value = (example_output, '') image_info = images.qemu_img_info(path) mock_execute.assert_called_once_with('env', 'LC_ALL=C', 'LANG=C', - 'qemu-img', 'info', path) + 'qemu-img', 'info', path, + prlimit=images.QEMU_IMG_LIMITS) mock_exists.assert_called_once_with(path) self.assertEqual('disk.config', image_info.image) self.assertEqual('qcow2', image_info.file_format) @@ -195,7 +199,8 @@ backing file: /var/lib/nova/a328c7998805951a_2 (actual path: /b/3a988059e51a_2) mock_execute.return_value = (example_output, '') image_info = images.qemu_img_info(path) mock_execute.assert_called_once_with('env', 'LC_ALL=C', 'LANG=C', - 'qemu-img', 'info', path) + 'qemu-img', 'info', path, + prlimit=images.QEMU_IMG_LIMITS) mock_exists.assert_called_once_with(path) self.assertEqual('disk.config', image_info.image) self.assertEqual('raw', image_info.file_format) @@ -223,7 +228,8 @@ junk stuff: bbb mock_execute.return_value = (example_output, '') image_info = images.qemu_img_info(path) mock_execute.assert_called_once_with('env', 'LC_ALL=C', 'LANG=C', - 'qemu-img', 'info', path) + 'qemu-img', 'info', path, + prlimit=images.QEMU_IMG_LIMITS) mock_exists.assert_called_once_with(path) self.assertEqual('disk.config', image_info.image) self.assertEqual('raw', image_info.file_format) @@ -247,7 +253,8 @@ ID TAG VM SIZE DATE VM CLOCK mock_execute.return_value = (example_output, '') image_info = images.qemu_img_info(path) mock_execute.assert_called_once_with('env', 'LC_ALL=C', 'LANG=C', - 'qemu-img', 'info', path) + 'qemu-img', 'info', path, + prlimit=images.QEMU_IMG_LIMITS) mock_exists.assert_called_once_with(path) self.assertEqual('disk.config', image_info.image) self.assertEqual('raw', image_info.file_format) @@ -283,7 +290,8 @@ ID TAG VM SIZE DATE VM CLOCK mock_execute.return_value = ('stdout', None) libvirt_utils.create_cow_image('/some/path', '/the/new/cow') expected_args = [(('env', 'LC_ALL=C', 'LANG=C', - 'qemu-img', 'info', '/some/path'),), + 'qemu-img', 'info', '/some/path'), + {'prlimit': images.QEMU_IMG_LIMITS}), (('qemu-img', 'create', '-f', 'qcow2', '-o', 'backing_file=/some/path', '/the/new/cow'),)] @@ -369,7 +377,8 @@ disk size: 4.4M mock_execute.return_value = (example_output, '') self.assertEqual(4592640, disk.get_disk_size('/some/path')) mock_execute.assert_called_once_with('env', 'LC_ALL=C', 'LANG=C', - 'qemu-img', 'info', path) + 'qemu-img', 'info', path, + prlimit=images.QEMU_IMG_LIMITS) mock_exists.assert_called_once_with(path) def test_copy_image(self): diff --git a/nova/virt/images.py b/nova/virt/images.py index 466bd270f162..7ce1992f0b17 100644 --- a/nova/virt/images.py +++ b/nova/virt/images.py @@ -25,6 +25,7 @@ from oslo_concurrency import processutils from oslo_config import cfg from oslo_log import log as logging from oslo_utils import fileutils +from oslo_utils import units from nova import exception from nova.i18n import _, _LE @@ -43,6 +44,16 @@ image_opts = [ CONF = cfg.CONF CONF.register_opts(image_opts) IMAGE_API = image.API() +QEMU_IMG_LIMITS = None + +try: + QEMU_IMG_LIMITS = processutils.ProcessLimits( + cpu_time=2, + address_space=1 * units.Gi) +except Exception: + LOG.error(_LE('Please upgrade to oslo.concurrency version ' + '2.6.1 -- this version has fixes for the ' + 'vulnerability CVE-2015-5162.')) def qemu_img_info(path, format=None): @@ -61,7 +72,10 @@ def qemu_img_info(path, format=None): if format is not None: cmd = cmd + ('-f', format) try: - out, err = utils.execute(*cmd) + if QEMU_IMG_LIMITS is not None: + out, err = utils.execute(*cmd, prlimit=QEMU_IMG_LIMITS) + else: + out, err = utils.execute(*cmd) except processutils.ProcessExecutionError as exp: msg = (_("qemu-img failed to execute on %(path)s : %(exp)s") % {'path': path, 'exp': exp}) diff --git a/releasenotes/notes/apply-limits-to-qemu-img-8813f7a333ebdf69.yaml b/releasenotes/notes/apply-limits-to-qemu-img-8813f7a333ebdf69.yaml new file mode 100644 index 000000000000..fc1dd25d117c --- /dev/null +++ b/releasenotes/notes/apply-limits-to-qemu-img-8813f7a333ebdf69.yaml @@ -0,0 +1,8 @@ +--- +security: + - The qemu-img tool now has resource limits applied + which prevent it from using more than 1GB of address + space or more than 2 seconds of CPU time. This provides + protection against denial of service attacks from + maliciously crafted or corrupted disk images. + oslo.concurrency>=2.6.1 is required for this fix.