From: Eric Harney <eharney@redhat.com>
Date: Tue, 31 Mar 2015 23:48:17 +0000 (-0400)
Subject: Disallow backing files when uploading volumes to image
X-Git-Url: https://review.fuel-infra.org/gitweb?a=commitdiff_plain;h=b1143ee45323e63b965a3710f9063e65b252c978;p=openstack-build%2Fcinder-build.git

Disallow backing files when uploading volumes to image

Volumes with a header referencing a backing file can leak
file data into the destination image when uploading a
volume to an image.

Halt the upload process if the volume data references a
backing file to prevent this.

Closes-Bug: #1415087
Change-Id: Iab9718794e7f7e8444015712cfa08c46848ebf78
---

diff --git a/cinder/image/image_utils.py b/cinder/image/image_utils.py
index 34fddc402..76897c186 100644
--- a/cinder/image/image_utils.py
+++ b/cinder/image/image_utils.py
@@ -354,6 +354,20 @@ def upload_volume(context, image_service, image_meta, volume_path,
     with temporary_file() as tmp:
         LOG.debug("%s was %s, converting to %s",
                   image_id, volume_format, image_meta['disk_format'])
+
+        data = qemu_img_info(volume_path, run_as_root=run_as_root)
+        backing_file = data.backing_file
+        fmt = data.file_format
+        if backing_file is not None:
+            # Disallow backing files as a security measure.
+            # This prevents a user from writing an image header into a raw
+            # volume with a backing file pointing to data they wish to
+            # access.
+            raise exception.ImageUnacceptable(
+                image_id=image_id,
+                reason=_("fmt=%(fmt)s backed by:%(backing_file)s")
+                % {'fmt': fmt, 'backing_file': backing_file})
+
         convert_image(volume_path, tmp, image_meta['disk_format'],
                       run_as_root=run_as_root)
 
diff --git a/cinder/tests/unit/test_image_utils.py b/cinder/tests/unit/test_image_utils.py
index 3eea79edd..025275fb7 100644
--- a/cinder/tests/unit/test_image_utils.py
+++ b/cinder/tests/unit/test_image_utils.py
@@ -381,6 +381,7 @@ class TestUploadVolume(test.TestCase):
         mock_os.name = 'posix'
         data = mock_info.return_value
         data.file_format = mock.sentinel.disk_format
+        data.backing_file = None
         temp_file = mock_temp.return_value.__enter__.return_value
 
         output = image_utils.upload_volume(ctxt, image_service, image_meta,
@@ -391,7 +392,8 @@ class TestUploadVolume(test.TestCase):
                                              temp_file,
                                              mock.sentinel.disk_format,
                                              run_as_root=True)
-        mock_info.assert_called_once_with(temp_file, run_as_root=True)
+        mock_info.assert_called_with(temp_file, run_as_root=True)
+        self.assertEqual(mock_info.call_count, 2)
         mock_open.assert_called_once_with(temp_file, 'rb')
         image_service.update.assert_called_once_with(
             ctxt, image_meta['id'], {},
@@ -470,6 +472,7 @@ class TestUploadVolume(test.TestCase):
         mock_os.name = 'posix'
         data = mock_info.return_value
         data.file_format = mock.sentinel.other_disk_format
+        data.backing_file = None
         temp_file = mock_temp.return_value.__enter__.return_value
 
         self.assertRaises(exception.ImageUnacceptable,
@@ -479,7 +482,8 @@ class TestUploadVolume(test.TestCase):
                                              temp_file,
                                              mock.sentinel.disk_format,
                                              run_as_root=True)
-        mock_info.assert_called_once_with(temp_file, run_as_root=True)
+        mock_info.assert_called_with(temp_file, run_as_root=True)
+        self.assertEqual(mock_info.call_count, 2)
         self.assertFalse(image_service.update.called)