For bug #81097, detect and report suspicious hardlinks to suid/sgid files. False...
authorZac Medico <zmedico@gentoo.org>
Sun, 10 Jun 2007 22:58:51 +0000 (22:58 -0000)
committerZac Medico <zmedico@gentoo.org>
Sun, 10 Jun 2007 22:58:51 +0000 (22:58 -0000)
svn path=/main/branches/2.1.2/; revision=6795

pym/output.py
pym/portage.py

index bd9c474e6ee3b5acbf242d3153c16a625f110fc5..ca265124ac738a7af1400b2ae3b9990981046946 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():
index bc2e979b7a1eab4c9da8b66d9c128b52c980e506..c59edb941a88bca4801a3b48d3133fa4f3a9a5e1 100644 (file)
@@ -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