From: Zac Medico Date: Sun, 10 Jun 2007 22:58:51 +0000 (-0000) Subject: For bug #81097, detect and report suspicious hardlinks to suid/sgid files. False... X-Git-Tag: v2.1.3_rc1~19 X-Git-Url: http://git.tremily.us/?a=commitdiff_plain;h=d0dc4df791e8500e89c955a09436f5f2a0385d7e;p=portage.git 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 --- 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