[openstack-nova/el6] fix host corruption through file injection vulnerabilty

Pádraig Brady pbrady at fedoraproject.org
Fri Jul 6 23:38:54 UTC 2012


commit 67b1c0ad56dfcd942837775e474688c6468f39ba
Author: Pádraig Brady <P at draigBrady.com>
Date:   Thu Jul 5 16:59:51 2012 +0100

    fix host corruption through file injection vulnerabilty
    
    CVE-2012-3360, CVE-2012-3361
    
    Also include a pending stable commit to improve
    volume overlimit exceptions
    (cherry picked from commit ffced741483009adf52afd44e657d990c30d9a80)

 ...file-injection-writing-to-host-filesystem.patch |  183 +++++++++++++++++
 ...pam_lib-from-changing-the-timeout-setting.patch |    2 +-
 ...tomic-manipulation-of-libvirt-disk-images.patch |    2 +-
 ...e-don-t-access-the-net-when-building-docs.patch |    2 +-
 ...0007-fix-useexisting-deprecation-warnings.patch |    2 +-
 ...-configurable-libvirt-injection-partition.patch |    2 +-
 ... 0009-repeat-fusermount-to-avoid-business.patch |    2 +-
 ...unt-guest-image-once-when-injecting-files.patch |   27 +--
 ...ect-SELinux-context-for-injected-ssh-keys.patch |   32 ++--
 ...ish-over-quota-for-volume-size-and-number.patch |  211 ++++++++++++++++++++
 openstack-nova.spec                                |   26 ++-
 11 files changed, 448 insertions(+), 43 deletions(-)
