Fix backing file detection in libvirt live snapshot
When doing a live snapshot, the libvirt driver creates an intermediate qcow2 file with the same backing file as the original disk. However, it calls qemu-img info without specifying the input format explicitly. An authenticated user can write data to a raw disk which will cause this code to misinterpret the disk as a qcow2 file with a user-specified backing file on the host, and return an arbitrary host file as the backing file. This bug does not appear to result in a data leak in this case, but this is hard to verify. It certainly results in corrupt output. Closes-Bug: #1524274 Change-Id: I11485f077d28f4e97529a691e55e3e3c0bea8872
This commit is contained in:
parent
5d6e0086b5
commit
915fdbbfb8
|
@ -32,11 +32,11 @@ def create_cow_image(backing_file, path):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def get_disk_size(path):
|
def get_disk_size(path, format=None):
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
def get_disk_backing_file(path):
|
def get_disk_backing_file(path, format=None):
|
||||||
return disk_backing_files.get(path, None)
|
return disk_backing_files.get(path, None)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -12558,7 +12558,7 @@ class LibvirtConnTestCase(test.NoDBTestCase):
|
||||||
|
|
||||||
image_meta = objects.ImageMeta.from_dict(self.test_image_meta)
|
image_meta = objects.ImageMeta.from_dict(self.test_image_meta)
|
||||||
drvr._live_snapshot(self.context, self.test_instance, guest,
|
drvr._live_snapshot(self.context, self.test_instance, guest,
|
||||||
srcfile, dstfile, "qcow2", image_meta)
|
srcfile, dstfile, "qcow2", "qcow2", image_meta)
|
||||||
|
|
||||||
mock_dom.XMLDesc.assert_called_once_with(flags=(
|
mock_dom.XMLDesc.assert_called_once_with(flags=(
|
||||||
fakelibvirt.VIR_DOMAIN_XML_INACTIVE |
|
fakelibvirt.VIR_DOMAIN_XML_INACTIVE |
|
||||||
|
@ -12569,8 +12569,9 @@ class LibvirtConnTestCase(test.NoDBTestCase):
|
||||||
fakelibvirt.VIR_DOMAIN_BLOCK_REBASE_REUSE_EXT |
|
fakelibvirt.VIR_DOMAIN_BLOCK_REBASE_REUSE_EXT |
|
||||||
fakelibvirt.VIR_DOMAIN_BLOCK_REBASE_SHALLOW))
|
fakelibvirt.VIR_DOMAIN_BLOCK_REBASE_SHALLOW))
|
||||||
|
|
||||||
mock_size.assert_called_once_with(srcfile)
|
mock_size.assert_called_once_with(srcfile, format="qcow2")
|
||||||
mock_backing.assert_called_once_with(srcfile, basename=False)
|
mock_backing.assert_called_once_with(srcfile, basename=False,
|
||||||
|
format="qcow2")
|
||||||
mock_create_cow.assert_called_once_with(bckfile, dltfile, 1004009)
|
mock_create_cow.assert_called_once_with(bckfile, dltfile, 1004009)
|
||||||
mock_chown.assert_called_once_with(dltfile, os.getuid())
|
mock_chown.assert_called_once_with(dltfile, os.getuid())
|
||||||
mock_snapshot.assert_called_once_with(dltfile, "qcow2",
|
mock_snapshot.assert_called_once_with(dltfile, "qcow2",
|
||||||
|
|
|
@ -45,7 +45,7 @@ CONF.register_opts(image_opts)
|
||||||
IMAGE_API = image.API()
|
IMAGE_API = image.API()
|
||||||
|
|
||||||
|
|
||||||
def qemu_img_info(path):
|
def qemu_img_info(path, format=None):
|
||||||
"""Return an object containing the parsed output from qemu-img info."""
|
"""Return an object containing the parsed output from qemu-img info."""
|
||||||
# TODO(mikal): this code should not be referring to a libvirt specific
|
# TODO(mikal): this code should not be referring to a libvirt specific
|
||||||
# flag.
|
# flag.
|
||||||
|
@ -58,8 +58,10 @@ def qemu_img_info(path):
|
||||||
raise exception.InvalidDiskInfo(reason=msg)
|
raise exception.InvalidDiskInfo(reason=msg)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
out, err = utils.execute('env', 'LC_ALL=C', 'LANG=C',
|
cmd = ('env', 'LC_ALL=C', 'LANG=C', 'qemu-img', 'info', path)
|
||||||
'qemu-img', 'info', path)
|
if format is not None:
|
||||||
|
cmd = cmd + ('-f', format)
|
||||||
|
out, err = utils.execute(*cmd)
|
||||||
except processutils.ProcessExecutionError as exp:
|
except processutils.ProcessExecutionError as exp:
|
||||||
msg = (_("qemu-img failed to execute on %(path)s : %(exp)s") %
|
msg = (_("qemu-img failed to execute on %(path)s : %(exp)s") %
|
||||||
{'path': path, 'exp': exp})
|
{'path': path, 'exp': exp})
|
||||||
|
|
|
@ -1498,7 +1498,8 @@ class LibvirtDriver(driver.ComputeDriver):
|
||||||
# NOTE(xqueralt): libvirt needs o+x in the temp directory
|
# NOTE(xqueralt): libvirt needs o+x in the temp directory
|
||||||
os.chmod(tmpdir, 0o701)
|
os.chmod(tmpdir, 0o701)
|
||||||
self._live_snapshot(context, instance, guest, disk_path,
|
self._live_snapshot(context, instance, guest, disk_path,
|
||||||
out_path, image_format, image_meta)
|
out_path, source_format, image_format,
|
||||||
|
image_meta)
|
||||||
else:
|
else:
|
||||||
snapshot_backend.snapshot_extract(out_path, image_format)
|
snapshot_backend.snapshot_extract(out_path, image_format)
|
||||||
finally:
|
finally:
|
||||||
|
@ -1599,7 +1600,7 @@ class LibvirtDriver(driver.ComputeDriver):
|
||||||
self._set_quiesced(context, instance, image_meta, False)
|
self._set_quiesced(context, instance, image_meta, False)
|
||||||
|
|
||||||
def _live_snapshot(self, context, instance, guest, disk_path, out_path,
|
def _live_snapshot(self, context, instance, guest, disk_path, out_path,
|
||||||
image_format, image_meta):
|
source_format, image_format, image_meta):
|
||||||
"""Snapshot an instance without downtime."""
|
"""Snapshot an instance without downtime."""
|
||||||
dev = guest.get_block_device(disk_path)
|
dev = guest.get_block_device(disk_path)
|
||||||
|
|
||||||
|
@ -1617,9 +1618,11 @@ class LibvirtDriver(driver.ComputeDriver):
|
||||||
# in QEMU 1.3. In order to do this, we need to create
|
# in QEMU 1.3. In order to do this, we need to create
|
||||||
# a destination image with the original backing file
|
# a destination image with the original backing file
|
||||||
# and matching size of the instance root disk.
|
# and matching size of the instance root disk.
|
||||||
src_disk_size = libvirt_utils.get_disk_size(disk_path)
|
src_disk_size = libvirt_utils.get_disk_size(disk_path,
|
||||||
|
format=source_format)
|
||||||
src_back_path = libvirt_utils.get_disk_backing_file(disk_path,
|
src_back_path = libvirt_utils.get_disk_backing_file(disk_path,
|
||||||
basename=False)
|
format=source_format,
|
||||||
|
basename=False)
|
||||||
disk_delta = out_path + '.delta'
|
disk_delta = out_path + '.delta'
|
||||||
libvirt_utils.create_cow_image(src_back_path, disk_delta,
|
libvirt_utils.create_cow_image(src_back_path, disk_delta,
|
||||||
src_disk_size)
|
src_disk_size)
|
||||||
|
|
|
@ -161,24 +161,25 @@ def pick_disk_driver_name(hypervisor_version, is_block_dev=False):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def get_disk_size(path):
|
def get_disk_size(path, format=None):
|
||||||
"""Get the (virtual) size of a disk image
|
"""Get the (virtual) size of a disk image
|
||||||
|
|
||||||
:param path: Path to the disk image
|
:param path: Path to the disk image
|
||||||
|
:param format: the on-disk format of path
|
||||||
:returns: Size (in bytes) of the given disk image as it would be seen
|
:returns: Size (in bytes) of the given disk image as it would be seen
|
||||||
by a virtual machine.
|
by a virtual machine.
|
||||||
"""
|
"""
|
||||||
size = images.qemu_img_info(path).virtual_size
|
size = images.qemu_img_info(path, format).virtual_size
|
||||||
return int(size)
|
return int(size)
|
||||||
|
|
||||||
|
|
||||||
def get_disk_backing_file(path, basename=True):
|
def get_disk_backing_file(path, basename=True, format=None):
|
||||||
"""Get the backing file of a disk image
|
"""Get the backing file of a disk image
|
||||||
|
|
||||||
:param path: Path to the disk image
|
:param path: Path to the disk image
|
||||||
:returns: a path to the image's backing store
|
:returns: a path to the image's backing store
|
||||||
"""
|
"""
|
||||||
backing_file = images.qemu_img_info(path).backing_file
|
backing_file = images.qemu_img_info(path, format).backing_file
|
||||||
if backing_file and basename:
|
if backing_file and basename:
|
||||||
backing_file = os.path.basename(backing_file)
|
backing_file = os.path.basename(backing_file)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue