For bug #81097, detect and report suspicious hardlinks to suid/sgid files. False...
authorZac Medico <zmedico@gentoo.org>
Sun, 10 Jun 2007 21:44:15 +0000 (21:44 -0000)
committerZac Medico <zmedico@gentoo.org>
Sun, 10 Jun 2007 21:44:15 +0000 (21:44 -0000)
svn path=/main/trunk/; revision=6792

pym/portage/dbapi/vartree.py
pym/portage/output.py

index 5ddfc2f0fff6b461edce487f79a7284e67595d0b..73d2245f9d5e419730087ad42ecaeffe52b00b05 100644 (file)
@@ -996,6 +996,17 @@ class dblink(object):
                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.
@@ -1458,6 +1469,47 @@ class dblink(object):
                        except OSError:
                                pass
 
+       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 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 portage.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):
                """
@@ -1507,6 +1559,10 @@ class dblink(object):
 
                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
index 83ff3f1c283551077af31cdbe2e7d5ff9f007cf2..deb8c548b36c1035443f6ccd17f0b54c0b467f39 100644 (file)
@@ -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():