---
diff --git a/0003-Prevent-file-injection-writing-to-host-filesystem.patch b/0003-Prevent-file-injection-writing-to-host-filesystem.patch
new file mode 100644
index 0000000..1c4253a
--- /dev/null
+++ b/0003-Prevent-file-injection-writing-to-host-filesystem.patch
@@ -0,0 +1,183 @@
+From b0feaffdb2b1c51182b8dce41b367f3449af5dd9 Mon Sep 17 00:00:00 2001
+From: Thierry Carrez <thierry at openstack.org>
+Date: Tue, 3 Jul 2012 16:32:12 +0200
+Subject: [PATCH] Prevent file injection writing to host filesystem.
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Fix bug 1015531, CVE-2012-3360, CVE-2012-3361
+
+This patch prevents the file injection code from writing into the host
+filesystem if a user specifies a path for the injected file that
+contains '..'. The check is to make sure that the final normalized path
+that is about to be written to is within the mounted guest filesystem.
+
+This is a backport of Russell Bryant, Pádraig Brady and Mark
+McLoughlin's Folsom patch.
+
+Change-Id: Id8bd6ffb4d878467ba2086d341fce23f2ff5aa0a
+---
+ nova/tests/test_virt.py |   20 +++++++++++++
+ nova/virt/disk/api.py   |   71 ++++++++++++++++++++++++++++++----------------
+ 2 files changed, 66 insertions(+), 25 deletions(-)
+
+diff --git a/nova/tests/test_virt.py b/nova/tests/test_virt.py
+index 388f075..c4aa828 100644
+--- a/nova/tests/test_virt.py
++++ b/nova/tests/test_virt.py
+@@ -15,8 +15,10 @@
+ #    License for the specific language governing permissions and limitations
+ #    under the License.
+ 
++from nova import exception
+ from nova import flags
+ from nova import test
++from nova.virt.disk import api as disk_api
+ from nova.virt import driver
+ 
+ FLAGS = flags.FLAGS
+@@ -81,3 +83,21 @@ class TestVirtDriver(test.TestCase):
+                                                 'swap_size': 0}))
+         self.assertTrue(driver.swap_is_usable({'device_name': '/dev/sdb',
+                                                 'swap_size': 1}))
++
++
++class TestVirtDisk(test.TestCase):
++    def test_check_safe_path(self):
++        ret = disk_api._join_and_check_path_within_fs('/foo', 'etc',
++                                                      'something.conf')
++        self.assertEquals(ret, '/foo/etc/something.conf')
++
++    def test_check_unsafe_path(self):
++        self.assertRaises(exception.Invalid,
++                          disk_api._join_and_check_path_within_fs,
++                          '/foo', 'etc/../../../something.conf')
++
++    def test_inject_files_with_bad_path(self):
++        self.assertRaises(exception.Invalid,
++                          disk_api._inject_file_into_fs,
++                          '/tmp', '/etc/../../../../etc/passwd',
++                          'hax')
+diff --git a/nova/virt/disk/api.py b/nova/virt/disk/api.py
+index 6cb19f2..04feb76 100644
+--- a/nova/virt/disk/api.py
++++ b/nova/virt/disk/api.py
+@@ -306,20 +306,39 @@ def inject_data_into_fs(fs, key, net, metadata, admin_password, execute):
+         _inject_admin_password_into_fs(admin_password, fs, execute=execute)
+ 
+ 
+-def _inject_file_into_fs(fs, path, contents):
+-    absolute_path = os.path.join(fs, path.lstrip('/'))
++def _join_and_check_path_within_fs(fs, *args):
++    '''os.path.join() with safety check for injected file paths.
++
++    Join the supplied path components and make sure that the
++    resulting path we are injecting into is within the
++    mounted guest fs.  Trying to be clever and specifying a
++    path with '..' in it will hit this safeguard.
++    '''
++    absolute_path = os.path.realpath(os.path.join(fs, *args))
++    if not absolute_path.startswith(os.path.realpath(fs) + '/'):
++        raise exception.Invalid(_('injected file path not valid'))
++    return absolute_path
++
++
++def _inject_file_into_fs(fs, path, contents, append=False):
++    absolute_path = _join_and_check_path_within_fs(fs, path.lstrip('/'))
++
+     parent_dir = os.path.dirname(absolute_path)
+     utils.execute('mkdir', '-p', parent_dir, run_as_root=True)
+-    utils.execute('tee', absolute_path, process_input=contents,
+-          run_as_root=True)
++
++    args = []
++    if append:
++        args.append('-a')
++    args.append(absolute_path)
++
++    kwargs = dict(process_input=contents, run_as_root=True)
++
++    utils.execute('tee', *args, **kwargs)
+ 
+ 
+ def _inject_metadata_into_fs(metadata, fs, execute=None):
+-    metadata_path = os.path.join(fs, "meta.js")
+     metadata = dict([(m.key, m.value) for m in metadata])
+-
+-    utils.execute('tee', metadata_path,
+-                  process_input=json.dumps(metadata), run_as_root=True)
++    _inject_file_into_fs(fs, 'meta.js', json.dumps(metadata))
+ 
+ 
+ def _inject_key_into_fs(key, fs, execute=None):
+@@ -328,20 +347,22 @@ def _inject_key_into_fs(key, fs, execute=None):
+     key is an ssh key string.
+     fs is the path to the base of the filesystem into which to inject the key.
+     """
+-    sshdir = os.path.join(fs, 'root', '.ssh')
++    sshdir = _join_and_check_path_within_fs(fs, 'root', '.ssh')
+     utils.execute('mkdir', '-p', sshdir, run_as_root=True)
+     utils.execute('chown', 'root', sshdir, run_as_root=True)
+     utils.execute('chmod', '700', sshdir, run_as_root=True)
+-    keyfile = os.path.join(sshdir, 'authorized_keys')
+-    key_data = [
++
++    keyfile = os.path.join('root', '.ssh', 'authorized_keys')
++
++    key_data = ''.join([
+         '\n',
+         '# The following ssh key was injected by Nova',
+         '\n',
+         key.strip(),
+         '\n',
+-    ]
+-    utils.execute('tee', '-a', keyfile,
+-                  process_input=''.join(key_data), run_as_root=True)
++    ])
++
++    _inject_file_into_fs(fs, keyfile, key_data, append=True)
+ 
+ 
+ def _inject_net_into_fs(net, fs, execute=None):
+@@ -349,12 +370,13 @@ def _inject_net_into_fs(net, fs, execute=None):
+ 
+     net is the contents of /etc/network/interfaces.
+     """
+-    netdir = os.path.join(os.path.join(fs, 'etc'), 'network')
++    netdir = _join_and_check_path_within_fs(fs, 'etc', 'network')
+     utils.execute('mkdir', '-p', netdir, run_as_root=True)
+     utils.execute('chown', 'root:root', netdir, run_as_root=True)
+     utils.execute('chmod', 755, netdir, run_as_root=True)
+-    netfile = os.path.join(netdir, 'interfaces')
+-    utils.execute('tee', netfile, process_input=net, run_as_root=True)
++
++    netfile = os.path.join('etc', 'network', 'interfaces')
++    _inject_file_into_fs(fs, netfile, net)
+ 
+ 
+ def _inject_admin_password_into_fs(admin_passwd, fs, execute=None):
+@@ -379,16 +401,15 @@ def _inject_admin_password_into_fs(admin_passwd, fs, execute=None):
+     fd, tmp_shadow = tempfile.mkstemp()
+     os.close(fd)
+ 
+-    utils.execute('cp', os.path.join(fs, 'etc', 'passwd'), tmp_passwd,
+-                  run_as_root=True)
+-    utils.execute('cp', os.path.join(fs, 'etc', 'shadow'), tmp_shadow,
+-                  run_as_root=True)
++    passwd_path = _join_and_check_path_within_fs(fs, 'etc', 'passwd')
++    shadow_path = _join_and_check_path_within_fs(fs, 'etc', 'shadow')
++
++    utils.execute('cp', passwd_path, tmp_passwd, run_as_root=True)
++    utils.execute('cp', shadow_path, tmp_shadow, run_as_root=True)
+     _set_passwd(admin_user, admin_passwd, tmp_passwd, tmp_shadow)
+-    utils.execute('cp', tmp_passwd, os.path.join(fs, 'etc', 'passwd'),
+-                  run_as_root=True)
++    utils.execute('cp', tmp_passwd, passwd_path, run_as_root=True)
+     os.unlink(tmp_passwd)
+-    utils.execute('cp', tmp_shadow, os.path.join(fs, 'etc', 'shadow'),
+-                  run_as_root=True)
++    utils.execute('cp', tmp_shadow, shadow_path, run_as_root=True)
+     os.unlink(tmp_shadow)
+ 
+ 
diff --git a/0003-Stop-nova_ipam_lib-from-changing-the-timeout-setting.patch b/0004-Stop-nova_ipam_lib-from-changing-the-timeout-setting.patch
similarity index 98%
rename from 0003-Stop-nova_ipam_lib-from-changing-the-timeout-setting.patch
rename to 0004-Stop-nova_ipam_lib-from-changing-the-timeout-setting.patch
index 0c213ac..fd66e64 100644
--- a/0003-Stop-nova_ipam_lib-from-changing-the-timeout-setting.patch
+++ b/0004-Stop-nova_ipam_lib-from-changing-the-timeout-setting.patch
@@ -1,4 +1,4 @@
-From 5b772f98fa139e7b4b32698004922add56ed22d0 Mon Sep 17 00:00:00 2001
+From 72c218d730736210961cf86f88543c69bf52d1fc Mon Sep 17 00:00:00 2001
 From: Vishvananda Ishaya <vishvananda at gmail.com>
 Date: Tue, 26 Jun 2012 20:37:02 +0000
 Subject: [PATCH] Stop nova_ipam_lib from changing the timeout setting
