Sophie

Sophie

distrib > Fedora > 16 > i386 > by-pkgid > 5c655bb31b7eacedb96e8b5da992c6ce > files > 22

openstack-nova-2011.3.1-11.fc16.src.rpm

From acb534fd6dbecebabdae4ac8770eeedef776fbe6 Mon Sep 17 00:00:00 2001
From: Thierry Carrez <thierry@openstack.org>
Date: Tue, 3 Jul 2012 16:34:58 +0200
Subject: [PATCH] Prevent key/net/md injection writing to host fs
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Fix bug 1015531, CVE-2012-3361

Checks that the final normalized path that is about to be written
to is always within the mounted guest filesystem.

This is a Diablo backport of the part of Russell Bryant, Pádraig Brady
and Mark McLoughlin's Folsom patch that applies to stable/diablo.

Change-Id: I134c40258ff2c9c225bd6092decd9c10e4e22273
---
 nova/tests/test_virt.py |   20 +++++++++++++++++++
 nova/virt/disk/base.py  |   49 ++++++++++++++++++++++++++++++++++++----------
 2 files changed, 58 insertions(+), 11 deletions(-)

diff --git a/nova/tests/test_virt.py b/nova/tests/test_virt.py
index 388f075..c33d7b3 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 import disk
 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._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._join_and_check_path_within_fs,
+                          '/foo', 'etc/../../../something.conf')
+
+    def test_inject_files_with_bad_path(self):
+        self.assertRaises(exception.Invalid,
+                          disk._inject_file_into_fs,
+                          '/tmp', '/etc/../../../../etc/passwd',
+                         'hax')
diff --git a/nova/virt/disk/base.py b/nova/virt/disk/base.py
index 9ae0bd1..f708741 100644
--- a/nova/virt/disk/base.py
+++ b/nova/virt/disk/base.py
@@ -250,12 +250,39 @@ def inject_data_into_fs(fs, key, net, metadata, execute):
         _inject_metadata_into_fs(metadata, fs, execute=execute)
 
 
+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()
+    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)
+
+    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):
@@ -264,13 +291,12 @@ 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')
-    utils.execute('tee', '-a', keyfile,
-                  process_input='\n' + key.strip() + '\n', run_as_root=True)
+    keyfile = os.path.join('root', '.ssh', 'authorized_keys')
+    _inject_file_into_fs(fs, keyfile, '\n' + key.strip() + '\n', append=True)
 
 
 def _inject_net_into_fs(net, fs, execute=None):
@@ -278,9 +304,10 @@ 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)