--- 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