diff --git a/0004-ensure-atomic-manipulation-of-libvirt-disk-images.patch b/0005-ensure-atomic-manipulation-of-libvirt-disk-images.patch
similarity index 99%
rename from 0004-ensure-atomic-manipulation-of-libvirt-disk-images.patch
rename to 0005-ensure-atomic-manipulation-of-libvirt-disk-images.patch
index 9561852..cc7e0a4 100644
--- a/0004-ensure-atomic-manipulation-of-libvirt-disk-images.patch
+++ b/0005-ensure-atomic-manipulation-of-libvirt-disk-images.patch
@@ -1,4 +1,4 @@
-From 972858eab37408304f447a443641b949e9ca5b42 Mon Sep 17 00:00:00 2001
+From c765c320651a7cf029658ffb1b987e7162b5e2bf Mon Sep 17 00:00:00 2001
 From: =?UTF-8?q?P=C3=A1draig=20Brady?= <pbrady at redhat.com>
 Date: Fri, 16 Mar 2012 03:43:49 +0000
 Subject: [PATCH] ensure atomic manipulation of libvirt disk images
diff --git a/0005-Ensure-we-don-t-access-the-net-when-building-docs.patch b/0006-Ensure-we-don-t-access-the-net-when-building-docs.patch
similarity index 94%
rename from 0005-Ensure-we-don-t-access-the-net-when-building-docs.patch
rename to 0006-Ensure-we-don-t-access-the-net-when-building-docs.patch
index d569112..995f211 100644
--- a/0005-Ensure-we-don-t-access-the-net-when-building-docs.patch
+++ b/0006-Ensure-we-don-t-access-the-net-when-building-docs.patch
@@ -1,4 +1,4 @@
-From 6161e1a9dc1b760ceb3abfc48e892fc5fd811e67 Mon Sep 17 00:00:00 2001
+From 2f7444c4b5c8eeebdd422d45929d37aa4ccd0ec4 Mon Sep 17 00:00:00 2001
 From: =?UTF-8?q?P=C3=A1draig=20Brady?= <pbrady at redhat.com>
 Date: Fri, 6 Jan 2012 12:16:34 +0000
 Subject: [PATCH] Ensure we don't access the net when building docs
diff --git a/0006-fix-useexisting-deprecation-warnings.patch b/0007-fix-useexisting-deprecation-warnings.patch
similarity index 97%
rename from 0006-fix-useexisting-deprecation-warnings.patch
rename to 0007-fix-useexisting-deprecation-warnings.patch
index 54b20b5..a81071f 100644
--- a/0006-fix-useexisting-deprecation-warnings.patch
+++ b/0007-fix-useexisting-deprecation-warnings.patch
@@ -1,4 +1,4 @@
-From 36efbacfce3cde895e48612fbf92718e490ede9d Mon Sep 17 00:00:00 2001
+From 45f00db6fe33eeb2514c1fecb2a56f4ee294bfd2 Mon Sep 17 00:00:00 2001
 From: =?UTF-8?q?P=C3=A1draig=20Brady?= <pbrady at redhat.com>
 Date: Thu, 8 Mar 2012 16:32:30 +0000
 Subject: [PATCH] fix useexisting deprecation warnings
