From 23e3c9b73b830ce04c94a1202f7cc8e684e3cd82 Mon Sep 17 00:00:00 2001 From: Michael Hanselmann <hansmi@google.com> Date: Wed, 17 Oct 2012 16:10:07 +0200 Subject: [PATCH] bdev: Add verification for file storage paths An earlier version of this patch series verified all paths in cmdlib in the master daemon. With this change all that verification code is moved to bdev to run inside the node daemon. The checks are much stricter now--it is no longer possible to use forbidden paths (e.g. /bin) to manipulate file storage devices (once these checks are being used). Signed-off-by: Michael Hanselmann <hansmi@google.com> Reviewed-by: Iustin Pop <iustin@google.com> --- lib/backend.py | 1 + lib/bdev.py | 63 +++++++++++++++++++++++++++- test/ganeti.bdev_unittest.py | 79 ++++++++++++++++++++++++++++++++++-- 3 files changed, 137 insertions(+), 6 deletions(-) diff --git a/lib/backend.py b/lib/backend.py index 032547d22..6da70d609 100644 --- a/lib/backend.py +++ b/lib/backend.py @@ -800,6 +800,7 @@ def VerifyNode(what, cluster_name): result[constants.NV_BRIDGES] = [bridge for bridge in what[constants.NV_BRIDGES] if not utils.BridgeExists(bridge)] + return result diff --git a/lib/bdev.py b/lib/bdev.py index 8d41f53b6..d544675aa 100644 --- a/lib/bdev.py +++ b/lib/bdev.py @@ -29,6 +29,7 @@ import stat import pyparsing as pyp import os import logging +import itertools from ganeti import utils from ganeti import errors @@ -100,6 +101,58 @@ def _CanReadDevice(path): return False +def _GetForbiddenFileStoragePaths(): + """Builds a list of path prefixes which shouldn't be used for file storage. + + @rtype: frozenset + + """ + paths = set([ + "/boot", + "/dev", + "/etc", + "/home", + "/proc", + "/root", + "/sys", + ]) + + for prefix in ["", "/usr", "/usr/local"]: + paths.update(map(lambda s: "%s/%s" % (prefix, s), + ["bin", "lib", "lib32", "lib64", "sbin"])) + + return frozenset(map(os.path.normpath, paths)) + + +def _ComputeWrongFileStoragePaths(paths, + _forbidden=_GetForbiddenFileStoragePaths()): + """Cross-checks a list of paths for prefixes considered bad. + + Some paths, e.g. "/bin", should not be used for file storage. + + @type paths: list + @param paths: List of paths to be checked + @rtype: list + @return: Sorted list of paths for which the user should be warned + + """ + def _Check(path): + return (not os.path.isabs(path) or + path in _forbidden or + filter(lambda p: utils.IsBelowDir(p, path), _forbidden)) + + return utils.NiceSort(filter(_Check, map(os.path.normpath, paths))) + + +def ComputeWrongFileStoragePaths(_filename=pathutils.FILE_STORAGE_PATHS_FILE): + """Returns a list of file storage paths whose prefix is considered bad. + + See L{_ComputeWrongFileStoragePaths}. + + """ + return _ComputeWrongFileStoragePaths(_LoadAllowedFileStoragePaths(_filename)) + + def _CheckFileStoragePath(path, allowed): """Checks if a path is in a list of allowed paths for file storage. @@ -126,7 +179,7 @@ def _CheckFileStoragePath(path, allowed): " storage" % path) -def LoadAllowedFileStoragePaths(filename): +def _LoadAllowedFileStoragePaths(filename): """Loads file containing allowed file storage paths. @rtype: list @@ -149,7 +202,13 @@ def CheckFileStoragePath(path, _filename=pathutils.FILE_STORAGE_PATHS_FILE): @raise errors.FileStoragePathError: If the path is not allowed """ - _CheckFileStoragePath(path, LoadAllowedFileStoragePaths(_filename)) + allowed = _LoadAllowedFileStoragePaths(_filename) + + if _ComputeWrongFileStoragePaths([path]): + raise errors.FileStoragePathError("Path '%s' uses a forbidden prefix" % + path) + + _CheckFileStoragePath(path, allowed) class BlockDev(object): diff --git a/test/ganeti.bdev_unittest.py b/test/ganeti.bdev_unittest.py index e90071977..d58ee226f 100755 --- a/test/ganeti.bdev_unittest.py +++ b/test/ganeti.bdev_unittest.py @@ -373,7 +373,48 @@ class TestRADOSBlockDevice(testutils.GanetiTestCase): output_extra_matches, volume_name) -class TestCheckFileStoragePath(unittest.TestCase): +class TestComputeWrongFileStoragePathsInternal(unittest.TestCase): + def testPaths(self): + paths = bdev._GetForbiddenFileStoragePaths() + + for path in ["/bin", "/usr/local/sbin", "/lib64", "/etc", "/sys"]: + self.assertTrue(path in paths) + + self.assertEqual(set(map(os.path.normpath, paths)), paths) + + def test(self): + vfsp = bdev._ComputeWrongFileStoragePaths + self.assertEqual(vfsp([]), []) + self.assertEqual(vfsp(["/tmp"]), []) + self.assertEqual(vfsp(["/bin/ls"]), ["/bin/ls"]) + self.assertEqual(vfsp(["/bin"]), ["/bin"]) + self.assertEqual(vfsp(["/usr/sbin/vim", "/srv/file-storage"]), + ["/usr/sbin/vim"]) + + +class TestComputeWrongFileStoragePaths(testutils.GanetiTestCase): + def test(self): + tmpfile = self._CreateTempFile() + + utils.WriteFile(tmpfile, data=""" + /tmp + x/y///z/relative + # This is a test file + /srv/storage + /bin + /usr/local/lib32/ + relative/path + """) + + self.assertEqual(bdev.ComputeWrongFileStoragePaths(_filename=tmpfile), [ + "/bin", + "/usr/local/lib32", + "relative/path", + "x/y/z/relative", + ]) + + +class TestCheckFileStoragePathInternal(unittest.TestCase): def testNonAbsolute(self): for i in ["", "tmp", "foo/bar/baz"]: self.assertRaises(errors.FileStoragePathError, @@ -395,14 +436,44 @@ class TestCheckFileStoragePath(unittest.TestCase): bdev._CheckFileStoragePath("/tmp/foo/a/x", ["/tmp/foo"]) +class TestCheckFileStoragePath(testutils.GanetiTestCase): + def testNonExistantFile(self): + filename = "/tmp/this/file/does/not/exist" + assert not os.path.exists(filename) + self.assertRaises(errors.FileStoragePathError, + bdev.CheckFileStoragePath, "/bin/", _filename=filename) + self.assertRaises(errors.FileStoragePathError, + bdev.CheckFileStoragePath, "/srv/file-storage", + _filename=filename) + + def testAllowedPath(self): + tmpfile = self._CreateTempFile() + + utils.WriteFile(tmpfile, data=""" + /srv/storage + """) + + bdev.CheckFileStoragePath("/srv/storage/inst1", _filename=tmpfile) + + # No additional path component + self.assertRaises(errors.FileStoragePathError, + bdev.CheckFileStoragePath, "/srv/storage", + _filename=tmpfile) + + # Forbidden path + self.assertRaises(errors.FileStoragePathError, + bdev.CheckFileStoragePath, "/usr/lib64/xyz", + _filename=tmpfile) + + class TestLoadAllowedFileStoragePaths(testutils.GanetiTestCase): def testDevNull(self): - self.assertEqual(bdev.LoadAllowedFileStoragePaths("/dev/null"), []) + self.assertEqual(bdev._LoadAllowedFileStoragePaths("/dev/null"), []) def testNonExistantFile(self): filename = "/tmp/this/file/does/not/exist" assert not os.path.exists(filename) - self.assertEqual(bdev.LoadAllowedFileStoragePaths(filename), []) + self.assertEqual(bdev._LoadAllowedFileStoragePaths(filename), []) def test(self): tmpfile = self._CreateTempFile() @@ -414,7 +485,7 @@ class TestLoadAllowedFileStoragePaths(testutils.GanetiTestCase): relative/path """) - self.assertEqual(bdev.LoadAllowedFileStoragePaths(tmpfile), [ + self.assertEqual(bdev._LoadAllowedFileStoragePaths(tmpfile), [ "/tmp", "/srv/storage", "relative/path", -- GitLab