components/openstack/nova/patches/04-CVE-2014-0134-partial.patch
changeset 3998 5bd484384122
parent 3997 0ca3f3d6c919
child 4002 95b8f35fcdd5
equal deleted inserted replaced
3997:0ca3f3d6c919 3998:5bd484384122
     1 This proposed upstream patch is a follow-up to the original fix for
       
     2 CVE-2014-0134 (Launchpad bug 1221190) but is tracked under the same CVE
       
     3 and Launchpad bug as the original fix. It is designated as such below
       
     4 ('Partial-bug: #1221190').
       
     5 
       
     6 From 63064a709162ba1a8a9a19643bd3acdf57c0c0b4 Mon Sep 17 00:00:00 2001
       
     7 From: Nikola Dipanov <[email protected]>
       
     8 Date: Wed, 9 Apr 2014 15:50:20 +0200
       
     9 Subject: [PATCH] Avoid the possibility of truncating disk info file
       
    10 
       
    11 Commit dc8de42 makes nova persist image format to a file to avoid
       
    12 attacks based on changing it later. However the way it was implemented
       
    13 leaves a small window of opportunity for the file to be truncated before
       
    14 it gets written back to effectively making it possible for data to get
       
    15 lost leaving us with a potential problem next time it is attempted to be
       
    16 read.
       
    17 
       
    18 This patch changes the way file is updated to be atomic, thus closing
       
    19 the race window (and also removes the chown that we did not really
       
    20 need).
       
    21 
       
    22 It is worth noting that a better solution to this would be
       
    23 to allow the code calling the imagebackend to write the file (once!)
       
    24 and make it impossible to update after the boot process is done. This
       
    25 approach would require more refactoring of the libvirt driver code, and
       
    26 may be done in the future.
       
    27 
       
    28 Partial-bug: #1221190
       
    29 Change-Id: Ia1b073f38e096989f34d1774a12a1b4151773fc7
       
    30 (cherry picked from commit d416f4310bb946b4b127201ec3c37e530d988714)
       
    31 ---
       
    32  etc/nova/rootwrap.d/compute.filters          |    1 -
       
    33  nova/tests/virt/libvirt/test_imagebackend.py |   21 ---------------------
       
    34  nova/utils.py                                |   14 --------------
       
    35  nova/virt/libvirt/imagebackend.py            |   25 +++++++++++++------------
       
    36  4 files changed, 13 insertions(+), 48 deletions(-)
       
    37 
       
    38 diff --git a/etc/nova/rootwrap.d/compute.filters b/etc/nova/rootwrap.d/compute.filters
       
    39 index e98c3f2..ac67180 100644
       
    40 --- a/etc/nova/rootwrap.d/compute.filters
       
    41 +++ b/etc/nova/rootwrap.d/compute.filters
       
    42 @@ -41,7 +41,6 @@ mkdir: CommandFilter, mkdir, root
       
    43  # nova/virt/libvirt/connection.py: 'chown', os.getuid( console_log
       
    44  # nova/virt/libvirt/connection.py: 'chown', os.getuid( console_log
       
    45  # nova/virt/libvirt/connection.py: 'chown', 'root', basepath('disk')
       
    46 -# nova/utils.py: 'chown', owner_uid, path
       
    47  chown: CommandFilter, chown, root
       
    48  
       
    49  # nova/virt/disk/vfs/localfs.py: 'chmod'
       
    50 diff --git a/nova/tests/virt/libvirt/test_imagebackend.py b/nova/tests/virt/libvirt/test_imagebackend.py
       
    51 index 5424f7b..80ade57 100644
       
    52 --- a/nova/tests/virt/libvirt/test_imagebackend.py
       
    53 +++ b/nova/tests/virt/libvirt/test_imagebackend.py
       
    54 @@ -29,7 +29,6 @@ from nova.openstack.common import uuidutils
       
    55  from nova import test
       
    56  from nova.tests import fake_processutils
       
    57  from nova.tests.virt.libvirt import fake_libvirt_utils
       
    58 -from nova import utils
       
    59  from nova.virt.libvirt import imagebackend
       
    60  
       
    61  CONF = cfg.CONF
       
    62 @@ -68,10 +67,6 @@ class _ImageTestCase(object):
       
    63              'nova.virt.libvirt.imagebackend.libvirt_utils',
       
    64              fake_libvirt_utils))
       
    65  
       
    66 -        def fake_chown(path, owner_uid=None):
       
    67 -            return None
       
    68 -        self.stubs.Set(utils, 'chown', fake_chown)
       
    69 -
       
    70      def tearDown(self):
       
    71          super(_ImageTestCase, self).tearDown()
       
    72          shutil.rmtree(self.INSTANCES_PATH)
       
    73 @@ -128,10 +123,6 @@ class RawTestCase(_ImageTestCase, test.NoDBTestCase):
       
    74          super(RawTestCase, self).setUp()
       
    75          self.stubs.Set(imagebackend.Raw, 'correct_format', lambda _: None)
       
    76  
       
    77 -        def fake_chown(path, owner_uid=None):
       
    78 -            return None
       
    79 -        self.stubs.Set(utils, 'chown', fake_chown)
       
    80 -
       
    81      def prepare_mocks(self):
       
    82          fn = self.mox.CreateMockAnything()
       
    83          self.mox.StubOutWithMock(imagebackend.utils.synchronized,
       
    84 @@ -246,10 +237,6 @@ class RawTestCase(_ImageTestCase, test.NoDBTestCase):
       
    85          self.mox.StubOutWithMock(os.path, 'exists')
       
    86          self.mox.StubOutWithMock(imagebackend.images, 'qemu_img_info')
       
    87  
       
    88 -        def fake_chown(path, owner_uid=None):
       
    89 -            return None
       
    90 -        self.stubs.Set(utils, 'chown', fake_chown)
       
    91 -
       
    92          os.path.exists(self.PATH).AndReturn(True)
       
    93          os.path.exists(self.DISK_INFO_PATH).AndReturn(False)
       
    94          info = self.mox.CreateMockAnything()
       
    95 @@ -278,10 +265,6 @@ class Qcow2TestCase(_ImageTestCase, test.NoDBTestCase):
       
    96          self.QCOW2_BASE = (self.TEMPLATE_PATH +
       
    97                             '_%d' % (self.SIZE / (1024 * 1024 * 1024)))
       
    98  
       
    99 -        def fake_chown(path, owner_uid=None):
       
   100 -            return None
       
   101 -        self.stubs.Set(utils, 'chown', fake_chown)
       
   102 -
       
   103      def prepare_mocks(self):
       
   104          fn = self.mox.CreateMockAnything()
       
   105          self.mox.StubOutWithMock(imagebackend.utils.synchronized,
       
   106 @@ -873,10 +856,6 @@ class BackendTestCase(test.NoDBTestCase):
       
   107      def setUp(self):
       
   108          super(BackendTestCase, self).setUp()
       
   109  
       
   110 -        def fake_chown(path, owner_uid=None):
       
   111 -            return None
       
   112 -        self.stubs.Set(utils, 'chown', fake_chown)
       
   113 -
       
   114      def get_image(self, use_cow, image_type):
       
   115          return imagebackend.Backend(use_cow).image(self.INSTANCE,
       
   116                                                     self.NAME,
       
   117 diff --git a/nova/utils.py b/nova/utils.py
       
   118 index 4757f3a..599cb64 100755
       
   119 --- a/nova/utils.py
       
   120 +++ b/nova/utils.py
       
   121 @@ -924,20 +924,6 @@ def temporary_chown(path, owner_uid=None):
       
   122              execute('chown', orig_uid, path, run_as_root=True)
       
   123  
       
   124  
       
   125 -def chown(path, owner_uid=None):
       
   126 -    """chown a path.
       
   127 -
       
   128 -    :param owner_uid: UID of owner (defaults to current user)
       
   129 -    """
       
   130 -    if owner_uid is None:
       
   131 -        owner_uid = os.getuid()
       
   132 -
       
   133 -    orig_uid = os.stat(path).st_uid
       
   134 -
       
   135 -    if orig_uid != owner_uid:
       
   136 -        execute('chown', owner_uid, path, run_as_root=True)
       
   137 -
       
   138 -
       
   139  @contextlib.contextmanager
       
   140  def tempdir(**kwargs):
       
   141      argdict = kwargs.copy()
       
   142 diff --git a/nova/virt/libvirt/imagebackend.py b/nova/virt/libvirt/imagebackend.py
       
   143 index ed11c90..29131d9 100644
       
   144 --- a/nova/virt/libvirt/imagebackend.py
       
   145 +++ b/nova/virt/libvirt/imagebackend.py
       
   146 @@ -264,20 +264,21 @@ class Image(object):
       
   147                              lock_path=self.lock_path)
       
   148          def write_to_disk_info_file():
       
   149              # Use os.open to create it without group or world write permission.
       
   150 -            fd = os.open(self.disk_info_path, os.O_RDWR | os.O_CREAT, 0o644)
       
   151 -            with os.fdopen(fd, "r+") as disk_info_file:
       
   152 +            fd = os.open(self.disk_info_path, os.O_RDONLY | os.O_CREAT, 0o644)
       
   153 +            with os.fdopen(fd, "r") as disk_info_file:
       
   154                  line = disk_info_file.read().rstrip()
       
   155                  dct = _dict_from_line(line)
       
   156 -                if self.path in dct:
       
   157 -                    msg = _("Attempted overwrite of an existing value.")
       
   158 -                    raise exception.InvalidDiskInfo(reason=msg)
       
   159 -                dct.update({self.path: driver_format})
       
   160 -                disk_info_file.seek(0)
       
   161 -                disk_info_file.truncate()
       
   162 -                disk_info_file.write('%s\n' % jsonutils.dumps(dct))
       
   163 -            # Ensure the file is always owned by the nova user so qemu can't
       
   164 -            # write it.
       
   165 -            utils.chown(self.disk_info_path, owner_uid=os.getuid())
       
   166 +
       
   167 +            if self.path in dct:
       
   168 +                msg = _("Attempted overwrite of an existing value.")
       
   169 +                raise exception.InvalidDiskInfo(reason=msg)
       
   170 +            dct.update({self.path: driver_format})
       
   171 +
       
   172 +            tmp_path = self.disk_info_path + ".tmp"
       
   173 +            fd = os.open(tmp_path, os.O_WRONLY | os.O_CREAT, 0o644)
       
   174 +            with os.fdopen(fd, "w") as tmp_file:
       
   175 +                tmp_file.write('%s\n' % jsonutils.dumps(dct))
       
   176 +            os.rename(tmp_path, self.disk_info_path)
       
   177  
       
   178          try:
       
   179              if (self.disk_info_path is not None and
       
   180 -- 
       
   181 1.7.9.2
       
   182