diff --git a/0007-support-a-configurable-libvirt-injection-partition.patch b/0008-support-a-configurable-libvirt-injection-partition.patch
similarity index 98%
rename from 0007-support-a-configurable-libvirt-injection-partition.patch
rename to 0008-support-a-configurable-libvirt-injection-partition.patch
index 4891b6f..8c9fdd2 100644
--- a/0007-support-a-configurable-libvirt-injection-partition.patch
+++ b/0008-support-a-configurable-libvirt-injection-partition.patch
@@ -1,4 +1,4 @@
-From 54f03b5c74a16f1f5ed1d67a2dc5d55fddac32f9 Mon Sep 17 00:00:00 2001
+From aee4d472fc80726bc9a18d7175627d9c5d1fab5c Mon Sep 17 00:00:00 2001
 From: =?UTF-8?q?P=C3=A1draig=20Brady?= <pbrady at redhat.com>
 Date: Wed, 18 Apr 2012 23:27:31 +0100
 Subject: [PATCH] support a configurable libvirt injection partition
diff --git a/0008-repeat-fusermount-to-avoid-business.patch b/0009-repeat-fusermount-to-avoid-business.patch
similarity index 92%
rename from 0008-repeat-fusermount-to-avoid-business.patch
rename to 0009-repeat-fusermount-to-avoid-business.patch
index 81d06bc..c6a02e0 100644
--- a/0008-repeat-fusermount-to-avoid-business.patch
+++ b/0009-repeat-fusermount-to-avoid-business.patch
@@ -1,4 +1,4 @@
-From 8346d52f7f4e4bc7c93dc877ac5bc5aa63e999ee Mon Sep 17 00:00:00 2001
+From ea9d8a5ae2ec89fa873999986654f365a6cd65d1 Mon Sep 17 00:00:00 2001
 From: Alessio Ababilov <aababilov at griddynamics.com>
 Date: Fri, 15 Jun 2012 18:33:26 +0300
 Subject: [PATCH] repeat fusermount to avoid business
diff --git a/0009-only-mount-guest-image-once-when-injecting-files.patch b/0010-only-mount-guest-image-once-when-injecting-files.patch
similarity index 89%
rename from 0009-only-mount-guest-image-once-when-injecting-files.patch
rename to 0010-only-mount-guest-image-once-when-injecting-files.patch
index 67e0028..8ae6d8f 100644
--- a/0009-only-mount-guest-image-once-when-injecting-files.patch
+++ b/0010-only-mount-guest-image-once-when-injecting-files.patch
@@ -1,20 +1,20 @@
-From def7d00ded863b0647ef18d611c2832b1336d06a Mon Sep 17 00:00:00 2001
+From 8843f1e61d5b648306398f6e8b8448c1442f58bf Mon Sep 17 00:00:00 2001
 From: =?UTF-8?q?P=C3=A1draig=20Brady?= <pbrady at redhat.com>
-Date: Thu, 21 Jun 2012 15:28:05 +0100
+Date: Mon, 25 Jun 2012 13:52:46 +0100
 Subject: [PATCH] only mount guest image once when injecting files
 
 Previously we could incur the costly guest mount operation twice,
 if injecting files with any of net, ssh keys, password, metadata.
 Instead now inject files in the same operation.
 
-Change-Id: I250532af14262962777e354324e9aa89d5ecbea1
+Change-Id: I228da2229a19d9097507f67b697595616fa601fe
 ---
  nova/virt/disk/api.py           |   39 ++++++++++++++-------------------------
  nova/virt/libvirt/connection.py |   23 +++++++----------------
  2 files changed, 21 insertions(+), 41 deletions(-)
 
 diff --git a/nova/virt/disk/api.py b/nova/virt/disk/api.py
-index 6cb19f2..6756ac2 100644
+index 04feb76..d6d6007 100644
 --- a/nova/virt/disk/api.py
 +++ b/nova/virt/disk/api.py
 @@ -224,7 +224,7 @@ class _DiskImage(object):
@@ -76,18 +76,15 @@ index 6cb19f2..6756ac2 100644
 +            _inject_file_into_fs(fs, path, contents)
  
  
- def _inject_file_into_fs(fs, path, contents):
-@@ -314,7 +303,7 @@ def _inject_file_into_fs(fs, path, contents):
-           run_as_root=True)
+ def _join_and_check_path_within_fs(fs, *args):
+@@ -336,12 +325,12 @@ def _inject_file_into_fs(fs, path, contents, append=False):
+     utils.execute('tee', *args, **kwargs)
  
  
 -def _inject_metadata_into_fs(metadata, fs, execute=None):
 +def _inject_metadata_into_fs(metadata, fs):
