From e209120b9862c490f7151d64c1b4ce0a82d95278 Mon Sep 17 00:00:00 2001 From: Zac Medico Date: Sun, 10 Jun 2007 21:44:15 +0000 Subject: 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). svn path=/main/trunk/; revision=6792 --- pym/portage/dbapi/vartree.py | 56 ++++++++++++++++++++++++++++++++++++++++++++ pym/portage/output.py | 1 + 2 files changed, 57 insertions(+) diff --git a/pym/portage/dbapi/vartree.py b/pym/portage/dbapi/vartree.py index 5ddfc2f0f..73d2245f9 100644 --- a/pym/portage/dbapi/vartree.py +++ b/pym/portage/dbapi/vartree.py @@ -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 diff --git a/pym/portage/output.py b/pym/portage/output.py index 83ff3f1c2..deb8c548b 100644 --- a/pym/portage/output.py +++ b/pym/portage/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(): -- cgit v1.2.3-1-g7c22