summaryrefslogtreecommitdiffstats
path: root/pym
diff options
context:
space:
mode:
authorZac Medico <zmedico@gentoo.org>2007-06-10 22:58:51 +0000
committerZac Medico <zmedico@gentoo.org>2007-06-10 22:58:51 +0000
commitd0dc4df791e8500e89c955a09436f5f2a0385d7e (patch)
treeb91444679fd695e05ab6478244d510dee99a0850 /pym
parent5ae7438dee18228c42b3b36455e7d9f7b3f5904a (diff)
downloadportage-d0dc4df791e8500e89c955a09436f5f2a0385d7e.tar.gz
portage-d0dc4df791e8500e89c955a09436f5f2a0385d7e.tar.bz2
portage-d0dc4df791e8500e89c955a09436f5f2a0385d7e.zip
For bug #81097, detect and report suspicious hardlinks to suid/sgid files. False positives are prevented by doing reference counts for each inode having suid/sgid bits and multiple hardlinks. The security check is done prior to each merge or unmerge phase and it will cause the phase to abort if a problem is found (so that the user can investigate before any files are removed). (trunk r6791:6794)
svn path=/main/branches/2.1.2/; revision=6795
Diffstat (limited to 'pym')
-rw-r--r--pym/output.py1
-rw-r--r--pym/portage.py66
2 files changed, 61 insertions, 6 deletions
diff --git a/pym/output.py b/pym/output.py
index bd9c474e6..ca265124a 100644
--- a/pym/output.py
+++ b/pym/output.py
@@ -127,6 +127,7 @@ codes["BRACKET"] = codes["blue"]
# Portage functions
codes["INFORM"] = codes["darkgreen"]
codes["UNMERGE_WARN"] = codes["red"]
+codes["SECURITY_WARN"] = codes["red"]
codes["MERGE_LIST_PROGRESS"] = codes["yellow"]
def parse_color_map():
diff --git a/pym/portage.py b/pym/portage.py
index bc2e979b7..c59edb941 100644
--- a/pym/portage.py
+++ b/pym/portage.py
@@ -7023,6 +7023,17 @@ class dblink:
before and after this method.
"""
+ # When new_contents is supplied, the security check has already been
+ # done for this slot, so it shouldn't be repeated until the next
+ # replacement or unmerge operation.
+ if new_contents is None:
+ slot = self.vartree.dbapi.aux_get(self.mycpv, ["SLOT"])[0]
+ slot_matches = self.vartree.dbapi.match(
+ "%s:%s" % (dep_getkey(self.mycpv), slot))
+ retval = self._security_check(slot_matches)
+ if retval:
+ return retval
+
contents = self.getcontents()
# Now, don't assume that the name of the ebuild is the same as the
# name of the dir; the package may have been moved.
@@ -7236,12 +7247,9 @@ class dblink:
writemsg_stdout("--- !md5 %s %s\n" % ("obj", obj))
continue
try:
- if statobj.st_mode & (stat.S_ISUID | stat.S_ISGID):
- # Always blind chmod 0 before unlinking to avoid race conditions.
- os.chmod(obj, 0000)
- if statobj.st_nlink > 1:
- writemsg("setXid: "+str(statobj.st_nlink-1)+ \
- " hardlinks to '%s'\n" % obj)
+ # Remove permissions to ensure that any hardlinks to
+ # suid/sgid files are rendered harmless.
+ os.chmod(obj, 0)
os.unlink(obj)
except (OSError,IOError),e:
pass
@@ -7305,6 +7313,48 @@ class dblink:
return False
+ def _security_check(self, slot_matches):
+ if not slot_matches:
+ return 0
+ file_paths = set()
+ for cpv in slot_matches:
+ file_paths.update(dblink(self.cat, catsplit(cpv)[1],
+ self.vartree.root, self.settings,
+ vartree=self.vartree).getcontents())
+ inode_map = {}
+ for path in file_paths:
+ try:
+ s = os.lstat(path)
+ except OSError, e:
+ if e.errno != errno.ENOENT:
+ raise
+ del e
+ continue
+ if stat.S_ISREG(s.st_mode) and \
+ s.st_nlink > 1 and \
+ s.st_mode & (stat.S_ISUID | stat.S_ISGID):
+ k = (s.st_dev, s.st_ino)
+ inode_map.setdefault(k, []).append((path, s))
+ suspicious_hardlinks = []
+ for path_list in inode_map.itervalues():
+ path, s = path_list[0]
+ if len(path_list) == s.st_nlink:
+ # All hardlinks seem to be owned by this package.
+ continue
+ suspicious_hardlinks.append(path_list)
+ if not suspicious_hardlinks:
+ return 0
+ from output import colorize
+ prefix = colorize("SECURITY_WARN", "*") + " WARNING: "
+ writemsg(prefix + "suid/sgid file(s) " + \
+ "with suspicious hardlink(s):\n", noiselevel=-1)
+ for path_list in suspicious_hardlinks:
+ for path, s in path_list:
+ writemsg(prefix + " '%s'\n" % path, noiselevel=-1)
+ writemsg(prefix + "See the Gentoo Security Handbook " + \
+ "guide for advice on how to proceed.\n", noiselevel=-1)
+ return 1
+
def treewalk(self, srcroot, destroot, inforoot, myebuild, cleanup=0,
mydbapi=None, prev_mtimes=None):
"""
@@ -7352,6 +7402,10 @@ class dblink:
slot_matches = self.vartree.dbapi.match(
"%s:%s" % (self.mysplit[0], self.settings["SLOT"]))
+ retval = self._security_check(slot_matches)
+ if retval:
+ return retval
+
if slot_matches:
# Used by self.isprotected().
max_cpv = None