15780631 problem in UTILITY/ZONES s11u3-sru
authorsaurabh.vyas@oracle.com
Thu, 08 Dec 2016 03:41:02 +0530
branchs11u3-sru
changeset 3487 a07516e7fec8
parent 3486 547edb2cfaa7
child 3488 6f6b89648375
15780631 problem in UTILITY/ZONES
src/modules/catalog.py
src/modules/client/image.py
src/modules/client/publisher.py
src/modules/misc.py
src/modules/p5p.py
src/tests/api/t_catalog.py
src/tests/cli/t_pkg_image_create.py
--- a/src/modules/catalog.py	Thu Dec 08 03:41:00 2016 +0530
+++ b/src/modules/catalog.py	Thu Dec 08 03:41:02 2016 +0530
@@ -19,7 +19,7 @@
 #
 # CDDL HEADER END
 #
-# Copyright (c) 2007, 2014, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2007, 2016, Oracle and/or its affiliates. All rights reserved.
 
 """Interfaces and implementation for the Catalog object, as well as functions
 that operate on lists of package FMRIs."""
@@ -209,19 +209,23 @@
         __file_mode = stat.S_IRUSR|stat.S_IWUSR|stat.S_IRGRP|stat.S_IROTH
 
         __meta_root = None
+        __file_root = None
         last_modified = None
         loaded = False
         name = None
         sign = True
         signatures = None
 
-        def __init__(self, name, meta_root=None, sign=True):
+        def __init__(self, name, meta_root=None, sign=True, file_root=None):
                 """Initializes a CatalogPartBase object."""
 
                 self.meta_root = meta_root
+                self.file_root = file_root
+                if not self.file_root:
+                        self.file_root = meta_root
                 # Sanity check: part names can't be pathname-ish.
                 if name != os.path.basename(name):
-                        raise UnrecognizedCatalogPart(name)
+                        raise api_errors.UnrecognizedCatalogPart(name)
                 self.name = name
                 self.sign = sign
                 self.signatures = {}
@@ -243,6 +247,9 @@
         def __get_meta_root(self):
                 return self.__meta_root
 
+        def __get_file_root(self):
+                return self.__file_root
+
         def __last_modified(self):
                 """A UTC datetime object representing the time the file used to
                 to store object metadata was modified, or None if it does not
@@ -264,6 +271,11 @@
                         path = os.path.abspath(path)
                 self.__meta_root = path
 
+        def __set_file_root(self, path):
+                if path:
+                        path = os.path.abspath(path)
+                self.__file_root = path
+
         def destroy(self):
                 """Removes any on-disk files that exist for the catalog part and
                 discards all content."""
@@ -300,7 +312,8 @@
                 location = os.path.join(self.meta_root, self.name)
 
                 try:
-                        fobj = file(location, "rb")
+                        fobj = misc.open_image_file(self.file_root, location,
+                            os.O_RDONLY, misc.PKG_FILE_MODE)
                 except EnvironmentError, e:
                         if e.errno == errno.ENOENT:
                                 raise api_errors.RetrievalError(e,
@@ -311,6 +324,9 @@
                         if e.errno == errno.EACCES:
                                 raise api_errors.PermissionsException(
                                     e.filename)
+                        if e.errno == errno.EREMOTE:
+                                raise api_errors.UnrecognizedCatalogPart(
+                                    self.name)
                         raise
 
                 try:
@@ -374,6 +390,7 @@
                         os.utime(self.pathname, (mtime, mtime))
 
         meta_root = property(__get_meta_root, __set_meta_root)
+        file_root = property(__get_file_root, __set_file_root)
 
 
 class CatalogPart(CatalogPartBase):
@@ -383,15 +400,16 @@
         __data = None
         ordered = None
 
-        def __init__(self, name, meta_root=None, ordered=True, sign=True):
+        def __init__(self, name, meta_root=None, ordered=True, sign=True,
+            file_root=None):
                 """Initializes a CatalogPart object."""
 
                 self.__data = {}
                 self.ordered = ordered
                 if not name.startswith("catalog."):
-                        raise UnrecognizedCatalogPart(name)
+                        raise api_errors.UnrecognizedCatalogPart(name)
                 CatalogPartBase.__init__(self, name, meta_root=meta_root,
-                    sign=sign)
+                    sign=sign, file_root=file_root)
 
         def __iter_entries(self, last=False, ordered=False, pubs=EmptyI):
                 """Private generator function to iterate over catalog entries.
