components/openstack/nova/patches/05-CVE-2013-4463.patch
branchs11-update
changeset 3178 77584387a894
parent 3175 1ff833d174d4
child 3179 07c03b663108
equal deleted inserted replaced
3175:1ff833d174d4 3178:77584387a894
     1 Upstream patch fixed in Grizzly 2013.1.5, Havana 2013.2.1, Icehouse
       
     2 
       
     3 commit 135faa7b5d9855312bedc19e5e1ecebae34d3d18
       
     4 Author: Pádraig Brady <[email protected]>
       
     5 Date:   Fri Sep 27 04:07:14 2013 +0100
       
     6 
       
     7     ensure we don't boot oversized images
       
     8     
       
     9     Since we can't generally shrink incoming images, add extra checks
       
    10     to ensure oversized images are not allowed through.
       
    11     All cases when populating the libvirt image cache are now handled,
       
    12     including the initial download from glance, where we avoid
       
    13     converting to raw, as that could generate non sparse images
       
    14     much larger than the downloaded image.
       
    15     
       
    16     * nova/virt/libvirt/utils.py (fetch_image): Allow passing through
       
    17     of the max_size parameter.
       
    18     * nova/virt/images.py (fetch_to_raw): Accept the max_size parameter,
       
    19     and use it to discard images with larger (virtual) sizes.
       
    20     * nova/virt/libvirt/imagebackend.py (verify_base_size): A new
       
    21     refactored function to identify and raise exception to oversized images.
       
    22     (Raw.create_image): Pass the max_size to the fetch function.
       
    23     Also enforce virtual image size checking for already fetched images,
       
    24     as this class (despite the name) can be handling qcow files.
       
    25     (Qcow2.create_image): Pass the max_size to the fetch function,
       
    26     or verify the virtual size for the instance as done previously.
       
    27     (Lvm.create_image): Pass the max_size to the fetch function.
       
    28     Also check the size before transferring to the volume to improve
       
    29     efficiency by not even attempting the transfer of oversized images.
       
    30     (Rbd.create_image): Likewise.
       
    31     * nova/tests/fake_libvirt_utils.py: Support max_size arg.
       
    32     * nova/tests/test_libvirt.py (test_fetch_raw_image):
       
    33     Add a case to check oversized images are discarded.
       
    34     * nova/tests/test_imagebackend.py (test_create_image_too_small):
       
    35     Adjust to avoid the fetch size check.
       
    36     
       
    37     Fixes bug: 1177830
       
    38     Fixes bug: 1206081
       
    39     
       
    40     Conflicts:
       
    41     
       
    42     	nova/tests/test_imagebackend.py
       
    43     	nova/virt/libvirt/imagebackend.py
       
    44     
       
    45     Change-Id: Idc35fce580be4f74e23883d1b4bea6475c3f6e30
       
    46 
       
    47 diff --git a/nova/tests/fake_libvirt_utils.py b/nova/tests/fake_libvirt_utils.py
       
    48 index 23b758e..ecf357a 100644
       
    49 --- a/nova/tests/fake_libvirt_utils.py
       
    50 +++ b/nova/tests/fake_libvirt_utils.py
       
    51 @@ -193,7 +193,7 @@ def get_fs_info(path):
       
    52              'free': 84 * (1024 ** 3)}
       
    53  
       
    54  
       
    55 -def fetch_image(context, target, image_id, user_id, project_id):
       
    56 +def fetch_image(context, target, image_id, user_id, project_id, max_size=0):
       
    57      pass
       
    58  
       
    59  
       
    60 diff --git a/nova/tests/test_imagebackend.py b/nova/tests/test_imagebackend.py
       
    61 index 77446e8..93ed23d 100644
       
    62 --- a/nova/tests/test_imagebackend.py
       
    63 +++ b/nova/tests/test_imagebackend.py
       
    64 @@ -189,7 +189,7 @@ class RawTestCase(_ImageTestCase, test.TestCase):
       
    65  
       
    66      def test_create_image(self):
       
    67          fn = self.prepare_mocks()
       
    68 -        fn(target=self.TEMPLATE_PATH, image_id=None)
       
    69 +        fn(target=self.TEMPLATE_PATH, max_size=None, image_id=None)
       
    70          imagebackend.libvirt_utils.copy_image(self.TEMPLATE_PATH, self.PATH)
       
    71          self.mox.ReplayAll()
       
    72  
       
    73 @@ -210,7 +210,7 @@ class RawTestCase(_ImageTestCase, test.TestCase):
       
    74  
       
    75      def test_create_image_extend(self):
       
    76          fn = self.prepare_mocks()
       
    77 -        fn(target=self.TEMPLATE_PATH, image_id=None)
       
    78 +        fn(max_size=self.SIZE, target=self.TEMPLATE_PATH, image_id=None)
       
    79          imagebackend.libvirt_utils.copy_image(self.TEMPLATE_PATH, self.PATH)
       
    80          imagebackend.disk.extend(self.PATH, self.SIZE)
       
    81          self.mox.ReplayAll()
       
    82 @@ -260,7 +260,7 @@ class Qcow2TestCase(_ImageTestCase, test.TestCase):
       
    83  
       
    84      def test_create_image(self):
       
    85          fn = self.prepare_mocks()
       
    86 -        fn(target=self.TEMPLATE_PATH)
       
    87 +        fn(max_size=None, target=self.TEMPLATE_PATH)
       
    88          imagebackend.libvirt_utils.create_cow_image(self.TEMPLATE_PATH,
       
    89                                                      self.PATH)
       
    90          self.mox.ReplayAll()
       
    91 @@ -272,15 +272,12 @@ class Qcow2TestCase(_ImageTestCase, test.TestCase):
       
    92  
       
    93      def test_create_image_with_size(self):
       
    94          fn = self.prepare_mocks()
       
    95 -        fn(target=self.TEMPLATE_PATH)
       
    96 +        fn(max_size=self.SIZE, target=self.TEMPLATE_PATH)
       
    97          self.mox.StubOutWithMock(os.path, 'exists')
       
    98 -        self.mox.StubOutWithMock(imagebackend.disk, 'get_disk_size')
       
    99          if self.OLD_STYLE_INSTANCE_PATH:
       
   100              os.path.exists(self.OLD_STYLE_INSTANCE_PATH).AndReturn(False)
       
   101          os.path.exists(self.TEMPLATE_PATH).AndReturn(False)
       
   102          os.path.exists(self.PATH).AndReturn(False)
       
   103 -        imagebackend.disk.get_disk_size(self.TEMPLATE_PATH
       
   104 -                                       ).AndReturn(self.SIZE)
       
   105          os.path.exists(self.PATH).AndReturn(False)
       
   106          imagebackend.libvirt_utils.create_cow_image(self.TEMPLATE_PATH,
       
   107                                                      self.PATH)
       
   108 @@ -294,27 +291,24 @@ class Qcow2TestCase(_ImageTestCase, test.TestCase):
       
   109  
       
   110      def test_create_image_too_small(self):
       
   111          fn = self.prepare_mocks()
       
   112 -        fn(target=self.TEMPLATE_PATH)
       
   113          self.mox.StubOutWithMock(os.path, 'exists')
       
   114          self.mox.StubOutWithMock(imagebackend.disk, 'get_disk_size')
       
   115          if self.OLD_STYLE_INSTANCE_PATH:
       
   116              os.path.exists(self.OLD_STYLE_INSTANCE_PATH).AndReturn(False)
       
   117 -        os.path.exists(self.TEMPLATE_PATH).AndReturn(False)
       
   118 -        os.path.exists(self.PATH).AndReturn(False)
       
   119 +        os.path.exists(self.TEMPLATE_PATH).AndReturn(True)
       
   120          imagebackend.disk.get_disk_size(self.TEMPLATE_PATH
       
   121                                         ).AndReturn(self.SIZE)
       
   122          self.mox.ReplayAll()
       
   123  
       
   124          image = self.image_class(self.INSTANCE, self.NAME)
       
   125 -        self.assertRaises(exception.ImageTooLarge, image.create_image, fn,
       
   126 -                          self.TEMPLATE_PATH, 1)
       
   127 +        self.assertRaises(exception.InstanceTypeDiskTooSmall,
       
   128 +                          image.create_image, fn, self.TEMPLATE_PATH, 1)
       
   129          self.mox.VerifyAll()
       
   130  
       
   131      def test_generate_resized_backing_files(self):
       
   132          fn = self.prepare_mocks()
       
   133 -        fn(target=self.TEMPLATE_PATH)
       
   134 +        fn(max_size=self.SIZE, target=self.TEMPLATE_PATH)
       
   135          self.mox.StubOutWithMock(os.path, 'exists')
       
   136 -        self.mox.StubOutWithMock(imagebackend.disk, 'get_disk_size')
       
   137          self.mox.StubOutWithMock(imagebackend.libvirt_utils,
       
   138                                   'get_disk_backing_file')
       
   139          if self.OLD_STYLE_INSTANCE_PATH:
       
   140 @@ -329,8 +323,6 @@ class Qcow2TestCase(_ImageTestCase, test.TestCase):
       
   141                                                self.QCOW2_BASE)
       
   142          imagebackend.disk.extend(self.QCOW2_BASE, self.SIZE)
       
   143  
       
   144 -        imagebackend.disk.get_disk_size(self.TEMPLATE_PATH
       
   145 -                                       ).AndReturn(self.SIZE)
       
   146          os.path.exists(self.PATH).AndReturn(True)
       
   147          self.mox.ReplayAll()
       
   148  
       
   149 @@ -341,9 +333,8 @@ class Qcow2TestCase(_ImageTestCase, test.TestCase):
       
   150  
       
   151      def test_qcow2_exists_and_has_no_backing_file(self):
       
   152          fn = self.prepare_mocks()
       
   153 -        fn(target=self.TEMPLATE_PATH)
       
   154 +        fn(max_size=self.SIZE, target=self.TEMPLATE_PATH)
       
   155          self.mox.StubOutWithMock(os.path, 'exists')
       
   156 -        self.mox.StubOutWithMock(imagebackend.disk, 'get_disk_size')
       
   157          self.mox.StubOutWithMock(imagebackend.libvirt_utils,
       
   158                                   'get_disk_backing_file')
       
   159          if self.OLD_STYLE_INSTANCE_PATH:
       
   160 @@ -353,8 +344,6 @@ class Qcow2TestCase(_ImageTestCase, test.TestCase):
       
   161  
       
   162          imagebackend.libvirt_utils.get_disk_backing_file(self.PATH)\
       
   163              .AndReturn(None)
       
   164 -        imagebackend.disk.get_disk_size(self.TEMPLATE_PATH
       
   165 -                                       ).AndReturn(self.SIZE)
       
   166          os.path.exists(self.PATH).AndReturn(True)
       
   167          self.mox.ReplayAll()
       
   168  
       
   169 @@ -391,7 +380,7 @@ class LvmTestCase(_ImageTestCase, test.TestCase):
       
   170  
       
   171      def _create_image(self, sparse):
       
   172          fn = self.prepare_mocks()
       
   173 -        fn(target=self.TEMPLATE_PATH)
       
   174 +        fn(max_size=None, target=self.TEMPLATE_PATH)
       
   175          self.libvirt_utils.create_lvm_image(self.VG,
       
   176                                              self.LV,
       
   177                                              self.TEMPLATE_SIZE,
       
   178 @@ -423,7 +412,7 @@ class LvmTestCase(_ImageTestCase, test.TestCase):
       
   179  
       
   180      def _create_image_resize(self, sparse):
       
   181          fn = self.prepare_mocks()
       
   182 -        fn(target=self.TEMPLATE_PATH)
       
   183 +        fn(max_size=self.SIZE, target=self.TEMPLATE_PATH)
       
   184          self.libvirt_utils.create_lvm_image(self.VG, self.LV,
       
   185                                              self.SIZE, sparse=sparse)
       
   186          self.disk.get_disk_size(self.TEMPLATE_PATH
       
   187 @@ -462,7 +451,7 @@ class LvmTestCase(_ImageTestCase, test.TestCase):
       
   188  
       
   189      def test_create_image_negative(self):
       
   190          fn = self.prepare_mocks()
       
   191 -        fn(target=self.TEMPLATE_PATH)
       
   192 +        fn(max_size=self.SIZE, target=self.TEMPLATE_PATH)
       
   193          self.libvirt_utils.create_lvm_image(self.VG,
       
   194                                              self.LV,
       
   195                                              self.SIZE,
       
   196 diff --git a/nova/tests/test_libvirt.py b/nova/tests/test_libvirt.py
       
   197 index d8c4cf2..e422ec7 100644
       
   198 --- a/nova/tests/test_libvirt.py
       
   199 +++ b/nova/tests/test_libvirt.py
       
   200 @@ -4826,7 +4826,8 @@ disk size: 4.4M''', ''))
       
   201          image_id = '4'
       
   202          user_id = 'fake'
       
   203          project_id = 'fake'
       
   204 -        images.fetch_to_raw(context, image_id, target, user_id, project_id)
       
   205 +        images.fetch_to_raw(context, image_id, target, user_id, project_id,
       
   206 +                            max_size=0)
       
   207  
       
   208          self.mox.ReplayAll()
       
   209          libvirt_utils.fetch_image(context, target, image_id,
       
   210 @@ -4856,20 +4857,27 @@ disk size: 4.4M''', ''))
       
   211                  file_format = path.split('.')[-2]
       
   212              elif file_format == 'converted':
       
   213                  file_format = 'raw'
       
   214 +
       
   215              if 'backing' in path:
       
   216                  backing_file = 'backing'
       
   217              else:
       
   218                  backing_file = None
       
   219  
       
   220 +            if 'big' in path:
       
   221 +                virtual_size = 2
       
   222 +            else:
       
   223 +                virtual_size = 1
       
   224 +
       
   225              FakeImgInfo.file_format = file_format
       
   226              FakeImgInfo.backing_file = backing_file
       
   227 +            FakeImgInfo.virtual_size = virtual_size
       
   228  
       
   229              return FakeImgInfo()
       
   230  
       
   231          self.stubs.Set(utils, 'execute', fake_execute)
       
   232          self.stubs.Set(os, 'rename', fake_rename)
       
   233          self.stubs.Set(os, 'unlink', fake_unlink)
       
   234 -        self.stubs.Set(images, 'fetch', lambda *_: None)
       
   235 +        self.stubs.Set(images, 'fetch', lambda *_, **__: None)
       
   236          self.stubs.Set(images, 'qemu_img_info', fake_qemu_img_info)
       
   237          self.stubs.Set(utils, 'delete_if_exists', fake_rm_on_errror)
       
   238  
       
   239 @@ -4884,7 +4892,8 @@ disk size: 4.4M''', ''))
       
   240                                't.qcow2.part', 't.qcow2.converted'),
       
   241                               ('rm', 't.qcow2.part'),
       
   242                               ('mv', 't.qcow2.converted', 't.qcow2')]
       
   243 -        images.fetch_to_raw(context, image_id, target, user_id, project_id)
       
   244 +        images.fetch_to_raw(context, image_id, target, user_id, project_id,
       
   245 +                            max_size=1)
       
   246          self.assertEqual(self.executes, expected_commands)
       
   247  
       
   248          target = 't.raw'
       
   249 @@ -4901,6 +4910,15 @@ disk size: 4.4M''', ''))
       
   250                            context, image_id, target, user_id, project_id)
       
   251          self.assertEqual(self.executes, expected_commands)
       
   252  
       
   253 +        target = 'big.qcow2'
       
   254 +        self.executes = []
       
   255 +        expected_commands = [('rm', '-f', 'big.qcow2.part')]
       
   256 +        self.assertRaises(exception.InstanceTypeDiskTooSmall,
       
   257 +                          images.fetch_to_raw,
       
   258 +                          context, image_id, target, user_id, project_id,
       
   259 +                          max_size=1)
       
   260 +        self.assertEqual(self.executes, expected_commands)
       
   261 +
       
   262          del self.executes
       
   263  
       
   264      def test_get_disk_backing_file(self):
       
   265 diff --git a/nova/virt/images.py b/nova/virt/images.py
       
   266 index b40f566..541779a 100755
       
   267 --- a/nova/virt/images.py
       
   268 +++ b/nova/virt/images.py
       
   269 @@ -190,7 +190,7 @@ def convert_image(source, dest, out_format, run_as_root=False):
       
   270      utils.execute(*cmd, run_as_root=run_as_root)
       
   271  
       
   272  
       
   273 -def fetch(context, image_href, path, _user_id, _project_id):
       
   274 +def fetch(context, image_href, path, _user_id, _project_id, max_size=0):
       
   275      # TODO(vish): Improve context handling and add owner and auth data
       
   276      #             when it is added to glance.  Right now there is no
       
   277      #             auth checking in glance, so we assume that access was
       
   278 @@ -202,9 +202,10 @@ def fetch(context, image_href, path, _user_id, _project_id):
       
   279              image_service.download(context, image_id, image_file)
       
   280  
       
   281  
       
   282 -def fetch_to_raw(context, image_href, path, user_id, project_id):
       
   283 +def fetch_to_raw(context, image_href, path, user_id, project_id, max_size=0):
       
   284      path_tmp = "%s.part" % path
       
   285 -    fetch(context, image_href, path_tmp, user_id, project_id)
       
   286 +    fetch(context, image_href, path_tmp, user_id, project_id,
       
   287 +          max_size=max_size)
       
   288  
       
   289      with utils.remove_path_on_error(path_tmp):
       
   290          data = qemu_img_info(path_tmp)
       
   291 @@ -220,6 +221,23 @@ def fetch_to_raw(context, image_href, path, user_id, project_id):
       
   292              raise exception.ImageUnacceptable(image_id=image_href,
       
   293                  reason=_("fmt=%(fmt)s backed by: %(backing_file)s") % locals())
       
   294  
       
   295 +        # We can't generally shrink incoming images, so disallow
       
   296 +        # images > size of the flavor we're booting.  Checking here avoids
       
   297 +        # an immediate DoS where we convert large qcow images to raw
       
   298 +        # (which may compress well but not be sparse).
       
   299 +        # TODO(p-draigbrady): loop through all flavor sizes, so that
       
   300 +        # we might continue here and not discard the download.
       
   301 +        # If we did that we'd have to do the higher level size checks
       
   302 +        # irrespective of whether the base image was prepared or not.
       
   303 +        disk_size = data.virtual_size
       
   304 +        if max_size and max_size < disk_size:
       
   305 +            msg = _('%(base)s virtual size %(disk_size)s '
       
   306 +                    'larger than flavor root disk size %(size)s')
       
   307 +            LOG.error(msg % {'base': path,
       
   308 +                             'disk_size': disk_size,
       
   309 +                             'size': max_size})
       
   310 +            raise exception.InstanceTypeDiskTooSmall()
       
   311 +
       
   312          if fmt != "raw" and CONF.force_raw_images:
       
   313              staged = "%s.converted" % path
       
   314              LOG.debug("%s was %s, converting to raw" % (image_href, fmt))
       
   315 diff --git a/nova/virt/libvirt/imagebackend.py b/nova/virt/libvirt/imagebackend.py
       
   316 index e2c7ccf..dc85c97 100755
       
   317 --- a/nova/virt/libvirt/imagebackend.py
       
   318 +++ b/nova/virt/libvirt/imagebackend.py
       
   319 @@ -177,6 +177,36 @@ class Image(object):
       
   320                            (CONF.preallocate_images, self.path))
       
   321          return can_fallocate
       
   322  
       
   323 +    @staticmethod
       
   324 +    def verify_base_size(base, size, base_size=0):
       
   325 +        """Check that the base image is not larger than size.
       
   326 +           Since images can't be generally shrunk, enforce this
       
   327 +           constraint taking account of virtual image size.
       
   328 +        """
       
   329 +
       
   330 +        # Note(pbrady): The size and min_disk parameters of a glance
       
   331 +        #  image are checked against the instance size before the image
       
   332 +        #  is even downloaded from glance, but currently min_disk is
       
   333 +        #  adjustable and doesn't currently account for virtual disk size,
       
   334 +        #  so we need this extra check here.
       
   335 +        # NOTE(cfb): Having a flavor that sets the root size to 0 and having
       
   336 +        #  nova effectively ignore that size and use the size of the
       
   337 +        #  image is considered a feature at this time, not a bug.
       
   338 +
       
   339 +        if size is None:
       
   340 +            return
       
   341 +
       
   342 +        if size and not base_size:
       
   343 +            base_size = disk.get_disk_size(base)
       
   344 +
       
   345 +        if size < base_size:
       
   346 +            msg = _('%(base)s virtual size %(base_size)s '
       
   347 +                    'larger than flavor root disk size %(size)s')
       
   348 +            LOG.error(msg % {'base': base,
       
   349 +                              'base_size': base_size,
       
   350 +                              'size': size})
       
   351 +            raise exception.InstanceTypeDiskTooSmall()
       
   352 +
       
   353      def snapshot_create(self):
       
   354          raise NotImplementedError
       
   355  
       
   356 @@ -217,7 +247,8 @@ class Raw(Image):
       
   357              #Generating image in place
       
   358              prepare_template(target=self.path, *args, **kwargs)
       
   359          else:
       
   360 -            prepare_template(target=base, *args, **kwargs)
       
   361 +            prepare_template(target=base, max_size=size, *args, **kwargs)
       
   362 +            self.verify_base_size(base, size)
       
   363              if not os.path.exists(self.path):
       
   364                  with utils.remove_path_on_error(self.path):
       
   365                      copy_raw_image(base, self.path, size)
       
   366 @@ -257,7 +288,9 @@ class Qcow2(Image):
       
   367  
       
   368          # Download the unmodified base image unless we already have a copy.
       
   369          if not os.path.exists(base):
       
   370 -            prepare_template(target=base, *args, **kwargs)
       
   371 +            prepare_template(target=base, max_size=size, *args, **kwargs)
       
   372 +        else:
       
   373 +            self.verify_base_size(base, size)
       
   374  
       
   375          legacy_backing_size = None
       
   376          legacy_base = base
       
   377 @@ -283,13 +316,6 @@ class Qcow2(Image):
       
   378                      libvirt_utils.copy_image(base, legacy_base)
       
   379                      disk.extend(legacy_base, legacy_backing_size)
       
   380  
       
   381 -        # NOTE(cfb): Having a flavor that sets the root size to 0 and having
       
   382 -        #            nova effectively ignore that size and use the size of the
       
   383 -        #            image is considered a feature at this time, not a bug.
       
   384 -        if size and size < disk.get_disk_size(base):
       
   385 -            LOG.error('%s virtual size larger than flavor root disk size %s' %
       
   386 -                      (base, size))
       
   387 -            raise exception.ImageTooLarge()
       
   388          if not os.path.exists(self.path):
       
   389              with utils.remove_path_on_error(self.path):
       
   390                  copy_qcow2_image(base, self.path, size)
       
   391 @@ -348,6 +374,7 @@ class Lvm(Image):
       
   392                                  lock_path=self.lock_path)
       
   393          def create_lvm_image(base, size):
       
   394              base_size = disk.get_disk_size(base)
       
   395 +            self.verify_base_size(base, size, base_size=base_size)
       
   396              resize = size > base_size
       
   397              size = size if resize else base_size
       
   398              libvirt_utils.create_lvm_image(self.vg, self.lv,
       
   399 @@ -365,7 +392,7 @@ class Lvm(Image):
       
   400              with self.remove_volume_on_error(self.path):
       
   401                  prepare_template(target=self.path, *args, **kwargs)
       
   402          else:
       
   403 -            prepare_template(target=base, *args, **kwargs)
       
   404 +            prepare_template(target=base, max_size=size, *args, **kwargs)
       
   405              with self.remove_volume_on_error(self.path):
       
   406                  create_lvm_image(base, size)
       
   407  
       
   408 diff --git a/nova/virt/libvirt/utils.py b/nova/virt/libvirt/utils.py
       
   409 index 6972243..4c31fcb 100755
       
   410 --- a/nova/virt/libvirt/utils.py
       
   411 +++ b/nova/virt/libvirt/utils.py
       
   412 @@ -592,9 +592,10 @@ def get_fs_info(path):
       
   413              'used': used}
       
   414  
       
   415  
       
   416 -def fetch_image(context, target, image_id, user_id, project_id):
       
   417 +def fetch_image(context, target, image_id, user_id, project_id, max_size=0):
       
   418      """Grab image."""
       
   419 -    images.fetch_to_raw(context, image_id, target, user_id, project_id)
       
   420 +    images.fetch_to_raw(context, image_id, target, user_id, project_id,
       
   421 +                        max_size=max_size)
       
   422  
       
   423  
       
   424  def get_instance_path(instance, forceold=False, relative=False):