-     metadata_path = os.path.join(fs, "meta.js")
      metadata = dict([(m.key, m.value) for m in metadata])
- 
-@@ -322,7 +311,7 @@ def _inject_metadata_into_fs(metadata, fs, execute=None):
-                   process_input=json.dumps(metadata), run_as_root=True)
+     _inject_file_into_fs(fs, 'meta.js', json.dumps(metadata))
  
  
 -def _inject_key_into_fs(key, fs, execute=None):
@@ -95,8 +92,8 @@ index 6cb19f2..6756ac2 100644
      """Add the given public ssh key to root's authorized_keys.
  
      key is an ssh key string.
-@@ -344,7 +333,7 @@ def _inject_key_into_fs(key, fs, execute=None):
-                   process_input=''.join(key_data), run_as_root=True)
+@@ -365,7 +354,7 @@ def _inject_key_into_fs(key, fs, execute=None):
+     _inject_file_into_fs(fs, keyfile, key_data, append=True)
  
  
 -def _inject_net_into_fs(net, fs, execute=None):
@@ -104,8 +101,8 @@ index 6cb19f2..6756ac2 100644
      """Inject /etc/network/interfaces into the filesystem rooted at fs.
  
      net is the contents of /etc/network/interfaces.
-@@ -357,7 +346,7 @@ def _inject_net_into_fs(net, fs, execute=None):
-     utils.execute('tee', netfile, process_input=net, run_as_root=True)
+@@ -379,7 +368,7 @@ def _inject_net_into_fs(net, fs, execute=None):
+     _inject_file_into_fs(fs, netfile, net)
  
  
 -def _inject_admin_password_into_fs(admin_passwd, fs, execute=None):
diff --git a/0010-set-correct-SELinux-context-for-injected-ssh-keys.patch b/0011-set-correct-SELinux-context-for-injected-ssh-keys.patch
similarity index 72%
rename from 0010-set-correct-SELinux-context-for-injected-ssh-keys.patch
rename to 0011-set-correct-SELinux-context-for-injected-ssh-keys.patch
index d2baf54..282e1f8 100644
--- a/0010-set-correct-SELinux-context-for-injected-ssh-keys.patch
+++ b/0011-set-correct-SELinux-context-for-injected-ssh-keys.patch
@@ -1,4 +1,4 @@
-From 43bcb2febf73a1420c8e3e0ad8c88ce076403c21 Mon Sep 17 00:00:00 2001
+From 0cb4c982d353089b1396b9cfaf5d2cdc53496a6f Mon Sep 17 00:00:00 2001
 From: =?UTF-8?q?P=C3=A1draig=20Brady?= <pbrady at redhat.com>
 Date: Wed, 27 Jun 2012 10:29:57 +0100
 Subject: [PATCH] set correct SELinux context for injected ssh keys
@@ -13,8 +13,8 @@ Suggested-by: David Naori <dnaori at redhat.com>
 Change-Id: Ibf3869e3ee477e91623e0c030838c1ec8a6128a6
 ---
  nova/rootwrap/compute.py |    4 ++++
- nova/virt/disk/api.py    |   27 +++++++++++++++++++++++++++
- 2 files changed, 31 insertions(+), 0 deletions(-)
+ nova/virt/disk/api.py    |   33 +++++++++++++++++++++++++++++++++
+ 2 files changed, 37 insertions(+), 0 deletions(-)
 
 diff --git a/nova/rootwrap/compute.py b/nova/rootwrap/compute.py
 index bb53abc..0d43caa 100755
@@ -32,20 +32,25 @@ index bb53abc..0d43caa 100755
      filters.CommandFilter("/usr/bin/touch", "root"),
  
 diff --git a/nova/virt/disk/api.py b/nova/virt/disk/api.py
-index 6756ac2..ce3df1b 100644
+index d6d6007..16d03c6 100644
 --- a/nova/virt/disk/api.py
 +++ b/nova/virt/disk/api.py
-@@ -311,6 +311,31 @@ def _inject_metadata_into_fs(metadata, fs):
-                   process_input=json.dumps(metadata), run_as_root=True)
+@@ -330,6 +330,37 @@ def _inject_metadata_into_fs(metadata, fs):
+     _inject_file_into_fs(fs, 'meta.js', json.dumps(metadata))
  
  
 +def _setup_selinux_for_keys(fs):
 +    """Get selinux guests to ensure correct context on injected keys."""
 +
-+    rclocal = os.path.join(fs, 'etc', 'rc.local')
++    se_cfg = _join_and_check_path_within_fs(fs, 'etc', 'selinux')
++    se_cfg, _err = utils.trycmd('readlink', '-e', se_cfg, run_as_root=True)
++    if not se_cfg:
++        return
++
++    rclocal = _join_and_check_path_within_fs(fs, 'etc', 'rc.local')
 +
 +    # Support systemd based systems