@@ -963,14 +981,14 @@
         ADD = "add"
         REMOVE = "remove"
 
-        def __init__(self, name, meta_root=None, sign=True):
+        def __init__(self, name, meta_root=None, sign=True, file_root=None):
                 """Initializes a CatalogUpdate object."""
 
                 self.__data = {}
                 if not name.startswith("update."):
-                        raise UnrecognizedCatalogPart(name)
+                        raise api_errors.UnrecognizedCatalogPart(name)
                 CatalogPartBase.__init__(self, name, meta_root=meta_root,
-                    sign=sign)
+                    sign=sign, file_root=file_root)
 
         def add(self, pfmri, operation, op_time, metadata=None):
                 """Records the specified catalog operation and any related
@@ -1129,12 +1147,12 @@
             "version": 1,
         }
 
-        def __init__(self, meta_root=None, sign=True):
+        def __init__(self, meta_root=None, sign=True, file_root=None):
                 """Initializes a CatalogAttrs object."""
 
                 self.__data = {}
                 CatalogPartBase.__init__(self, name="catalog.attrs",
-                    meta_root=meta_root, sign=sign)
+                    meta_root=meta_root, sign=sign, file_root=file_root)
 
                 if self.loaded:
                         # If the data is already seen as 'loaded' during init,
@@ -1265,6 +1283,22 @@
                                                     "%s {%s: %s}" % (self.name,
                                                     key, subpart))
 
+                                        # Check if subpart is a symbolic link
+                                        # that would cause an access to be
+                                        # redirected outside of 'file_root'.
+                                        try:
+                                                misc.open_image_file(
+                                                    self.file_root,
+                                                    os.path.join(self.meta_root,
+                                                    subpart), os.O_RDONLY,
+                                                    misc.PKG_FILE_MODE)
+                                        except OSError as e:
+                                                if e.errno == errno.EREMOTE:
+                                                        raise api_errors.\
+                                                            UnrecognizedCatalogPart(
+                                                            "{0} {{{1}: {2}}}".format(
+                                                            self.name, key, subpart))
+
                                 # Build datetimes from timestamps.
                                 for e in val:
                                         lm = val[e].get("last-modified", None)
@@ -1343,6 +1377,7 @@
         __lock = None
         __manifest_cb = None
         __meta_root = None
+        __file_root = None
         __sign = None
 
         # These are used to cache or store CatalogPart and CatalogUpdate objects
@@ -1355,7 +1390,7 @@
         DEPENDENCY, SUMMARY = range(2)
 
         def __init__(self, batch_mode=False, meta_root=None, log_updates=False,
-            manifest_cb=None, read_only=False, sign=True):
+            manifest_cb=None, read_only=False, sign=True, file_root=None):
                 """Initializes a Catalog object.
 
                 'batch_mode' is an optional boolean value that indicates that
@@ -1402,9 +1437,13 @@
                 self.meta_root = meta_root
                 self.read_only = read_only
                 self.sign = sign
+                self.file_root = file_root
+                if not self.file_root:
+                        self.file_root = meta_root
 
                 # Must be set after the above.
-                self._attrs = CatalogAttrs(meta_root=self.meta_root, sign=sign)
+                self._attrs = CatalogAttrs(meta_root=self.meta_root, sign=sign,
+                    file_root=file_root)
 
                 # This lock is used to protect the catalog file from multiple
                 # threads writing to it at the same time.
@@ -1731,6 +1770,9 @@
         def __get_meta_root(self):
                 return self.__meta_root
 