-+    rc_d = os.path.join(fs, 'etc', 'rc.d')
++    rc_d = _join_and_check_path_within_fs(fs, 'etc', 'rc.d')
 +    rclocal_e, _err = utils.trycmd('readlink', '-e', rclocal, run_as_root=True)
 +    rc_d_e, _err = utils.trycmd('readlink', '-e', rc_d, run_as_root=True)
 +    if not rclocal_e and rc_d_e:
@@ -59,17 +64,18 @@ index 6756ac2..ce3df1b 100644
 +        '# Added by Nova to ensure injected ssh keys have the right context\n',
 +        'restorecon -RF /root/.ssh/ 2>/dev/null || :\n',
 +    ]
-+    utils.execute('tee', '-a', rclocal,
-+                  process_input=''.join(restorecon), run_as_root=True)
++
++    rclocal_rel = os.path.relpath(rclocal, fs)
++    _inject_file_into_fs(fs, rclocal_rel, ''.join(restorecon), append=True)
 +    utils.execute('chmod', 'a+x', rclocal, run_as_root=True)
 +
 +
  def _inject_key_into_fs(key, fs):
      """Add the given public ssh key to root's authorized_keys.
  
-@@ -332,6 +357,8 @@ def _inject_key_into_fs(key, fs):
-     utils.execute('tee', '-a', keyfile,
-                   process_input=''.join(key_data), run_as_root=True)
+@@ -353,6 +384,8 @@ def _inject_key_into_fs(key, fs):
+ 
+     _inject_file_into_fs(fs, keyfile, key_data, append=True)
  
 +    _setup_selinux_for_keys(fs)
 +
diff --git a/0012-Distinguish-over-quota-for-volume-size-and-number.patch b/0012-Distinguish-over-quota-for-volume-size-and-number.patch
new file mode 100644
index 0000000..ff17e86
--- /dev/null
+++ b/0012-Distinguish-over-quota-for-volume-size-and-number.patch
@@ -0,0 +1,211 @@
+From 6fb416d31517b347e9f4ce990d14d9248537cba0 Mon Sep 17 00:00:00 2001
+From: Eoghan Glynn <eglynn at redhat.com>
+Date: Wed, 4 Jul 2012 19:14:00 +0100
+Subject: [PATCH] Distinguish over-quota for volume size and number.
+
+Fixes LP 1020634
+
+Ensure that exceeding the allowed number of volumes is not
+mis-represented in log messages and exception handling as
+excessive space usage.
+
+Change-Id: I71ec995c77bc447bfc9221084b057bd8d69a4513
+---
+ nova/quota.py             |   35 ++++++++++++++++++++---------
+ nova/tests/test_quota.py  |   53 +++++++++++++++++++++++---------------------
+ nova/tests/test_volume.py |   30 +++++++++++++++++++++++++
+ nova/volume/api.py        |   23 ++++++++++++++++---
+ 4 files changed, 101 insertions(+), 40 deletions(-)
+
+diff --git a/nova/quota.py b/nova/quota.py
+index 12dd146..bcdad99 100644
+--- a/nova/quota.py
++++ b/nova/quota.py
+@@ -129,23 +129,36 @@ def allowed_instances(context, requested_instances, instance_type):
+ 
+ 
+ def allowed_volumes(context, requested_volumes, size):
+-    """Check quota and return min(requested_volumes, allowed_volumes)."""
++    """Check volume quotas and return breached if any."""
+     project_id = context.project_id
+     context = context.elevated()
+     size = int(size)
+-    requested_gigabytes = requested_volumes * size
++
++    allowed = {}
++    overs = []
++
+     used_volumes, used_gigabytes = db.volume_data_get_for_project(context,
+                                                                   project_id)
+-    quota = get_project_quotas(context, project_id)
+-    allowed_volumes = _get_request_allotment(requested_volumes, used_volumes,
+-                                             quota['volumes'])
+-    allowed_gigabytes = _get_request_allotment(requested_gigabytes,
+-                                               used_gigabytes,
+-                                               quota['gigabytes'])
++    usages = dict(volumes=used_volumes, gigabytes=used_gigabytes)
++
++    quotas = get_project_quotas(context, project_id)
++
++    def _check_allowed(resource, requested):
++        allow = _get_request_allotment(requested,
++                                       usages[resource],
++                                       quotas[resource])
++        if requested and allow < requested:
++            overs.append(resource)
++        allowed[resource] = allow
++
++    _check_allowed('volumes', requested_volumes)
++    _check_allowed('gigabytes', requested_volumes * size)
++
+     if size != 0:
+-        allowed_volumes = min(allowed_volumes,
+-                              int(allowed_gigabytes // size))
+-    return min(requested_volumes, allowed_volumes)
++        allowed['volumes'] = min(allowed['volumes'],
++                                 int(allowed['gigabytes'] // size))
++
++    return (overs, usages, quotas, allowed)
+ 
+ 
+ def allowed_floating_ips(context, requested_floating_ips):
+diff --git a/nova/tests/test_quota.py b/nova/tests/test_quota.py
+index 8cc5577..69242ea 100644
+--- a/nova/tests/test_quota.py
++++ b/nova/tests/test_quota.py
+@@ -196,31 +196,34 @@ class QuotaTestCase(test.TestCase):
+                                                 instance_type)
+         self.assertEqual(num_instances, 101)
+ 
+-    def test_unlimited_volumes(self):
+-        self.flags(quota_volumes=10, quota_gigabytes=-1)
+-        volumes = quota.allowed_volumes(self.context, 100, 1)
+-        self.assertEqual(volumes, 10)
+-        db.quota_create(self.context, self.project_id, 'volumes', None)
+-        volumes = quota.allowed_volumes(self.context, 100, 1)
+-        self.assertEqual(volumes, 100)
+-        db.quota_create(self.context, self.project_id, 'volumes', -1)
+-        volumes = quota.allowed_volumes(self.context, 100, 1)
+-        self.assertEqual(volumes, 100)
+-        volumes = quota.allowed_volumes(self.context, 101, 1)
+-        self.assertEqual(volumes, 101)
+-
+-    def test_unlimited_gigabytes(self):
+-        self.flags(quota_volumes=-1, quota_gigabytes=10)
+-        volumes = quota.allowed_volumes(self.context, 100, 1)
+-        self.assertEqual(volumes, 10)
+-        db.quota_create(self.context, self.project_id, 'gigabytes', None)
+-        volumes = quota.allowed_volumes(self.context, 100, 1)
+-        self.assertEqual(volumes, 100)
+-        db.quota_create(self.context, self.project_id, 'gigabytes', -1)
+-        volumes = quota.allowed_volumes(self.context, 100, 1)
+-        self.assertEqual(volumes, 100)
+-        volumes = quota.allowed_volumes(self.context, 101, 1)
+-        self.assertEqual(volumes, 101)
++    def _do_test_volume_quota(self, resource):
++
++        def _validate(result, request, quota, allow):
++            (overs, usages, quotas, allowed) = result
++            print 'overs %s' % overs
++            self.assertEquals(request > allow, resource in overs)
++            self.assertEquals(usages[resource], 0)
++            self.assertEquals(quotas[resource], quota)
++            self.assertEquals(allowed[resource], allow)
++
++        quota_volumes = 10 if resource == 'volumes' else -1
++        quota_gigabytes = 10 if resource == 'gigabytes' else -1
++        self.flags(quota_volumes=quota_volumes,
++                   quota_gigabytes=quota_gigabytes)
++        _validate(quota.allowed_volumes(self.context, 100, 1), 100, 10, 10)
++
++        db.quota_create(self.context, self.project_id, resource, None)
++        _validate(quota.allowed_volumes(self.context, 100, 1), 100, None, 100)
++
++        db.quota_create(self.context, self.project_id, resource, -1)
++        _validate(quota.allowed_volumes(self.context, 100, 1), 100, None, 100)
++        _validate(quota.allowed_volumes(self.context, 101, 1), 101, None, 101)
++
++    def test_volumes_quota(self):
++        self._do_test_volume_quota('volumes')
++
++    def test_gigabytes_quota(self):
++        self._do_test_volume_quota('gigabytes')
+ 
+     def test_unlimited_floating_ips(self):
+         self.flags(quota_floating_ips=10)
+diff --git a/nova/tests/test_volume.py b/nova/tests/test_volume.py
+index fca593c..118fd5b 100644
+--- a/nova/tests/test_volume.py
++++ b/nova/tests/test_volume.py
+@@ -81,6 +81,36 @@ class VolumeTestCase(test.TestCase):
+                           self.context,
+                           volume_id)
+ 
++    def _do_test_create_over_quota(self, resource, expected):
++        """Test volume creation over quota."""
++
++        def fake_allowed_volumes(context, requested_volumes, size):
++            return ([resource],
++                    dict(gigabytes=999, volumes=9),
++                    dict(gigabytes=1000, volumes=10),
++                    dict(gigabytes=0, volumes=0))
++
++        self.stubs.Set(nova.quota, 'allowed_volumes', fake_allowed_volumes)
++
++        volume_api = nova.volume.api.API()
++
++        try:
++            volume_api.create(self.context,
++                              2,
++                             'name',
++                             'description')
++            self.fail('expected QuotaError')
++        except exception.QuotaError as qe:
++            self.assertTrue(str(qe).endswith('code=%s' % expected))
++
++    def test_create_volumes_over_quota(self):
++        self._do_test_create_over_quota('volumes',
++                                        'VolumeLimitExceeded')
++
++    def test_create_gigabytes_over_quota(self):
++        self._do_test_create_over_quota('gigabytes',
++                                        'VolumeSizeTooLarge')
++
+     def test_delete_busy_volume(self):
+         """Test volume survives deletion if driver reports it as busy."""
+         volume = self._create_volume()
+diff --git a/nova/volume/api.py b/nova/volume/api.py
+index 76de551..08c51bb 100644
+--- a/nova/volume/api.py
++++ b/nova/volume/api.py
+@@ -80,11 +80,26 @@ class API(base.Base):
+         else:
+             snapshot_id = None
+ 
+-        if quota.allowed_volumes(context, 1, size) < 1:
++        (overs, usages, quotas, allowed) = quota.allowed_volumes(context,
++                                                                 1,
++                                                                 size)
++
++        if allowed['volumes'] < 1:
+             pid = context.project_id
+-            LOG.warn(_("Quota exceeded for %(pid)s, tried to create"
+-                    " %(size)sG volume") % locals())
+-            raise exception.QuotaError(code="VolumeSizeTooLarge")
++            if 'gigabytes' in overs:
++                consumed = usages['gigabytes']
++                limit = quotas['gigabytes']
++                LOG.warn(_("Quota exceeded for %(pid)s, tried to create "
++                           "%(size)sG volume (%(consumed)dG of %(limit)dG "
++                           "already consumed)") % locals())
++                code = "VolumeSizeTooLarge"
++            elif 'volumes' in overs:
++                consumed = usages['volumes']
++                LOG.warn(_("Quota exceeded for %(pid)s, tried to create "
++                           "volume (%(consumed)d volumes already consumed)")
++                           % locals())
++                code = "VolumeLimitExceeded"
++            raise exception.QuotaError(code=code)
+ 
+         if availability_zone is None:
+             availability_zone = FLAGS.storage_availability_zone
diff --git a/openstack-nova.spec b/openstack-nova.spec
index 2af4ade..4e4dfb2 100644
--- a/openstack-nova.spec
+++ b/openstack-nova.spec
@@ -2,7 +2,7 @@
 
 Name:             openstack-nova
 Version:          2012.1.1
-Release:          2%{?dist}
+Release:          3%{?dist}
 Summary:          OpenStack Compute (nova)
 
 Group:            Applications/System
@@ -44,14 +44,16 @@ Source22:         nova-ifc-template
 # patches_base=2012.1.1
 #
 Patch0002: 0002-Call-libvirt_volume_driver-with-right-mountpoint.patch
-Patch0003: 0003-Stop-nova_ipam_lib-from-changing-the-timeout-setting.patch
-Patch0004: 0004-ensure-atomic-manipulation-of-libvirt-disk-images.patch
-Patch0005: 0005-Ensure-we-don-t-access-the-net-when-building-docs.patch
-Patch0006: 0006-fix-useexisting-deprecation-warnings.patch
-Patch0007: 0007-support-a-configurable-libvirt-injection-partition.patch
-Patch0008: 0008-repeat-fusermount-to-avoid-business.patch
-Patch0009: 0009-only-mount-guest-image-once-when-injecting-files.patch
-Patch0010: 0010-set-correct-SELinux-context-for-injected-ssh-keys.patch
+Patch0003: 0003-Prevent-file-injection-writing-to-host-filesystem.patch
+Patch0004: 0004-Stop-nova_ipam_lib-from-changing-the-timeout-setting.patch
+Patch0005: 0005-ensure-atomic-manipulation-of-libvirt-disk-images.patch
+Patch0006: 0006-Ensure-we-don-t-access-the-net-when-building-docs.patch
+Patch0007: 0007-fix-useexisting-deprecation-warnings.patch
+Patch0008: 0008-support-a-configurable-libvirt-injection-partition.patch
+Patch0009: 0009-repeat-fusermount-to-avoid-business.patch
+Patch0010: 0010-only-mount-guest-image-once-when-injecting-files.patch
+Patch0011: 0011-set-correct-SELinux-context-for-injected-ssh-keys.patch
+Patch0012: 0012-Distinguish-over-quota-for-volume-size-and-number.patch
 
 # This is EPEL specific and not upstream
 Patch100:         openstack-nova-newdeps.patch
@@ -186,6 +188,8 @@ This package contains documentation files for nova.
 %patch0008 -p1
 %patch0009 -p1
 %patch0010 -p1
+%patch0011 -p1
+%patch0012 -p1
 
 # Apply EPEL patch
 %patch100 -p1
@@ -396,6 +400,10 @@ fi
 %endif
 
 %changelog
+* Thu Jul  5 2012 Pádraig Brady <P at draigBrady.com> - 2012.1.1-3
+- Distinguish volume overlimit exceptions
+- Prohibit host file corruption through file injection (CVE-2012-3360, CVE-2012-3361)
+
 * Wed Jun 27 2012 Pádraig Brady <P at draigBrady.com> - 2012.1.1-2
 - Update to latest essex stable branch
 - Support injecting new .ssh/authorized_keys files to SELinux enabled guests


More information about the scm-commits mailing list