+        def __get_file_root(self):
+                return self.__file_root
+
         def __get_sign(self):
                 return self.__sign
 
@@ -1951,6 +1993,22 @@
                 for ulog in self.__updates.values():
                         ulog.meta_root = pathname
 
+        def __set_file_root(self, pathname):
+                if pathname:
+                        pathname = os.path.abspath(pathname)
+                self.__file_root = pathname
+
+                # If the Catalog's file_root changes, the file_root of all of
+                # its parts must be changed too.
+                if self._attrs:
+                        self._attrs.file_root = pathname
+
+                for part in self.__parts.values():
+                        part.file_root = pathname
+
+                for ulog in self.__updates.values():
+                        ulog.file_root = pathname
+
         def __set_perms(self):
                 """Sets permissions on attrs and parts if not read_only and if
                 the current user can do so; raises BadCatalogPermissions if the
@@ -2377,7 +2435,7 @@
                         ulog.destroy()
 
                 self._attrs = CatalogAttrs(meta_root=self.meta_root,
-                    sign=self.__sign)
+                    sign=self.__sign, file_root=self.file_root)
                 self.__parts = {}
                 self.__updates = {}
                 self._attrs.destroy()
@@ -3406,7 +3464,8 @@
                 # Next, since the part hasn't been cached, create an object
                 # for it and add it to catalog attributes.
                 part = CatalogPart(name, meta_root=self.meta_root,
-                    ordered=not self.__batch_mode, sign=self.__sign)
+                    ordered=not self.__batch_mode, sign=self.__sign,
+                    file_root=self.file_root)
                 if must_exist and self.meta_root and not part.exists:
                         # This is a double-check for the client case where
                         # there is a part that is known to the catalog but
@@ -3884,6 +3943,7 @@
             doc="A UTC datetime object indicating the last time the catalog "
             "was modified.")
         meta_root = property(__get_meta_root, __set_meta_root)
+        file_root = property(__get_file_root, __set_file_root)
         sign = property(__get_sign, __set_sign)
         version = property(__get_version, __set_version)
 
--- a/src/modules/client/image.py	Thu Dec 08 03:41:00 2016 +0530
+++ b/src/modules/client/image.py	Thu Dec 08 03:41:02 2016 +0530
@@ -2651,6 +2651,7 @@
                                 progtrack.job_add_progress(
                                     progtrack.JOB_IMAGE_STATE)
                                 cat.meta_root = cpath
+                                cat.file_root = tmp_state_root
                                 cat.finalize(pfmris=added)
                                 progtrack.job_add_progress(
                                     progtrack.JOB_IMAGE_STATE)
@@ -2773,7 +2774,8 @@
                 # image upgrade or metadata refresh.  In both cases, the catalog
                 # is resorted and finalized so this is always safe to use.
                 cat = pkg.catalog.Catalog(batch_mode=True,
-                    manifest_cb=self._manifest_cb, meta_root=croot, sign=False)
+                    manifest_cb=self._manifest_cb, meta_root=croot, sign=False,
+                    file_root=self.imgdir)
                 return cat
 
         def __remove_catalogs(self):
@@ -2986,7 +2988,8 @@
 
                 kcat = pkg.catalog.Catalog(batch_mode=True,
                     meta_root=os.path.join(tmp_state_root,
-                    self.IMG_CATALOG_KNOWN), sign=False)
+                    self.IMG_CATALOG_KNOWN), sign=False,
+                    file_root=self.imgdir)
 
                 # XXX if any of the below fails for any reason, the old 'known'
                 # catalog needs to be re-loaded so the client is in a consistent
@@ -3038,7 +3041,8 @@
                 # Create the new installed catalog in a temporary location.
                 icat = pkg.catalog.Catalog(batch_mode=True,
                     meta_root=os.path.join(tmp_state_root,
-                    self.IMG_CATALOG_INSTALLED), sign=False)
+                    self.IMG_CATALOG_INSTALLED), sign=False,
+                    file_root=self.imgdir)
 
                 excludes = self.list_excludes()
 
--- a/src/modules/client/publisher.py	Thu Dec 08 03:41:00 2016 +0530
+++ b/src/modules/client/publisher.py	Thu Dec 08 03:41:02 2016 +0530
@@ -21,7 +21,7 @@
 #
 
 #
-# Copyright (c) 2009, 2014, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2009, 2016, Oracle and/or its affiliates. All rights reserved.
 #
 
 #
@@ -1371,6 +1371,7 @@
                 self.__meta_root = pathname
                 if self._catalog:
                         self._catalog.meta_root = self.catalog_root
+                        self._catalog.file_root = self.__meta_root
                 if self.__meta_root:
                         self.__origin_root = os.path.join(self.__meta_root,
                             "origins")
@@ -1501,7 +1502,7 @@
                                 # an image format upgrade.)
                                 croot = None
                         self._catalog = pkg.catalog.Catalog(
-                            meta_root=croot)
+                            meta_root=croot, file_root=self.meta_root)
                 return self._catalog
 
         @property
--- a/src/modules/misc.py	Thu Dec 08 03:41:00 2016 +0530
+++ b/src/modules/misc.py	Thu Dec 08 03:41:02 2016 +0530
@@ -2584,6 +2584,24 @@
                 # No patterns or matched at least one.
                 yield item
 
+def open_image_file(root, path, flag, mode=None):
+        """Safely open files that ensures that the path we'are accessing resides
+        within a specified image root.
+
+        'root' is a directory that the path must reside in.
+        """
+
+        try:
+                return os.fdopen(os.open(path, flag|os.O_NOFOLLOW, mode))
+        except EnvironmentError as e:
+                if e.errno != errno.ELOOP:
+                        # Access to protected member; pylint: disable=W0212
+                        raise api_errors._convert_error(e)
+        # If it is a symbolic link, fall back to ar_open. ar_open interprets
+        # 'path' as relative to 'root', that is, 'root' will be prepended to
+        # 'path', so we need to call os.path.relpath here.
+        from pkg.altroot import ar_open
+        return os.fdopen(ar_open(root, os.path.relpath(path, root), flag, mode))
 
 sigdict = defaultdict(list)
 
--- a/src/modules/p5p.py	Thu Dec 08 03:41:00 2016 +0530
+++ b/src/modules/p5p.py	Thu Dec 08 03:41:02 2016 +0530
@@ -21,7 +21,7 @@
 #
 
 #
-# Copyright (c) 2011, 2015, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2011, 2016, Oracle and/or its affiliates. All rights reserved.
 #
 
 import atexit
@@ -836,6 +836,7 @@
                 # as having catalog data cached.
                 croot = self.__mkdtemp()
                 cat.meta_root = croot
+                cat.file_root = croot
                 cat.batch_mode = False
                 cat.finalize()
                 cat.save()
--- a/src/tests/api/t_catalog.py	Thu Dec 08 03:41:00 2016 +0530
+++ b/src/tests/api/t_catalog.py	Thu Dec 08 03:41:02 2016 +0530
@@ -21,7 +21,7 @@
 # CDDL HEADER END
 #
 
-# Copyright (c) 2008, 2012, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2008, 2016, Oracle and/or its affiliates. All rights reserved.
 
 import testutils
 if __name__ == "__main__":
@@ -1247,6 +1247,75 @@
                 self.assertRaises(api_errors.UnrecognizedCatalogPart,
                     catalog.Catalog, meta_root=self.test_root)
 
+        def test_corrupt_attrs7(self):
+                """Raise UnrecognizedCatalogPart for a catalog.attrs{parts}
+                with bogus subpart."""
+
+                file_root = os.path.join(self.test_root, "file_root")
+                croot = os.path.join(self.test_root, "file_root", "catalog")
+                os.makedirs(file_root)
+                os.makedirs(croot)
+                # make catalog
+                c = catalog.Catalog(meta_root=croot, file_root=file_root)
+                c.save()
+
+                # Test catalog.attrs{parts} by adding a symbolic link to
+                # the set of parts, where the link has a good part name but
+                # reference a file outside of the image root.
+                for name in ["link", "shadow", "catalog.foo", "update.bar"]:
+                        # Create a file that is outside the 'file_root'.
+                        self.make_file(os.path.join(self.test_root, "foo"), "")
+                        # Symlink to the target.
+                        os.symlink(os.path.join(self.test_root, "foo"),
+                            os.path.join(croot, name))
+
+                        fname = os.path.join(croot, "catalog.attrs")
+                        with open(fname, "r") as f:
+                                struct = simplejson.load(f)
+                                struct["parts"][name] = {}
+                        with open(fname, "w") as f:
+                                print >> f, simplejson.dumps(struct)
+
+                        # Catalog constructor should reject busted 'parts'.
+                        self.assertRaises(api_errors.UnrecognizedCatalogPart,
+                            catalog.Catalog, meta_root=croot,
+                            file_root=file_root)
+
+                        # Clears the contrived subpart for next loop.
+                        with open(fname, "r") as f:
+                               struct = simplejson.load(f)
+                               del struct["parts"][name]
+                        with open(fname, "w") as f:
+                                print >> f, simplejson.dumps(struct)
+
+        def test_corrupt_attrs8(self):
+                """Raise UnrecognizedCatalogPart for a catalog.attrs file
+                itself being a symlink referencing a file outside of the
+                image root."""
+
+                file_root = os.path.join(self.test_root, "file_root")
+                croot = os.path.join(self.test_root, "file_root", "catalog")
+                os.makedirs(file_root)
+                os.makedirs(croot)
+                # make catalog
+                c = catalog.Catalog(meta_root=croot, file_root=file_root)
+                c.save()
+
+                self.make_file(os.path.join(self.test_root, "foo"), "")
+                # Make catalog.attrs being a symlink to reference a file
+                # outside of 'file_root'.
+                temp = os.path.join(croot, "temp")
+                attrs = os.path.join(croot, "catalog.attrs")
+                portable.rename(attrs, temp)
+                os.symlink(os.path.join(self.test_root, "foo"), attrs)
+                with open(temp, "r") as rf:
+                        with open(attrs, "w") as wf:
+                                wf.write(rf.read())
+
+                # Catalog constructor should reject busted 'parts'.
+                self.assertRaises(api_errors.UnrecognizedCatalogPart,
+                    catalog.Catalog, meta_root=croot,
+                    file_root=file_root)
 
 if __name__ == "__main__":
         unittest.main()
--- a/src/tests/cli/t_pkg_image_create.py	Thu Dec 08 03:41:00 2016 +0530
+++ b/src/tests/cli/t_pkg_image_create.py	Thu Dec 08 03:41:02 2016 +0530
@@ -21,7 +21,7 @@
 #
 
 #
-# Copyright (c) 2008, 2013, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2008, 2016, Oracle and/or its affiliates. All rights reserved.
 #
 
 import testutils
@@ -465,7 +465,7 @@
                 pub_path = os.path.join(img_root, "publisher")
 
                 v1_cat = pkg.catalog.Catalog(meta_root=os.path.join(pub_path,
-                    "test1", "catalog"), read_only=True)
+                    "test1", "catalog"), read_only=True, file_root=img_root)
                 v0_cat_path = os.path.join(cat_path, "test1")
 
                 # For conversion, the v0 catalogs need to be generated in
@@ -493,7 +493,8 @@
                 # The existing installed state has to be converted to v2.
                 state_dir = os.path.join(img_root, "state")
                 inst_state_dir = os.path.join(state_dir, "installed")
-                cat = pkg.catalog.Catalog(meta_root=inst_state_dir)
+                cat = pkg.catalog.Catalog(meta_root=inst_state_dir,
+                    file_root=img_root)
                 for f in cat.fmris():
                         self.__add_install_file(img_root, f)
                 cat = None