Exemple #1
0
 def check_description(self, pkg, lang, ignored_words):
     description = pkg.langtag(rpm.RPMTAG_DESCRIPTION, lang)
     if not Pkg.is_utf8_bytestr(description):
         self.output.add_info('E', pkg, 'tag-not-utf8', '%description',
                              lang)
     description = byte_to_string(description)
     self._unexpanded_macros(pkg, '%%description -l %s' % lang, description)
     if self.spellcheck:
         pkgname = byte_to_string(pkg.header[rpm.RPMTAG_NAME])
         typos = self.spellchecker.spell_check(description,
                                               '%description -l {}', lang,
                                               pkgname, ignored_words)
         for typo in typos.items():
             self.output.add_info('E', pkg, 'spelling-error', typo)
     for l in description.splitlines():
         if len(l) > self.max_line_len:
             self.output.add_info('E', pkg, 'description-line-too-long',
                                  lang, l)
         res = self.forbidden_words_regex.search(l)
         if res and self.config.configuration['ForbiddenWords']:
             self.output.add_info('W', pkg, 'description-use-invalid-word',
                                  lang, res.group(1))
         res = tag_regex.search(l)
         if res:
             self.output.add_info('W', pkg, 'tag-in-description', lang,
                                  res.group(1))
Exemple #2
0
    def _gather_aux(self, header, list, nametag, flagstag, versiontag,
                    prereq=None):
        names = header[nametag]
        flags = header[flagstag]
        versions = header[versiontag]

        if versions:
            for loop in range(len(versions)):
                name = byte_to_string(names[loop])
                evr = stringToVersion(byte_to_string(versions[loop]))
                if prereq is not None and flags[loop] & PREREQ_FLAG:
                    prereq.append((name, flags[loop] & (~PREREQ_FLAG), evr))
                else:
                    list.append((name, flags[loop], evr))
Exemple #3
0
    def __comparePRCOs(self, old, new, name):
        try:
            oldflags = old[name[:-1] + 'FLAGS']
        except ValueError:
            # assume tag not supported, e.g. Recommends with older rpm
            return
        newflags = new[name[:-1] + 'FLAGS']
        # fix buggy rpm binding not returning list for single entries
        if not isinstance(oldflags, list):
            oldflags = [oldflags]
        if not isinstance(newflags, list):
            newflags = [newflags]

        o = zip(old[name], oldflags, old[name[:-1] + 'VERSION'])
        if not isinstance(o, list):
            o = list(o)
        n = zip(new[name], newflags, new[name[:-1] + 'VERSION'])
        if not isinstance(n, list):
            n = list(n)

        # filter self provides, TODO: self %name(%_isa) as well
        if name == 'PROVIDES':
            oldE = old['epoch'] is not None and str(old['epoch']) + ':' or ''
            oldV = '%s%s' % (oldE, old.format('%{VERSION}-%{RELEASE}'))
            oldNV = (old['name'], rpm.RPMSENSE_EQUAL, oldV.encode())
            newE = new['epoch'] is not None and str(new['epoch']) + ':' or ''
            newV = '%s%s' % (newE, new.format('%{VERSION}-%{RELEASE}'))
            newNV = (new['name'], rpm.RPMSENSE_EQUAL, newV.encode())
            o = [entry for entry in o if entry != oldNV]
            n = [entry for entry in n if entry != newNV]

        for oldentry in o:
            if oldentry not in n:
                namestr = name
                if namestr == 'REQUIRES':
                    namestr = self.req2str(oldentry[1])
                self.__add(
                    self.DEPFORMAT,
                    (self.REMOVED, namestr, byte_to_string(oldentry[0]),
                     self.sense2str(oldentry[1]), byte_to_string(oldentry[2])))
        for newentry in n:
            if newentry not in o:
                namestr = name
                if namestr == 'REQUIRES':
                    namestr = self.req2str(newentry[1])
                self.__add(
                    self.DEPFORMAT,
                    (self.ADDED, namestr, byte_to_string(newentry[0]),
                     self.sense2str(newentry[1]), byte_to_string(newentry[2])))
    def check(self, pkg):
        if pkg.is_source:
            return

        # populate scriptlets
        self.post = byte_to_string(pkg.header[rpm.RPMTAG_POSTIN])
        self.postun = byte_to_string(pkg.header[rpm.RPMTAG_POSTUN])

        if not self._check_ua_presence(pkg):
            return

        self._check_requirements(pkg)
        self._check_post_phase(pkg, self.post)
        self._check_postun_phase(pkg, self.postun)
        self._check_filelist(pkg)
Exemple #5
0
def getstatusoutput(cmd, stdoutonly=False, shell=False, raw=False, lc_all='C'):
    """
    A version of commands.getstatusoutput() which can take cmd as a
    sequence, thus making it potentially more secure.
    """
    env = dict(os.environ, LC_ALL=lc_all)
    if stdoutonly:
        proc = subprocess.Popen(cmd,
                                shell=shell,
                                stdin=subprocess.PIPE,
                                stdout=subprocess.PIPE,
                                close_fds=True,
                                env=env)
    else:
        proc = subprocess.Popen(cmd,
                                shell=shell,
                                stdin=subprocess.PIPE,
                                stdout=subprocess.PIPE,
                                env=env,
                                stderr=subprocess.STDOUT,
                                close_fds=True)
    proc.stdin.close()
    with proc.stdout:
        text = proc.stdout.read()
    sts = proc.wait()
    if not raw:
        text = byte_to_string(text)
        if text.endswith('\n'):
            text = text[:-1]
    if sts is None:
        sts = 0
    return sts, text
Exemple #6
0
def test_bytetostr():
    """
    Test bytetostr function
    """
    list_items = (
        b'\xc5\xbe\xc3\xad\xc5\xbeala',
        'texty',
    )
    item = b'p\xc5\x99\xc3\xad\xc5\xa1ern\xc4\x9b \xc5\xbelu\xc5\xa5ou\xc4\x8dk\xc3\xbd k\xc5\xaf\xc5\x88'

    result = helpers.byte_to_string(item)
    assert isinstance(result, str)
    assert result == 'příšerně žluťoučký kůň'

    result = helpers.byte_to_string(list_items)
    assert isinstance(result, list)
    assert result[0] == 'žížala'
Exemple #7
0
def get_magic(path):
    # file() method evaluates every file twice with python2,
    # use descriptor() method instead
    try:
        fd = os.open(path, os.O_RDONLY)
        magic = byte_to_string(_magic.descriptor(fd))
        os.close(fd)
        return magic
    except OSError:
        return ''
Exemple #8
0
 def check_file(self, pkg, filename):
     beam = BeamFile(pkg.files[filename].path)
     compile_state = byte_to_string(beam.compileinfo['source'].value)
     if 'debug_info' not in beam.compileinfo['options']:
         self.output.add_info('E', pkg, 'beam-compiled-without-debuginfo', filename)
     # This can't be an error as builddir can be user specific and vary between users
     # it could be error in OBS where all the builds are done by user abuild, not in
     # general.
     if not self.source_re.match(compile_state):
         self.output.add_info('W', pkg, 'beam-was-not-recompiled', filename, compile_state)
Exemple #9
0
 def check_summary(self, pkg, lang, ignored_words):
     summary = pkg.langtag(rpm.RPMTAG_SUMMARY, lang)
     if not Pkg.is_utf8_bytestr(summary):
         self.output.add_info('E', pkg, 'tag-not-utf8', 'Summary', lang)
     summary = byte_to_string(summary)
     self._unexpanded_macros(pkg, 'Summary(%s)' % lang, summary)
     if self.spellcheck:
         pkgname = byte_to_string(pkg.header[rpm.RPMTAG_NAME])
         typos = self.spellchecker.spell_check(summary, 'Summary({})', lang,
                                               pkgname, ignored_words)
         for typo in typos.items():
             self.output.add_info('E', pkg, 'spelling-error', typo)
     if '\n' in summary:
         self.output.add_info('E', pkg, 'summary-on-multiple-lines', lang)
     if (summary[0] != summary[0].upper()
             and summary.partition(' ')[0] not in CAPITALIZED_IGNORE_LIST):
         self.output.add_info('W', pkg, 'summary-not-capitalized', lang,
                              summary)
     if summary[-1] == '.':
         self.output.add_info('W', pkg, 'summary-ended-with-dot', lang,
                              summary)
     if len(summary) > self.max_line_len:
         self.output.add_info('E', pkg, 'summary-too-long', lang, summary)
     if leading_space_regex.search(summary):
         self.output.add_info('E', pkg, 'summary-has-leading-spaces', lang,
                              summary)
     res = self.forbidden_words_regex.search(summary)
     if res and self.config.configuration['ForbiddenWords']:
         self.output.add_info('W', pkg, 'summary-use-invalid-word', lang,
                              res.group(1))
     if pkg.name:
         sepchars = r'[\s%s]' % punct
         res = re.search(
             r'(?:^|\s)(%s)(?:%s|$)' % (re.escape(pkg.name), sepchars),
             summary, re.IGNORECASE | re.UNICODE)
         if res:
             self.output.add_info('W', pkg, 'name-repeated-in-summary',
                                  lang, res.group(1))
    def check(self, pkg):
        if pkg.is_source:
            return

        if self._check_libalternatives_presence(pkg):
            self.output.add_info('I', pkg, 'package supports libalternatives')
            self._check_libalternatives_requirements(pkg)
            self._check_libalternatives_filelist(pkg)

        self.install_binaries = {}
        self.slave_binaries = []
        # populate scriptlets
        self.post = byte_to_string(pkg.header[rpm.RPMTAG_POSTIN])
        self.postun = byte_to_string(pkg.header[rpm.RPMTAG_POSTUN])

        if not self._check_ua_presence(pkg):
            return
        self.output.add_info('I', pkg, 'package supports update-alternatives')

        self._check_requirements(pkg)
        self._check_post_phase(pkg, self.post)
        self._check_postun_phase(pkg, self.postun)
        self._check_filelist(pkg)
 def _normalize_script(self, script):
     """
     Remove "backslash+newline" to keep all commands as oneliners.
     Remove single and double quotes everywhere.
     Keep only the line that contains the update-alternatives call.
     Return the list of lines that contain update-alternatives calls
     """
     # with old rpm we get wrong type
     script = byte_to_string(script)
     script.replace('\\\n', '')
     script.replace('"', '')
     script.replace("'", '')
     script.strip()
     return [i for i in script.splitlines() if self.command in i]
Exemple #12
0
    def _check_doc_file_dependencies(self, pkg):
        """
        Check if docfiles create additional dependencies in the package and
        print a warning if so.
        """
        files = pkg.files

        reqs = {}
        for fname, pkgfile in files.items():
            reqs[fname] = [x[0] for x in pkgfile.requires]

        core_reqs = {}  # dependencies of non-doc files
        doc_reqs = {}  # dependencies of doc files

        for dep in pkg.header.dsFromHeader():
            # skip deps which were found by find-requires
            if dep.Flags() & rpm.RPMSENSE_FIND_REQUIRES != 0:
                continue
            core_reqs[dep.N()] = []

        # register things which are provided by the package
        for i in pkg.header[rpm.RPMTAG_PROVIDES]:
            core_reqs[byte_to_string(i)] = []
        for i in files:
            core_reqs[i] = []

        for i in files:
            if not reqs[i]:
                continue  # skip empty dependencies
            if i in pkg.doc_files:
                target = doc_reqs
            else:
                target = core_reqs

            for r in reqs[i]:
                if r not in target:
                    target[r] = []
                target[r].append(i)

        # go through the calculated requirements of the %doc files
        for (dep, req_files) in doc_reqs.items():
            if dep not in core_reqs:
                for f in req_files:
                    self.output.add_info('W', pkg, 'doc-file-dependency', f,
                                         dep)
Exemple #13
0
 def __getitem__(self, key):
     try:
         val = self.header[key]
     except KeyError:
         val = []
     if val == []:
         return None
     else:
         # Note that text tags we want to try decoding for real in TagsCheck
         # such as summary, description and changelog are not here.
         if key in (rpm.RPMTAG_NAME, rpm.RPMTAG_VERSION, rpm.RPMTAG_RELEASE,
                    rpm.RPMTAG_ARCH, rpm.RPMTAG_GROUP, rpm.RPMTAG_BUILDHOST,
                    rpm.RPMTAG_LICENSE, rpm.RPMTAG_HEADERI18NTABLE,
                    rpm.RPMTAG_PACKAGER, rpm.RPMTAG_SOURCERPM,
                    rpm.RPMTAG_DISTRIBUTION, rpm.RPMTAG_VENDOR) \
         or key in (x[0] for x in SCRIPT_TAGS) \
         or key in (x[1] for x in SCRIPT_TAGS):
             val = byte_to_string(val)
         return val
Exemple #14
0
 def _check_url(self, pkg):
     """Trigger check invalid-url, no-url-tag """
     for tag in ('URL', 'DistURL', 'BugURL'):
         if hasattr(rpm, 'RPMTAG_{}'.format(tag.upper())):
             url = byte_to_string(pkg[getattr(
                 rpm, 'RPMTAG_{}'.format(tag.upper()))])
             self._unexpanded_macros(pkg, tag, url, is_url=True)
             if url:
                 (scheme, netloc) = urlparse(url)[0:2]
                 # Check if a package contains a unreasonable URL
                 # [This check is also triggered with Source: tag value]
                 if not scheme or not netloc or '.' not in netloc or \
                         scheme not in ('http', 'https', 'ftp') or \
                         (self.config.configuration['InvalidURL'] and
                          self.invalid_url_regex.search(url)):
                     self.output.add_info('W', pkg, 'invalid-url', tag, url)
             # Check if a package does not have a URL: tag in its spec file
             elif tag == 'URL':
                 self.output.add_info('W', pkg, 'no-url-tag')
Exemple #15
0
    def __init__(self, config, output, pkg, path, fname, is_ar, is_shlib):
        self.readelf_error = False
        self.needed = []
        self.rpath = []
        self.undef = []
        self.unused = []
        self.config = config
        self.output = output
        self.comment = False
        self.soname = False
        self.non_pic = True
        self.stack = False
        self.exec_stack = False
        self.exit_calls = []
        self.forbidden_calls = []
        fork_called = False
        self.tail = ''
        self.lto_sections = False
        self.no_text_in_archive = False

        self.setgid = False
        self.setuid = False
        self.setgroups = False
        self.mktemp = False
        self.forbidden_functions = self.config.configuration['WarnOnFunction']
        if self.forbidden_functions:
            for name, func in self.forbidden_functions.items():
                # precompile regexps
                f_name = func['f_name']
                func['f_regex'] = create_nonlibc_regexp_call(f_name)
                if 'good_param' in func and func['good_param']:
                    func['waiver_regex'] = re.compile(func['good_param'])
                # register descriptions
                self.output.error_details.update({name: func['description']})

        is_debug = path.endswith('.debug')
        is_archive = path.endswith('.a')

        res = Pkg.getstatusoutput(
            ('readelf', '-W', '-S', '-l', '-d', '-s', path))
        if not res[0]:
            lines = res[1].splitlines()

            # For an archive, test if all .text sections are empty
            if is_archive:
                has_text_segment = False
                non_zero_text_segment = False

                for line in lines:
                    r = self.text_section_regex.search(line)
                    if r:
                        has_text_segment = True
                        size = int(r.group(1), 16)
                        if size > 0:
                            non_zero_text_segment = True
                if has_text_segment and not non_zero_text_segment:
                    self.no_text_in_archive = True

            for line in lines:
                if self.lto_section_name_prefix in line:
                    self.lto_sections = True

                r = self.needed_regex.search(line)
                if r:
                    self.needed.append(r.group(1))
                    continue

                r = self.rpath_regex.search(line)
                if r:
                    for p in r.group(1).split(':'):
                        self.rpath.append(p)
                    continue

                if self.comment_regex.search(line):
                    self.comment = True
                    continue

                if self.pic_regex.search(line):
                    self.non_pic = False
                    continue

                r = self.soname_regex.search(line)
                if r:
                    self.soname = r.group(1)
                    continue

                r = self.stack_regex.search(line)
                if r:
                    self.stack = True
                    flags = r.group(1)
                    if flags and self.stack_exec_regex.search(flags):
                        self.exec_stack = True
                    continue

                if line.startswith('Symbol table'):
                    break

            for line in lines:
                r = self.call_regex.search(line)
                if not r:
                    continue
                line = r.group(1)

                if self.mktemp_call_regex.search(line):
                    self.mktemp = True

                if self.setgid_call_regex.search(line):
                    self.setgid = True

                if self.setuid_call_regex.search(line):
                    self.setuid = True

                if self.setgroups_call_regex.search(line):
                    self.setgroups = True

                if self.forbidden_functions:
                    for r_name, func in self.forbidden_functions.items():
                        ret = func['f_regex'].search(line)
                        if ret:
                            self.forbidden_calls.append(r_name)

                if is_shlib:
                    r = self.exit_call_regex.search(line)
                    if r:
                        self.exit_calls.append(r.group(1))
                        continue
                    r = self.fork_call_regex.search(line)
                    if r:
                        fork_called = True
                        continue

            # check if we don't have a string that will automatically
            # waive the presence of a forbidden call
            if self.forbidden_calls:
                res = Pkg.getstatusoutput(('strings', path))
                if not res[0]:
                    for line in res[1].splitlines():
                        # as we need to remove elements, iterate backwards
                        for i in range(len(self.forbidden_calls) - 1, -1, -1):
                            func = self.forbidden_calls[i]
                            f = self.forbidden_functions[func]
                            if 'waiver_regex' not in f:
                                continue
                            r = f['waiver_regex'].search(line)
                            if r:
                                del self.forbidden_calls[i]

            if self.non_pic:
                self.non_pic = 'TEXTREL' in res[1]

            # Ignore all exit() calls if fork() is being called.
            # Does not have any context at all but without this kludge, the
            # number of false positives would probably be intolerable.
            if fork_called:
                self.exit_calls = []
        else:
            self.readelf_error = True
            # Go and others are producing ar archives that don't have ELF
            # headers, so don't complain about it
            if not is_ar:
                self.output.add_info('W', pkg, 'binaryinfo-readelf-failed',
                                     fname, re.sub('\n.*', '', res[1]))

        try:
            with open(path, 'rb') as fobj:
                fobj.seek(-12, os.SEEK_END)
                self.tail = byte_to_string(fobj.read())
        except Exception as e:
            self.output.add_info('W', pkg,
                                 'binaryinfo-tail-failed %s: %s' % (fname, e))

        # Undefined symbol and unused direct dependency checks make sense only
        # for installed packages.
        # skip debuginfo: https://bugzilla.redhat.com/190599
        if not is_ar and not is_debug and isinstance(pkg, Pkg.InstalledPkg):
            # We could do this with objdump, but it's _much_ simpler with ldd.
            res = Pkg.getstatusoutput(('ldd', '-d', '-r', path))
            if not res[0]:
                for line in res[1].splitlines():
                    undef = self.undef_regex.search(line)
                    if undef:
                        self.undef.append(undef.group(1))
                if self.undef:
                    try:
                        res = Pkg.getstatusoutput(['c++filt'] + self.undef)
                        if not res[0]:
                            self.undef = res[1].splitlines()
                    except OSError:
                        pass
            else:
                self.output.add_info('W', pkg, 'ldd-failed', fname)
            res = Pkg.getstatusoutput(('ldd', '-r', '-u', path))
            if res[0]:
                # Either ldd doesn't grok -u (added in glibc 2.3.4) or we have
                # unused direct dependencies
                in_unused = False
                for line in res[1].splitlines():
                    if not line.rstrip():
                        pass
                    elif line.startswith('Unused direct dependencies'):
                        in_unused = True
                    elif in_unused:
                        unused = self.unused_regex.search(line)
                        if unused:
                            self.unused.append(unused.group(1))
                        else:
                            in_unused = False
Exemple #16
0
    def _gatherFilesInfo(self):

        ret = {}
        flags = self.header[rpm.RPMTAG_FILEFLAGS]
        modes = self.header[rpm.RPMTAG_FILEMODES]
        users = self.header[rpm.RPMTAG_FILEUSERNAME]
        groups = self.header[rpm.RPMTAG_FILEGROUPNAME]
        links = [byte_to_string(x) for x in self.header[rpm.RPMTAG_FILELINKTOS]]
        sizes = self.header[rpm.RPMTAG_FILESIZES]
        md5s = self.header[rpm.RPMTAG_FILEMD5S]
        mtimes = self.header[rpm.RPMTAG_FILEMTIMES]
        rdevs = self.header[rpm.RPMTAG_FILERDEVS]
        langs = self.header[rpm.RPMTAG_FILELANGS]
        inodes = self.header[rpm.RPMTAG_FILEINODES]
        requires = [byte_to_string(x) for x in self.header[rpm.RPMTAG_FILEREQUIRE]]
        provides = [byte_to_string(x) for x in self.header[rpm.RPMTAG_FILEPROVIDE]]
        files = [byte_to_string(x) for x in self.header[rpm.RPMTAG_FILENAMES]]
        magics = [byte_to_string(x) for x in self.header[rpm.RPMTAG_FILECLASS]]
        try:  # rpm >= 4.7.0
            filecaps = self.header[rpm.RPMTAG_FILECAPS]
        except AttributeError:
            filecaps = None

        # rpm-python < 4.6 does not return a list for this (or FILEDEVICES,
        # FWIW) for packages containing exactly one file
        if not isinstance(inodes, list):
            inodes = [inodes]

        if files:
            for idx in range(0, len(files)):
                pkgfile = PkgFile(files[idx])
                pkgfile.path = os.path.normpath(os.path.join(
                    self.dirName() or '/', pkgfile.name.lstrip('/')))
                pkgfile.flags = flags[idx]
                pkgfile.mode = modes[idx]
                pkgfile.user = byte_to_string(users[idx])
                pkgfile.group = byte_to_string(groups[idx])
                pkgfile.linkto = links[idx] and os.path.normpath(links[idx])
                pkgfile.size = sizes[idx]
                pkgfile.md5 = md5s[idx]
                pkgfile.mtime = mtimes[idx]
                pkgfile.rdev = rdevs[idx]
                pkgfile.inode = inodes[idx]
                pkgfile.requires = parse_deps(requires[idx])
                pkgfile.provides = parse_deps(provides[idx])
                pkgfile.lang = byte_to_string(langs[idx])
                pkgfile.magic = magics[idx]
                if not pkgfile.magic:
                    if stat.S_ISDIR(pkgfile.mode):
                        pkgfile.magic = 'directory'
                    elif stat.S_ISLNK(pkgfile.mode):
                        pkgfile.magic = "symbolic link to `%s'" % pkgfile.linkto
                    elif not pkgfile.size:
                        pkgfile.magic = 'empty'
                if (not pkgfile.magic and
                        not pkgfile.is_ghost and _magic):
                    # file() method evaluates every file twice with python2,
                    # use descriptor() method instead
                    try:
                        fd = os.open(pkgfile.path, os.O_RDONLY)
                        pkgfile.magic = byte_to_string(_magic.descriptor(fd))
                        os.close(fd)
                    except OSError:
                        pass
                if pkgfile.magic is None:
                    pkgfile.magic = ''
                elif Pkg._magic_from_compressed_re.search(pkgfile.magic):
                    # Discard magic from inside compressed files ('file -z')
                    # until PkgFile gets decompression support.  We may get
                    # such magic strings from package headers already now;
                    # for example Fedora's rpmbuild as of F-11's 4.7.1 is
                    # patched so it generates them.
                    pkgfile.magic = ''
                if filecaps:
                    pkgfile.filecaps = filecaps[idx]
                ret[pkgfile.name] = pkgfile
        return ret
Exemple #17
0
    def check(self, pkg):
        """Contains methods that checks tags and values in a spec file of a package."""

        version = pkg[rpm.RPMTAG_VERSION]
        release = pkg[rpm.RPMTAG_RELEASE]
        epoch = pkg[rpm.RPMTAG_EPOCH]
        group = pkg[rpm.RPMTAG_GROUP]
        buildhost = pkg[rpm.RPMTAG_BUILDHOST]
        langs = pkg[rpm.RPMTAG_HEADERI18NTABLE]
        summary = byte_to_string(pkg[rpm.RPMTAG_SUMMARY])
        description = byte_to_string(pkg[rpm.RPMTAG_DESCRIPTION])
        changelog = pkg[rpm.RPMTAG_CHANGELOGNAME]
        rpm_license = pkg[rpm.RPMTAG_LICENSE]
        name = pkg.name
        deps = pkg.requires + pkg.prereq
        is_devel = FilesCheck.devel_regex.search(name)
        is_source = pkg.is_source

        # List of words to ignore in spell check
        ignored_words = set()
        for pf in pkg.files:
            ignored_words.update(pf.split('/'))
        for tag in ('provides', 'requires', 'conflicts', 'obsoletes'):
            ignored_words.update((x[0] for x in 'pkg.' + str(tag)))

        # Run checks for whole package
        self._check_invalid_packager(pkg)
        self._check_invalid_version_and_no_version_tag(pkg, version)
        self._check_non_standard_release_extension(pkg, release)
        self._check_no_epoch_tag(pkg, epoch)
        self._check_no_epoch_in_tags(pkg)
        self._check_multiple_dependencies(pkg, deps, is_devel, is_source)
        self._unexpanded_macros(pkg, 'Name', name)
        self._check_multiple_tags(pkg, name, is_devel, is_source, deps, epoch,
                                  version)
        self._check_summary_tag(pkg, summary, langs, ignored_words)
        self._check_description_tag(pkg, description, langs, ignored_words)
        self._check_group_tag(pkg, group)
        self._check_buildhost_tag(pkg, buildhost)
        self._check_changelog_tag(pkg, changelog, version, release, name,
                                  epoch)
        self._check_license(pkg, rpm_license)
        self._check_url(pkg)

        prov_names = [x[0] for x in pkg.provides]

        self._check_obsolete_not_provided(pkg, prov_names)

        for dep_token in pkg.obsoletes:
            value = Pkg.formatRequire(*dep_token)
            self._unexpanded_macros(pkg, 'Obsoletes {}'.format(value, ), value)

        self._check_useless_provides(pkg, prov_names)
        self._check_forbidden_controlchar(pkg)
        self._check_self_obsoletion(pkg)
        self._check_non_coherent_filename(pkg)

        for tag in ('Distribution', 'DistTag', 'ExcludeArch', 'ExcludeOS',
                    'Vendor'):
            if hasattr(rpm, 'RPMTAG_%s' % tag.upper()):
                res = byte_to_string(pkg[getattr(rpm,
                                                 'RPMTAG_%s' % tag.upper())])
                self._unexpanded_macros(pkg, tag, res)
Exemple #18
0
    def _check_changelog_tag(self, pkg, changelog, version, release, name,
                             epoch):
        """Trigger multiple check of type *-changelog, *-changelogname-*, changelog-*
        and forbidden-controlchar

        Contains all the checks that cause an issue during build of the rpm
        in the %changelog of the specfile

        Args:
            changelog: Find the %changelog in the specfile

        Returns:
            Output info to STDOUT
        """

        # Check if a package does not have a %changelog in its spec file
        if not changelog:
            self.output.add_info('E', pkg, 'no-changelogname-tag')
        else:
            clt = pkg[rpm.RPMTAG_CHANGELOGTEXT]
            if self.use_version_in_changelog:
                ret = changelog_version_regex.search(
                    byte_to_string(changelog[0]))
                if not ret and clt:
                    # we also allow the version specified as the first
                    # thing on the first line of the text
                    ret = changelog_text_version_regex.search(
                        byte_to_string(clt[0]))
                # Check if a package does not have version in the %changelog in latest version
                if not ret:
                    self.output.add_info('W', pkg,
                                         'no-version-in-last-changelog')
                elif version and release:
                    srpm = pkg[rpm.RPMTAG_SOURCERPM] or ''
                    # only check when source name correspond to name
                    if srpm[0:-8] == '%s-%s-%s' % (name, version, release):
                        expected = [version + '-' + release]
                        if epoch is not None:  # regardless of use_epoch
                            expected[0] = str(epoch) + ':' + expected[0]
                        # Allow EVR in changelog without release extension,
                        # the extension is often a macro or otherwise dynamic.
                        if self.release_ext:
                            expected.append(
                                self.extension_regex.sub('', expected[0]))
                        # Check if a package does not have a version that is
                        # compatible with epoch:vesrion-release tuple
                        if ret.group(1) not in expected:
                            if len(expected) == 1:
                                expected = expected[0]
                            self.output.add_info(
                                'W', pkg, 'incoherent-version-in-changelog',
                                ret.group(1), expected)
            if clt:
                changelog = changelog + clt
            for deptoken in changelog:
                dep = Pkg.has_forbidden_controlchars(deptoken)
                # Check if a package contains a forbidden character in %changelog
                if dep:
                    self.output.add_info('E', pkg,
                                         'forbidden-controlchar-found',
                                         '%%changelog : %s' % dep)
                    break

            clt = pkg[rpm.RPMTAG_CHANGELOGTIME][0]
            if clt:
                clt -= clt % (24 * 3600)  # roll back to 00:00:00, see #246
                # Check if a package contains a changelog entry that is suspiciously too far behind
                if clt < oldest_changelog_timestamp:
                    self.output.add_info(
                        'W', pkg, 'changelog-time-overflow',
                        time.strftime('%Y-%m-%d', time.gmtime(clt)))
                # Check if a package contians a entry in %changelog
                # with timestamp thats in the future of its writing
                elif clt > time.time():
                    self.output.add_info(
                        'E', pkg, 'changelog-time-in-future',
                        time.strftime('%Y-%m-%d', time.gmtime(clt)))
Exemple #19
0
    def check(self, pkg):

        packager = pkg[rpm.RPMTAG_PACKAGER]
        if packager:
            self._unexpanded_macros(pkg, 'Packager', packager)
            if self.config.configuration['Packager'] and \
               not self.packager_regex.search(packager):
                self.output.add_info('W', pkg, 'invalid-packager', packager)
        else:
            self.output.add_info('E', pkg, 'no-packager-tag')

        version = pkg[rpm.RPMTAG_VERSION]
        if version:
            self._unexpanded_macros(pkg, 'Version', version)
            res = invalid_version_regex.search(version)
            if res:
                self.output.add_info('E', pkg, 'invalid-version', version)
        else:
            self.output.add_info('E', pkg, 'no-version-tag')

        release = pkg[rpm.RPMTAG_RELEASE]
        if release:
            self._unexpanded_macros(pkg, 'Release', release)
            if self.release_ext and not self.extension_regex.search(release):
                self.output.add_info('W', pkg,
                                     'not-standard-release-extension', release)
        else:
            self.output.add_info('E', pkg, 'no-release-tag')

        epoch = pkg[rpm.RPMTAG_EPOCH]
        if epoch is None:
            if self.use_epoch:
                self.output.add_info('E', pkg, 'no-epoch-tag')
        else:
            if epoch > 99:
                self.output.add_info('W', pkg, 'unreasonable-epoch', epoch)
            epoch = str(epoch)

        if self.use_epoch:
            for tag in ('obsoletes', 'conflicts', 'provides', 'recommends',
                        'suggests', 'enhances', 'supplements'):
                for x in (x for x in getattr(pkg, tag)()
                          if x[1] and x[2][0] is None):
                    self.output.add_info('W', pkg, 'no-epoch-in-%s' % tag,
                                         Pkg.formatRequire(*x))

        name = pkg.name
        deps = pkg.requires + pkg.prereq
        devel_depend = False
        is_devel = FilesCheck.devel_regex.search(name)
        is_source = pkg.is_source
        for d in deps:
            value = Pkg.formatRequire(*d)
            if self.use_epoch and d[1] and d[2][0] is None and \
                    not d[0].startswith('rpmlib('):
                self.output.add_info('W', pkg, 'no-epoch-in-dependency', value)
            for r in self.invalid_requires:
                if r.search(d[0]):
                    self.output.add_info('E', pkg, 'invalid-dependency', d[0])

            if d[0].startswith('/usr/local/'):
                self.output.add_info('E', pkg, 'invalid-dependency', d[0])

            if is_source:
                if lib_devel_number_regex.search(d[0]):
                    self.output.add_info('E', pkg, 'invalid-build-requires',
                                         d[0])
            elif not is_devel:
                if not devel_depend and FilesCheck.devel_regex.search(d[0]):
                    self.output.add_info('E', pkg, 'devel-dependency', d[0])
                    devel_depend = True
                if not d[1]:
                    res = lib_package_regex.search(d[0])
                    if res and not res.group(1):
                        self.output.add_info('E', pkg,
                                             'explicit-lib-dependency', d[0])

            if d[1] == rpm.RPMSENSE_EQUAL and d[2][2] is not None:
                self.output.add_info('W', pkg, 'requires-on-release', value)
            self._unexpanded_macros(pkg, 'dependency %s' % (value, ), value)

        self._unexpanded_macros(pkg, 'Name', name)
        if not name:
            self.output.add_info('E', pkg, 'no-name-tag')
        else:
            if is_devel and not is_source:
                base = is_devel.group(1)
                dep = None
                has_so = False
                has_pc = False
                for fname in pkg.files:
                    if fname.endswith('.so'):
                        has_so = True
                    if pkg_config_regex.match(fname) and fname.endswith('.pc'):
                        has_pc = True
                if has_so:
                    base_or_libs = base + '*' + '/' + base + '-libs/lib' + base + '*'
                    # try to match *%_isa as well (e.g. '(x86-64)', '(x86-32)')
                    base_or_libs_re = re.compile(
                        r'^(lib)?%s(-libs)?[\d_-]*(\(\w+-\d+\))?$' %
                        re.escape(base))
                    for d in deps:
                        if base_or_libs_re.match(d[0]):
                            dep = d
                            break
                    if not dep:
                        self.output.add_info('W', pkg, 'no-dependency-on',
                                             base_or_libs)
                    elif version:
                        exp = (epoch, version, None)
                        sexp = Pkg.versionToString(exp)
                        if not dep[1]:
                            self.output.add_info('W', pkg,
                                                 'no-version-dependency-on',
                                                 base_or_libs, sexp)
                        elif dep[2][:2] != exp[:2]:
                            self.output.add_info(
                                'W', pkg, 'incoherent-version-dependency-on',
                                base_or_libs,
                                Pkg.versionToString(
                                    (dep[2][0], dep[2][1], None)), sexp)
                    res = devel_number_regex.search(name)
                    if not res:
                        self.output.add_info('W', pkg, 'no-major-in-name',
                                             name)
                    else:
                        if res.group(3):
                            prov = res.group(1) + res.group(2) + '-devel'
                        else:
                            prov = res.group(1) + '-devel'

                        if prov not in (x[0] for x in pkg.provides):
                            self.output.add_info('W', pkg, 'no-provides', prov)

                if has_pc:
                    found_pkg_config_dep = False
                    for p in (x[0] for x in pkg.provides):
                        if p.startswith('pkgconfig('):
                            found_pkg_config_dep = True
                            break
                    if not found_pkg_config_dep:
                        self.output.add_info('E', pkg,
                                             'no-pkg-config-provides')

        # List of words to ignore in spell check
        ignored_words = set()
        for pf in pkg.files:
            ignored_words.update(pf.split('/'))
        ignored_words.update((x[0] for x in pkg.provides))
        ignored_words.update((x[0] for x in pkg.requires))
        ignored_words.update((x[0] for x in pkg.conflicts))
        ignored_words.update((x[0] for x in pkg.obsoletes))

        langs = pkg[rpm.RPMTAG_HEADERI18NTABLE]

        summary = byte_to_string(pkg[rpm.RPMTAG_SUMMARY])
        if summary:
            if not langs:
                self._unexpanded_macros(pkg, 'Summary', summary)
            else:
                for lang in langs:
                    self.check_summary(pkg, lang, ignored_words)
        else:
            self.output.add_info('E', pkg, 'no-summary-tag')

        description = byte_to_string(pkg[rpm.RPMTAG_DESCRIPTION])
        if description:
            if not langs:
                self._unexpanded_macros(pkg, '%description', description)
            else:
                for lang in langs:
                    self.check_description(pkg, lang, ignored_words)

            if len(description) < len(pkg[rpm.RPMTAG_SUMMARY]):
                self.output.add_info('W', pkg,
                                     'description-shorter-than-summary')
        else:
            self.output.add_info('E', pkg, 'no-description-tag')

        group = pkg[rpm.RPMTAG_GROUP]
        self._unexpanded_macros(pkg, 'Group', group)
        if not group:
            self.output.add_info('E', pkg, 'no-group-tag')
        elif pkg.name.endswith(
                '-devel') and not group.startswith('Development/'):
            self.output.add_info('W', pkg,
                                 'devel-package-with-non-devel-group', group)
        elif self.valid_groups and group not in self.valid_groups:
            self.output.add_info('W', pkg, 'non-standard-group', group)

        buildhost = pkg[rpm.RPMTAG_BUILDHOST]
        self._unexpanded_macros(pkg, 'BuildHost', buildhost)
        if not buildhost:
            self.output.add_info('E', pkg, 'no-buildhost-tag')
        elif self.config.configuration['ValidBuildHost'] and \
                not self.valid_buildhost_regex.search(buildhost):
            self.output.add_info('W', pkg, 'invalid-buildhost', buildhost)

        changelog = pkg[rpm.RPMTAG_CHANGELOGNAME]
        if not changelog:
            self.output.add_info('E', pkg, 'no-changelogname-tag')
        else:
            clt = pkg[rpm.RPMTAG_CHANGELOGTEXT]
            if self.use_version_in_changelog:
                ret = changelog_version_regex.search(
                    byte_to_string(changelog[0]))
                if not ret and clt:
                    # we also allow the version specified as the first
                    # thing on the first line of the text
                    ret = changelog_text_version_regex.search(
                        byte_to_string(clt[0]))
                if not ret:
                    self.output.add_info('W', pkg,
                                         'no-version-in-last-changelog')
                elif version and release:
                    srpm = pkg[rpm.RPMTAG_SOURCERPM] or ''
                    # only check when source name correspond to name
                    if srpm[0:-8] == '%s-%s-%s' % (name, version, release):
                        expected = [version + '-' + release]
                        if epoch is not None:  # regardless of use_epoch
                            expected[0] = str(epoch) + ':' + expected[0]
                        # Allow EVR in changelog without release extension,
                        # the extension is often a macro or otherwise dynamic.
                        if self.release_ext:
                            expected.append(
                                self.extension_regex.sub('', expected[0]))
                        if ret.group(1) not in expected:
                            if len(expected) == 1:
                                expected = expected[0]
                            self.output.add_info(
                                'W', pkg, 'incoherent-version-in-changelog',
                                ret.group(1), expected)

            if clt:
                changelog = changelog + clt
            for s in changelog:
                if not Pkg.is_utf8_bytestr(s):
                    self.output.add_info('E', pkg, 'tag-not-utf8',
                                         '%changelog')
                    break
                e = Pkg.has_forbidden_controlchars(s)
                if e:
                    self.output.add_info('E', pkg,
                                         'forbidden-controlchar-found',
                                         '%%changelog : %s' % e)
                    break

            clt = pkg[rpm.RPMTAG_CHANGELOGTIME][0]
            if clt:
                clt -= clt % (24 * 3600)  # roll back to 00:00:00, see #246
                if clt < oldest_changelog_timestamp:
                    self.output.add_info(
                        'W', pkg, 'changelog-time-overflow',
                        time.strftime('%Y-%m-%d', time.gmtime(clt)))
                elif clt > time.time():
                    self.output.add_info(
                        'E', pkg, 'changelog-time-in-future',
                        time.strftime('%Y-%m-%d', time.gmtime(clt)))

        def split_license(text):
            return (x.strip()
                    for x in (l for l in license_regex.split(text) if l))

        def split_license_exception(text):
            x, y = license_exception_regex.split(text)[1:3] or (text, '')
            return x.strip(), y.strip()

        rpm_license = pkg[rpm.RPMTAG_LICENSE]
        if not rpm_license:
            self.output.add_info('E', pkg, 'no-license')
        else:
            valid_license = True
            if rpm_license not in self.valid_licenses:
                license_string = rpm_license

                l1, lexception = split_license_exception(rpm_license)
                # SPDX allows "<license> WITH <license-exception>"
                if lexception:
                    license_string = l1
                    if lexception not in self.valid_license_exceptions:
                        self.output.add_info('W', pkg,
                                             'invalid-license-exception',
                                             lexception)
                        valid_license = False

                for l1 in split_license(license_string):
                    if l1 in self.valid_licenses:
                        continue
                    for l2 in split_license(l1):
                        if l2 not in self.valid_licenses:
                            self.output.add_info('W', pkg, 'invalid-license',
                                                 l2)
                            valid_license = False
            if not valid_license:
                self._unexpanded_macros(pkg, 'License', rpm_license)

        for tag in ('URL', 'DistURL', 'BugURL'):
            if hasattr(rpm, 'RPMTAG_%s' % tag.upper()):
                url = byte_to_string(pkg[getattr(rpm,
                                                 'RPMTAG_%s' % tag.upper())])
                self._unexpanded_macros(pkg, tag, url, is_url=True)
                if url:
                    (scheme, netloc) = urlparse(url)[0:2]
                    if not scheme or not netloc or '.' not in netloc or \
                            scheme not in ('http', 'https', 'ftp') or \
                            (self.config.configuration['InvalidURL'] and
                             self.invalid_url_regex.search(url)):
                        self.output.add_info('W', pkg, 'invalid-url', tag, url)
                elif tag == 'URL':
                    self.output.add_info('W', pkg, 'no-url-tag')

        obs_names = [x[0] for x in pkg.obsoletes]
        prov_names = [x[0] for x in pkg.provides]

        for o in (x for x in obs_names if x not in prov_names):
            self.output.add_info('W', pkg, 'obsolete-not-provided', o)
        for o in pkg.obsoletes:
            value = Pkg.formatRequire(*o)
            self._unexpanded_macros(pkg, 'Obsoletes %s' % (value, ), value)

        # TODO: should take versions, <, <=, =, >=, > into account here
        #       https://bugzilla.redhat.com/460872
        useless_provides = set()
        for p in prov_names:
            if (prov_names.count(p) != 1 and not p.startswith('debuginfo(')
                    and p not in useless_provides):
                useless_provides.add(p)
        for p in sorted(useless_provides):
            self.output.add_info('E', pkg, 'useless-provides', p)

        for tagname, items in (('Provides', pkg.provides), ('Conflicts',
                                                            pkg.conflicts),
                               ('Obsoletes', pkg.obsoletes), ('Supplements',
                                                              pkg.supplements),
                               ('Suggests', pkg.suggests),
                               ('Enhances', pkg.enhances), ('Recommends',
                                                            pkg.recommends)):
            for p in items:
                e = Pkg.has_forbidden_controlchars(p)
                if e:
                    self.output.add_info('E', pkg,
                                         'forbidden-controlchar-found',
                                         '%s: %s' % (tagname, e))
                value = Pkg.formatRequire(*p)
                self._unexpanded_macros(pkg, '%s %s' % (tagname, value), value)

        for p in (pkg.requires):
            e = Pkg.has_forbidden_controlchars(p)
            if e:
                self.output.add_info('E', pkg, 'forbidden-controlchar-found',
                                     'Requires: %s' % e)

        obss = pkg.obsoletes
        if obss:
            provs = pkg.provides
            for prov in provs:
                for obs in obss:
                    if Pkg.rangeCompare(obs, prov):
                        self.output.add_info(
                            'W', pkg, 'self-obsoletion',
                            '%s obsoletes %s' % (Pkg.formatRequire(*obs),
                                                 Pkg.formatRequire(*prov)))

        expfmt = rpm.expandMacro('%{_build_name_fmt}')
        if pkg.is_source:
            # _build_name_fmt often (always?) ends up not outputting src/nosrc
            # as arch for source packages, do it ourselves
            expfmt = re.sub(r'(?i)%\{?ARCH\b\}?', pkg.arch, expfmt)
        expected = pkg.header.sprintf(expfmt).split('/')[-1]
        basename = Path(pkg.filename).parent
        if basename != expected:
            self.output.add_info('W', pkg, 'non-coherent-filename', basename,
                                 expected)

        for tag in ('Distribution', 'DistTag', 'ExcludeArch', 'ExcludeOS',
                    'Vendor'):
            if hasattr(rpm, 'RPMTAG_%s' % tag.upper()):
                res = byte_to_string(pkg[getattr(rpm,
                                                 'RPMTAG_%s' % tag.upper())])
                self._unexpanded_macros(pkg, tag, res)
Exemple #20
0
    def check_aux(self, pkg, files, prog, script, tag, prereq):
        if script:
            script_str = byte_to_string(script)
            if prog:
                if prog not in self.valid_shells:
                    self.output.add_info('E', pkg, 'invalid-shell-in-' + tag, prog)
                if prog in self.empty_shells:
                    self.output.add_info('E', pkg, 'non-empty-' + tag, prog)
            if prog in syntaxcheck_shells or prog == '/usr/bin/perl':
                if percent_regex.search(script_str):
                    self.output.add_info('W', pkg, 'percent-in-' + tag)
                if bracket_regex.search(script_str):
                    self.output.add_info('W', pkg, 'spurious-bracket-in-' + tag)
                res = dangerous_command_regex.search(script_str)
                if res:
                    self.output.add_info('W', pkg, 'dangerous-command-in-' + tag,
                                         res.group(2))
                res = selinux_regex.search(script_str)
                if res:
                    self.output.add_info('E', pkg, 'forbidden-selinux-command-in-' + tag,
                                         res.group(2))

                if 'update-menus' in script_str:
                    menu_error = True
                    for f in files:
                        if menu_regex.search(f):
                            menu_error = False
                            break
                    if menu_error:
                        self.output.add_info('E', pkg,
                                             'update-menus-without-menu-file-in-' + tag)
                if tmp_regex.search(script_str):
                    self.output.add_info('E', pkg, 'use-tmp-in-' + tag)
                for c in prereq_assoc:
                    if c[0].search(script_str):
                        found = False
                        for p in c[1]:
                            if p in prereq or p in files:
                                found = True
                                break
                        if not found:
                            self.output.add_info('E', pkg, 'no-prereq-on', c[1][0])

            if prog in syntaxcheck_shells:
                if incorrect_shell_script(prog, script):
                    self.output.add_info('E', pkg, 'shell-syntax-error-in-' + tag)
                if home_regex.search(script_str):
                    self.output.add_info('E', pkg, 'use-of-home-in-' + tag)
                res = bogus_var_regex.search(script_str)
                if res:
                    self.output.add_info('W', pkg, 'bogus-variable-use-in-' + tag,
                                         res.group(1))

            if prog == '/usr/bin/perl':
                if incorrect_perl_script(prog, script):
                    self.output.add_info('E', pkg, 'perl-syntax-error-in-' + tag)
            elif prog.endswith('sh'):
                res = single_command_regex.search(script_str)
                if res:
                    self.output.add_info('W', pkg, 'one-line-command-in-' + tag,
                                         res.group(1))

        elif prog not in self.empty_shells and prog in self.valid_shells:
            self.output.add_info('W', pkg, 'empty-' + tag)
Exemple #21
0
    def check(self, pkg):
        if pkg.is_source:
            return

        files = pkg.files
        for check in self.checks:
            if 'IgnorePkgIf' in check:
                if getattr(self, check['IgnorePkgIf'])(pkg):
                    continue

            if 'Good' in check or 'Bad' in check:
                for f in files:
                    ok = False
                    if 'Good' in check:
                        for g in check['Good']:
                            if ((not isinstance(g, str) and g.match(f)) or
                                    g == f):
                                ok = True
                                break
                    if ok:
                        continue

                    if 'Bad' in check:
                        for b in check['Bad']:
                            if 'IgnoreFileIf' in check:
                                if getattr(self, check['IgnoreFileIf'])(pkg, f):
                                    continue
                            if ((not isinstance(b, str) and b.match(f)) or
                                    b == f):
                                self.output.add_info('E', pkg, check['Message'], f)

        invalidfhs = set()
        invalidopt = set()

        is_suse = (pkg.header[RPMTAG_VENDOR] and 'SUSE' in byte_to_string(pkg.header[RPMTAG_VENDOR]))

        # the checks here only warn about a directory once rather
        # than reporting potentially hundreds of files individually
        for f, pkgfile in files.items():
            file_type = (pkgfile.mode >> 12) & 0o17

            # append / to directories
            if file_type == 4:
                f += '/'

            if not f.startswith(self.goodprefixes):
                base = f.rpartition('/')
                pfx = None
                # find the first invalid path component
                # (/usr/foo/bar/baz -> /usr)
                while (base[0] and not base[0].startswith(self.goodprefixes) and
                       not base[0] in self._restricteddirs):
                    pfx = base[0]
                    base = base[0].rpartition('/')

                if not pfx:
                    invalidfhs.add(f)
                else:
                    invalidfhs.add(pfx)

            if f.startswith('/opt'):
                try:
                    provider = f.split('/')[2]
                except Exception:
                    continue
                if is_suse and (provider == 'suse' or provider == 'novell'):
                    continue

                d = '/opt/' + provider
                invalidopt.add(d)

        for f in invalidfhs:
            self.output.add_info('E', pkg, 'filelist-forbidden-fhs23', f)

        for f in invalidopt:
            self.output.add_info('E', pkg, 'filelist-forbidden-opt', f)
Exemple #22
0
    def check(self, pkg):

        for filename in pkg.header[rpm.RPMTAG_FILENAMES] or ():
            if not is_utf8_bytestr(filename):
                self.output.add_info('E', pkg, 'filename-not-utf8', byte_to_string(filename))

        # Rest of the checks are for binary packages only
        if pkg.is_source:
            return

        files = pkg.files

        # Check if the package is a development package
        devel_pkg = devel_regex.search(pkg.name)

        if not devel_pkg:
            for p in pkg.provides:
                if devel_regex.search(p[0]):
                    devel_pkg = True
                    break

        config_files = pkg.config_files
        ghost_files = pkg.ghost_files
        req_names = pkg.req_names
        lib_package = lib_package_regex.search(pkg.name)
        is_kernel_package = kernel_package_regex.search(pkg.name)
        debuginfo_package = debuginfo_package_regex.search(pkg.name)
        debugsource_package = debugsource_package_regex.search(pkg.name)

        # report these errors only once
        perl_dep_error = False
        python_dep_error = False
        lib_file = False
        non_lib_file = None
        log_files = []
        logrotate_file = False
        debuginfo_srcs = False
        debuginfo_debugs = False

        if not lib_package and not pkg.doc_files:
            self.output.add_info('W', pkg, 'no-documentation')

        if files:
            if self.meta_package_regex.search(pkg.name):
                self.output.add_info('W', pkg, 'file-in-meta-package')
        elif debuginfo_package or debugsource_package:
            self.output.add_info('E', pkg, 'empty-debuginfo-package')

        # Prefetch scriptlets, strip quotes from them (#169)
        postin = pkg[rpm.RPMTAG_POSTIN] or \
            pkg.scriptprog(rpm.RPMTAG_POSTINPROG)
        if postin:
            postin = quotes_regex.sub('', postin)
        postun = pkg[rpm.RPMTAG_POSTUN] or \
            pkg.scriptprog(rpm.RPMTAG_POSTUNPROG)
        if postun:
            postun = quotes_regex.sub('', postun)

        # Unique (rdev, inode) combinations
        hardlinks = {}

        # All executable files from standard bin dirs (basename => [paths])
        # Hack: basenames with empty paths links are symlinks (not subject
        # to duplicate binary check, but yes for man page existence check)
        bindir_exes = {}

        # All man page 'base' names (without section etc extensions)
        man_basenames = set()

        for f, pkgfile in files.items():
            mode = pkgfile.mode
            user = pkgfile.user
            group = pkgfile.group
            link = pkgfile.linkto
            size = pkgfile.size
            rdev = pkgfile.rdev
            inode = pkgfile.inode
            is_doc = f in pkg.doc_files
            nonexec_file = False

            self._check_manpage_compressed(pkg, f)
            self._check_infopage_compressed(pkg, f)

            for match in self.macro_regex.findall(f):
                self.output.add_info('W', pkg, 'unexpanded-macro', f, match)
            if user not in self.standard_users:
                self.output.add_info('W', pkg, 'non-standard-uid', f, user)
            if group not in self.standard_groups:
                self.output.add_info('W', pkg, 'non-standard-gid', f, group)

            if not self.module_rpms_ok and kernel_modules_regex.search(f) and not \
                    is_kernel_package:
                self.output.add_info('E', pkg, 'kernel-modules-not-in-kernel-packages', f)

            for i in self.disallowed_dirs:
                if f.startswith(i):
                    self.output.add_info('E', pkg, 'dir-or-file-in-%s' %
                                         '-'.join(i.split('/')[1:]), f)

            if f.startswith('/run/'):
                if f not in ghost_files:
                    self.output.add_info('W', pkg, 'non-ghost-in-run', f)
            elif f.startswith('/etc/systemd/system/'):
                self.output.add_info('W', pkg, 'systemd-unit-in-etc', f)
            elif f.startswith('/etc/udev/rules.d/'):
                self.output.add_info('W', pkg, 'udev-rule-in-etc', f)
            elif f.startswith('/etc/tmpfiles.d/'):
                self.output.add_info('W', pkg, 'tmpfiles-conf-in-etc', f)
            elif sub_bin_regex.search(f):
                self.output.add_info('E', pkg, 'subdir-in-bin', f)
            elif '/site_perl/' in f:
                self.output.add_info('W', pkg, 'siteperl-in-perl-module', f)

            if backup_regex.search(f):
                self.output.add_info('E', pkg, 'backup-file-in-package', f)
            elif scm_regex.search(f):
                self.output.add_info('E', pkg, 'version-control-internal-file', f)
            elif f.endswith('/.htaccess'):
                self.output.add_info('E', pkg, 'htaccess-file', f)
            elif hidden_file_regex.search(f) and not f.startswith('/etc/skel/') and not f.endswith('/.build-id'):
                self.output.add_info('W', pkg, 'hidden-file-or-dir', f)
            elif manifest_perl_regex.search(f):
                self.output.add_info('W', pkg, 'manifest-in-perl-module', f)
            elif f == '/usr/info/dir' or f == '/usr/share/info/dir':
                self.output.add_info('E', pkg, 'info-dir-file', f)
            elif makefile_regex.search(f) and not f.startswith('/usr/share/selinux'):
                self.output.add_info('E', pkg, 'makefile-junk', f)

            res = logrotate_regex.search(f)
            if res:
                logrotate_file = True
                if res.group(1) != pkg.name:
                    self.output.add_info('E', pkg, 'incoherent-logrotate-file', f)

            deps = [x[0] for x in pkg.requires + pkg.recommends + pkg.suggests]
            if res and not ('logrotate' in deps) and pkg.name != 'logrotate':
                self.output.add_info('E', pkg, 'missing-dependency-to-logrotate', 'for logrotate script', f)
            if f.startswith('/etc/cron.') \
               and not ('cron' in deps) and pkg.name != 'cron':
                self.output.add_info('E', pkg, 'missing-dependency-to-cron', 'for cron script', f)
            if f.startswith('/etc/xinet.d/') \
               and not ('xinetd' in deps) and pkg.name != 'xinetd':
                self.output.add_info('E', pkg, 'missing-dependency-to-xinetd', 'for xinet.d script', f)

            if link != '':
                ext = compr_regex.search(link)
                if ext:
                    if not re.compile(r'\.%s$' % ext.group(1)).search(f):
                        self.output.add_info('E', pkg, 'compressed-symlink-with-wrong-ext',
                                             f, link)

            perm = mode & 0o7777
            mode_is_exec = mode & 0o111

            if log_regex.search(f):
                log_files.append(f)

            # Hardlink check
            for hardlink in hardlinks.get((rdev, inode), ()):
                if Path(hardlink).parent != Path(f).parent:
                    self.output.add_info('W', pkg, 'cross-directory-hard-link', f, hardlink)
            hardlinks.setdefault((rdev, inode), []).append(f)

            # normal file check
            if stat.S_ISREG(mode):

                # set[ug]id bit check
                if stat.S_ISGID & mode or stat.S_ISUID & mode:
                    if stat.S_ISUID & mode:
                        self.output.add_info('E', pkg, 'setuid-binary', f, user, '%o' % perm)
                    if stat.S_ISGID & mode:
                        if not (group == 'games' and
                                (games_path_regex.search(f) or
                                 self.games_group_regex.search(
                                    pkg[rpm.RPMTAG_GROUP]))):
                            self.output.add_info('E', pkg, 'setgid-binary', f, group,
                                                 '%o' % perm)
                    if mode & 0o777 != 0o755:
                        self.output.add_info('E', pkg, 'non-standard-executable-perm', f,
                                             '%o' % perm)

                if not devel_pkg:
                    if lib_path_regex.search(f):
                        lib_file = True
                    elif not is_doc:
                        non_lib_file = f

                if log_regex.search(f):
                    nonexec_file = True
                    if user != 'root':
                        self.output.add_info('E', pkg, 'non-root-user-log-file', f, user)
                    if group != 'root':
                        self.output.add_info('E', pkg, 'non-root-group-log-file', f, group)
                    if f not in ghost_files:
                        self.output.add_info('E', pkg, 'non-ghost-file', f)

                chunk = None
                istext = False
                res = None
                try:
                    res = os.access(pkgfile.path, os.R_OK)
                except UnicodeError as e:  # e.g. non-ASCII, C locale, python 3
                    self.output.add_info('W', pkg, 'inaccessible-filename', f, e)
                else:
                    if res:
                        (chunk, istext) = self.peek(pkgfile.path, pkg)

                (interpreter, interpreter_args) = script_interpreter(chunk)

                if doc_regex.search(f):
                    if not interpreter:
                        nonexec_file = True
                    if not is_doc:
                        self.output.add_info('E', pkg, 'not-listed-as-documentation', f)

                if devel_pkg and f.endswith('.typelib'):
                    self.output.add_info('E', pkg, 'non-devel-file-in-devel-package', f)

                # check ldconfig call in %post and %postun
                if lib_regex.search(f):
                    if devel_pkg and not (sofile_regex.search(f) and stat.S_ISLNK(mode)):
                        self.output.add_info('E', pkg, 'non-devel-file-in-devel-package', f)
                    if not postin:
                        self.output.add_info('E', pkg, 'library-without-ldconfig-postin', f)
                    else:
                        if not ldconfig_regex.search(postin):
                            self.output.add_info('E', pkg, 'postin-without-ldconfig', f)

                    if not postun:
                        self.output.add_info('E', pkg, 'library-without-ldconfig-postun', f)
                    else:
                        if not ldconfig_regex.search(postun):
                            self.output.add_info('E', pkg, 'postun-without-ldconfig', f)

                # check depmod call in %post and %postun
                res = not is_kernel_package and kernel_modules_regex.search(f)
                if res:
                    kernel_version = res.group(1)
                    kernel_version_regex = re.compile(
                        r'\bdepmod\s+-a.*F\s+/boot/System\.map-' +
                        re.escape(kernel_version) + r'\b.*\b' +
                        re.escape(kernel_version) + r'\b',
                        re.MULTILINE | re.DOTALL)

                    if not postin or not depmod_regex.search(postin):
                        self.output.add_info('E', pkg, 'module-without-depmod-postin', f)
                    # check that we run depmod on the right kernel
                    elif not kernel_version_regex.search(postin):
                        self.output.add_info('E', pkg, 'postin-with-wrong-depmod', f)

                    if not postun or not depmod_regex.search(postun):
                        self.output.add_info('E', pkg, 'module-without-depmod-postun', f)
                    # check that we run depmod on the right kernel
                    elif not kernel_version_regex.search(postun):
                        self.output.add_info('E', pkg, 'postun-with-wrong-depmod', f)

                # check install-info call in %post and %postun
                if f.startswith('/usr/share/info/'):
                    if not postin:
                        self.output.add_info('E', pkg,
                                             'info-files-without-install-info-postin', f)
                    elif not install_info_regex.search(postin):
                        self.output.add_info('E', pkg, 'postin-without-install-info', f)

                    preun = pkg[rpm.RPMTAG_PREUN] or \
                        pkg.scriptprog(rpm.RPMTAG_PREUNPROG)
                    if not postun and not preun:
                        self.output.add_info('E', pkg,
                                             'info-files-without-install-info-postun', f)
                    elif not ((postun and install_info_regex.search(postun)) or
                              (preun and install_info_regex.search(preun))):
                        self.output.add_info('E', pkg, 'postin-without-install-info', f)

                # check perl temp file
                if perl_temp_file_regex.search(f):
                    self.output.add_info('W', pkg, 'perl-temp-file', f)

                is_buildconfig = istext and buildconfigfile_regex.search(f)

                # check rpaths in buildconfig files
                if is_buildconfig:
                    ln = pkg.grep(buildconfig_rpath_regex, f)
                    if ln:
                        self.output.add_info('E', pkg, 'rpath-in-buildconfig', f, 'lines', ln)

                res = bin_regex.search(f)
                if res:
                    if not mode_is_exec:
                        self.output.add_info('W', pkg, 'non-executable-in-bin', f,
                                             '%o' % perm)
                    else:
                        exe = res.group(1)
                        if '/' not in exe:
                            bindir_exes.setdefault(exe, []).append(f)

                if (not devel_pkg and not is_doc and
                    (is_buildconfig or includefile_regex.search(f) or
                     develfile_regex.search(f))):
                    self.output.add_info('W', pkg, 'devel-file-in-non-devel-package', f)
                if mode & 0o444 != 0o444 and perm & 0o7000 == 0:
                    ok_nonreadable = False
                    for regex in non_readable_regexs:
                        if regex.search(f):
                            ok_nonreadable = True
                            break
                    if not ok_nonreadable:
                        self.output.add_info('E', pkg, 'non-readable', f, '%o' % perm)
                if size == 0 and not normal_zero_length_regex.search(f) and \
                        f not in ghost_files:
                    self.output.add_info('E', pkg, 'zero-length', f)

                if mode & stat.S_IWOTH:
                    self.output.add_info('E', pkg, 'world-writable', f, '%o' % perm)

                if not perl_dep_error:
                    res = perl_regex.search(f)
                    if res:
                        if self.perl_version_trick:
                            vers = res.group(1) + '.' + res.group(2)
                        else:
                            vers = res.group(1) + res.group(2)
                        if not (pkg.check_versioned_dep('perl-base', vers) or
                                pkg.check_versioned_dep('perl', vers)):
                            self.output.add_info('E', pkg, 'no-dependency-on',
                                                 'perl-base', vers)
                            perl_dep_error = True

                if not python_dep_error:
                    res = python_regex.search(f)
                    if (res and not
                            any((pkg.check_versioned_dep(dep, res.group(1))
                                for dep in (
                                    'python', 'python-base', 'python(abi)')))):
                        self.output.add_info('E', pkg, 'no-dependency-on', 'python-base',
                                             res.group(1))
                        python_dep_error = True

                source_file = python_bytecode_to_script(f)
                if source_file:
                    if source_file in files:
                        if chunk:
                            # Verify that the magic ABI value embedded in the
                            # .pyc header is correct
                            found_magic = pyc_magic_from_chunk(chunk)
                            exp_magic, exp_version = get_expected_pyc_magic(f, self.python_default_version)
                            if exp_magic and found_magic not in exp_magic:
                                found_version = 'unknown'
                                for (pv, pm) in _python_magic_values.items():
                                    if found_magic in pm:
                                        found_version = pv
                                        break
                                # If expected version was from the file path,
                                # issue # an error, otherwise a warning.
                                msg = (pkg,
                                       'python-bytecode-wrong-magic-value',
                                       f, 'expected %s (%s), found %d (%s)' %
                                       (' or '.join(map(str, exp_magic)),
                                        exp_version or self.python_default_version,
                                        found_magic, found_version))
                                if exp_version is not None:
                                    self.output.add_info('E', *msg)
                                else:
                                    self.output.add_info('W', *msg)

                            # Verify that the timestamp embedded in the .pyc
                            # header matches the mtime of the .py file:
                            pyc_timestamp = pyc_mtime_from_chunk(chunk)
                            # If it's a symlink, check target file mtime.
                            srcfile = pkg.readlink(files[source_file])
                            if not srcfile:
                                self.output.add_info('W', pkg, 'python-bytecode-without-source', f)
                            elif (pyc_timestamp is not None and
                                  pyc_timestamp != srcfile.mtime):
                                cts = datetime.fromtimestamp(
                                    pyc_timestamp).isoformat()
                                sts = datetime.fromtimestamp(
                                    srcfile.mtime).isoformat()
                                self.output.add_info('E',
                                                     pkg, 'python-bytecode-inconsistent-mtime',
                                                     f, cts, srcfile.name, sts)
                    else:
                        self.output.add_info('W', pkg, 'python-bytecode-without-source', f)

                # normal executable check
                if mode & stat.S_IXUSR and perm != 0o755:
                    self.output.add_info('E', pkg, 'non-standard-executable-perm',
                                         f, '%o' % perm)
                if mode_is_exec:
                    if f in config_files:
                        self.output.add_info('E', pkg, 'executable-marked-as-config-file', f)
                    if not nonexec_file:
                        # doc_regex and log_regex checked earlier, no match,
                        # check rest of usual cases here.  Sourced scripts have
                        # their own check, so disregard them here.
                        nonexec_file = f.endswith('.pc') or \
                            compr_regex.search(f) or \
                            includefile_regex.search(f) or \
                            develfile_regex.search(f) or \
                            logrotate_regex.search(f)
                    if nonexec_file:
                        self.output.add_info('W', pkg, 'spurious-executable-perm', f)
                elif f.startswith('/etc/') and f not in config_files and \
                        f not in ghost_files:
                    self.output.add_info('W', pkg, 'non-conffile-in-etc', f)

                if pkg.arch == 'noarch' and f.startswith('/usr/lib64/python'):
                    self.output.add_info('E', pkg, 'noarch-python-in-64bit-path', f)

                if debuginfo_package:
                    if f.endswith('.debug'):
                        debuginfo_debugs = True
                    else:
                        debuginfo_srcs = True

                res = man_base_regex.search(f)
                if res:
                    man_basenames.add(res.group(1))
                    if chunk:
                        # TODO: sequence based invocation
                        command = subprocess.run(
                            '%s %s | gtbl | groff -mtty-char -Tutf8 '
                            '-P-c -mandoc -w%s >%s' %
                            (catcmd(f), quote(pkgfile.path),
                             quote(self.man_warn_category), os.devnull),
                            shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
                            env=dict(os.environ, LC_ALL='en_US.UTF-8'))

                        for line in command.stdout.decode().split('\n'):
                            res = man_warn_regex.search(line)
                            if not res or man_nowarn_regex.search(line):
                                continue
                            self.output.add_info('W', pkg, 'manual-page-warning', f,
                                                 line[res.end(1):])

                if f.endswith('.svgz') and f[0:-1] not in files \
                        and scalable_icon_regex.search(f):
                    self.output.add_info('W', pkg, 'gzipped-svg-icon', f)

                if f.endswith('.pem') and f not in ghost_files:
                    if pkg.grep(start_certificate_regex, f):
                        self.output.add_info('W', pkg, 'pem-certificate', f)
                    if pkg.grep(start_private_key_regex, f):
                        self.output.add_info('E', pkg, 'pem-private-key', f)

                if tcl_regex.search(f):
                    self.output.add_info('E', pkg, 'tcl-extension-file', f)

                # text file checks
                if istext:
                    # ignore perl module shebang -- TODO: disputed...
                    if f.endswith('.pm'):
                        interpreter = None
                    # sourced scripts should not be executable
                    if sourced_script_regex.search(f):
                        if interpreter:
                            self.output.add_info('E', pkg,
                                                 'sourced-script-with-shebang', f,
                                                 interpreter, interpreter_args)
                        if mode_is_exec:
                            self.output.add_info('E', pkg, 'executable-sourced-script',
                                                 f, '%o' % perm)
                    # ...but executed ones should
                    elif interpreter or mode_is_exec or script_regex.search(f):
                        if interpreter:
                            res = interpreter_regex.search(interpreter)
                            if (mode_is_exec or script_regex.search(f)):
                                if res and res.group(1) == 'env':
                                    self.output.add_info('E', pkg, 'env-script-interpreter',
                                                         f, interpreter,
                                                         interpreter_args)
                                elif not res:
                                    self.output.add_info('E', pkg, 'wrong-script-interpreter',
                                                         f, interpreter,
                                                         interpreter_args)
                        elif not nonexec_file and not \
                                (lib_path_regex.search(f) and
                                 f.endswith('.la')):
                            self.output.add_info('E', pkg, 'script-without-shebang', f)

                        if not mode_is_exec and not is_doc and \
                                interpreter and interpreter.startswith('/'):
                            self.output.add_info('E', pkg, 'non-executable-script', f,
                                                 '%o' % perm, interpreter,
                                                 interpreter_args)
                        if b'\r' in chunk:
                            self.output.add_info('E', pkg, 'wrong-script-end-of-line-encoding', f)
                    elif is_doc and not self.skipdocs_regex.search(f):
                        if b'\r' in chunk:
                            self.output.add_info('W', pkg, 'wrong-file-end-of-line-encoding', f)
                        # We check only doc text files for UTF-8-ness;
                        # checking everything may be slow and can generate
                        # lots of unwanted noise.
                        if not is_utf8(pkgfile.path):
                            self.output.add_info('W', pkg, 'file-not-utf8', f)
                    if fsf_license_regex.search(chunk) and \
                            fsf_wrong_address_regex.search(chunk):
                        self.output.add_info('E', pkg, 'incorrect-fsf-address', f)

                elif is_doc and chunk and compr_regex.search(f):
                    ff = compr_regex.sub('', f)
                    if not self.skipdocs_regex.search(ff):
                        # compressed docs, eg. info and man files etc
                        if not is_utf8(pkgfile.path):
                            self.output.add_info('W', pkg, 'file-not-utf8', f)

            # normal dir check
            elif stat.S_ISDIR(mode):
                if mode & 0o1002 == 2:  # world writable w/o sticky bit
                    self.output.add_info('E', pkg, 'world-writable', f, '%o' % perm)
                if perm != 0o755:
                    self.output.add_info('E', pkg, 'non-standard-dir-perm', f, '%o' % perm)
                if pkg.name not in filesys_packages and f in STANDARD_DIRS:
                    self.output.add_info('E', pkg, 'standard-dir-owned-by-package', f)
                if hidden_file_regex.search(f) and not f.endswith('/.build-id'):
                    self.output.add_info('W', pkg, 'hidden-file-or-dir', f)

            # symbolic link check
            elif stat.S_ISLNK(mode):

                is_so = sofile_regex.search(f)
                if not devel_pkg and is_so and not link.endswith('.so'):
                    self.output.add_info('W', pkg, 'devel-file-in-non-devel-package', f)

                res = man_base_regex.search(f)
                if res:
                    man_basenames.add(res.group(1))
                else:
                    res = bin_regex.search(f)
                    if res:
                        exe = res.group(1)
                        if '/' not in exe:
                            bindir_exes.setdefault(exe, [])

                # absolute link
                r = absolute_regex.search(link)
                if r:
                    if not is_so and link not in files and \
                            link not in req_names:
                        is_exception = False
                        for e in self.dangling_exceptions.values():
                            if e['path'].search(link):
                                is_exception = e['name']
                                break
                        if is_exception:
                            if is_exception not in req_names:
                                self.output.add_info('W', pkg, 'no-dependency-on',
                                                     is_exception)
                        else:
                            self.output.add_info('W', pkg, 'dangling-symlink', f, link)
                    linktop = r.group(1)
                    r = absolute_regex.search(f)
                    if r:
                        filetop = r.group(1)
                        if filetop == linktop or self.use_relative_symlinks:
                            self.output.add_info('W', pkg, 'symlink-should-be-relative',
                                                 f, link)
                # relative link
                else:
                    if not is_so:
                        abslink = '%s/%s' % (Path(f).parent, link)
                        abslink = os.path.normpath(abslink)
                        if abslink not in files and abslink not in req_names:
                            is_exception = False
                            for e in self.dangling_exceptions.values():
                                if e['path'].search(link):
                                    is_exception = e['name']
                                    break
                            if is_exception:
                                if is_exception not in req_names:
                                    self.output.add_info('W', pkg, 'no-dependency-on',
                                                         is_exception)
                            else:
                                self.output.add_info('W', pkg, 'dangling-relative-symlink',
                                                     f, link)
                    pathcomponents = f.split('/')[1:]
                    r = points_regex.search(link)
                    lastpop = None
                    mylink = None

                    while r:
                        mylink = r.group(1)
                        if len(pathcomponents) == 0:
                            self.output.add_info('E', pkg, 'symlink-has-too-many-up-segments',
                                                 f, link)
                            break
                        else:
                            lastpop = pathcomponents[0]
                            pathcomponents = pathcomponents[1:]
                            r = points_regex.search(mylink)

                    if mylink and lastpop:
                        r = absolute2_regex.search(mylink)
                        linktop = r.group(1)

                        # does the link go up and then down into the same
                        # directory?
                        # if linktop == lastpop:
                        #    self.output.add_info('W', pkg, 'lengthy-symlink', f, link)

                        # have we reached the root directory?
                        if len(pathcomponents) == 0 and linktop != lastpop \
                                and not self.use_relative_symlinks:
                            # relative link into other toplevel directory
                            self.output.add_info('W', pkg, 'symlink-should-be-absolute', f,
                                                 link)
                        # check additional segments for mistakes like
                        # `foo/../bar/'
                        for linksegment in mylink.split('/'):
                            if linksegment == '..':
                                self.output.add_info('E',
                                                     pkg,
                                                     'symlink-contains-up-and-down-segments',
                                                     f, link)

            if f.startswith('/etc/cron.d/'):
                if stat.S_ISLNK(mode):
                    self.output.add_info('E', pkg, 'symlink-crontab-file', f)

                if mode_is_exec:
                    self.output.add_info('E', pkg, 'executable-crontab-file', f)

                if stat.S_IWGRP & mode or stat.S_IWOTH & mode:
                    self.output.add_info('E', pkg, 'non-owner-writeable-only-crontab-file', f)

        if len(log_files) and not logrotate_file:
            self.output.add_info('W', pkg, 'log-files-without-logrotate', sorted(log_files))

        if lib_package and lib_file and non_lib_file:
            self.output.add_info('E', pkg, 'outside-libdir-files', non_lib_file)

        if not self.use_debugsource and debuginfo_package and debuginfo_debugs and not debuginfo_srcs:
            self.output.add_info('E', pkg, 'debuginfo-without-sources')

        for exe, paths in bindir_exes.items():
            if len(paths) > 1:
                self.output.add_info('W', pkg, 'duplicate-executable', exe, paths)
            if exe not in man_basenames:
                self.output.add_info('W', pkg, 'no-manual-page-for-binary', exe)
Exemple #23
0
def script_interpreter(chunk):
    res = shebang_regex.search(chunk) if chunk else None
    return (byte_to_string(res.group(1)), byte_to_string(res.group(2)).strip()) \
        if res and res.start() == 0 else (None, '')
Exemple #24
0
def readlines(path):
    with open(path, 'rb') as fobj:
        for line in fobj:
            yield byte_to_string(line)
Exemple #25
0
    def __init__(self, config, output, pkg, path, fname, is_ar, is_shlib):
        self.readelf_error = False
        self.needed = []
        self.rpath = []
        self.undef = []
        self.unused = []
        self.config = config
        self.output = output
        self.comment = False
        self.soname = False
        self.non_pic = True
        self.stack = False
        self.exec_stack = False
        self.exit_calls = []
        self.forbidden_calls = []
        fork_called = False
        self.tail = ''

        self.setgid = False
        self.setuid = False
        self.setgroups = False
        self.chroot = False
        self.chdir = False
        self.chroot_near_chdir = False
        self.mktemp = False
        self.forbidden_functions = self.config.configuration['WarnOnFunction']
        if self.forbidden_functions:
            for name, func in self.forbidden_functions.items():
                # precompile regexps
                f_name = func['f_name']
                func['f_regex'] = create_nonlibc_regexp_call(f_name)
                if func['good_param']:
                    func['waiver_regex'] = re.compile(func['good_param'])
                # register descriptions
                self.output.error_details.update({name: func['description']})

        is_debug = path.endswith('.debug')
        # Currently this implementation works only on specific
        # architectures due to reliance on arch specific assembly.
        if (pkg.arch.startswith('armv') or pkg.arch == 'aarch64'):
            # 10450:   ebffffec        bl      10408 <chroot@plt>
            self.objdump_call_regex = re.compile(br'\sbl\s+(.*)')
        elif (pkg.arch.endswith('86') or pkg.arch == 'x86_64'):
            # 401eb8:   e8 c3 f0 ff ff          callq  400f80 <chdir@plt>
            self.objdump_call_regex = re.compile(br'callq?\s(.*)')
        else:
            self.objdump_call_regex = None

        res = Pkg.getstatusoutput(
            ('readelf', '-W', '-S', '-l', '-d', '-s', path))
        if not res[0]:
            lines = res[1].splitlines()
            for line in lines:
                r = self.needed_regex.search(line)
                if r:
                    self.needed.append(r.group(1))
                    continue

                r = self.rpath_regex.search(line)
                if r:
                    for p in r.group(1).split(':'):
                        self.rpath.append(p)
                    continue

                if self.comment_regex.search(line):
                    self.comment = True
                    continue

                if self.pic_regex.search(line):
                    self.non_pic = False
                    continue

                r = self.soname_regex.search(line)
                if r:
                    self.soname = r.group(1)
                    continue

                r = self.stack_regex.search(line)
                if r:
                    self.stack = True
                    flags = r.group(1)
                    if flags and self.stack_exec_regex.search(flags):
                        self.exec_stack = True
                    continue

                if line.startswith('Symbol table'):
                    break

            for line in lines:
                r = self.call_regex.search(line)
                if not r:
                    continue
                line = r.group(1)

                if self.mktemp_call_regex.search(line):
                    self.mktemp = True

                if self.setgid_call_regex.search(line):
                    self.setgid = True

                if self.setuid_call_regex.search(line):
                    self.setuid = True

                if self.setgroups_call_regex.search(line):
                    self.setgroups = True

                if self.chdir_call_regex.search(line):
                    self.chdir = True

                if self.chroot_call_regex.search(line):
                    self.chroot = True

                if self.forbidden_functions:
                    for r_name, func in self.forbidden_functions.items():
                        ret = func['f_regex'].search(line)
                        if ret:
                            self.forbidden_calls.append(r_name)

                if is_shlib:
                    r = self.exit_call_regex.search(line)
                    if r:
                        self.exit_calls.append(r.group(1))
                        continue
                    r = self.fork_call_regex.search(line)
                    if r:
                        fork_called = True
                        continue

            # check if we don't have a string that will automatically
            # waive the presence of a forbidden call
            if self.forbidden_calls:
                res = Pkg.getstatusoutput(('strings', path))
                if not res[0]:
                    for line in res[1].splitlines():
                        # as we need to remove elements, iterate backwards
                        for i in range(len(self.forbidden_calls) - 1, -1, -1):
                            func = self.forbidden_calls[i]
                            f = self.forbidden_functions[func]
                            if 'waiver_regex' not in f:
                                continue
                            r = f['waiver_regex'].search(line)
                            if r:
                                del self.forbidden_calls[i]

            if self.non_pic:
                self.non_pic = 'TEXTREL' in res[1]

            # Ignore all exit() calls if fork() is being called.
            # Does not have any context at all but without this kludge, the
            # number of false positives would probably be intolerable.
            if fork_called:
                self.exit_calls = []

            # check if chroot is near chdir (since otherwise, chroot is called
            # without chdir)
            if not self.objdump_call_regex and self.chroot and self.chdir:
                # On some architectures, e.g. PPC, it is to difficult to
                # find the actual invocations of chroot/chdir, if both
                # exist assume chroot is fine
                self.chroot_near_chdir = True

            elif self.chroot and self.chdir:
                p = subprocess.Popen(('objdump', '-d', path),
                                     stdout=subprocess.PIPE,
                                     bufsize=-1,
                                     env=dict(os.environ, LC_ALL='C'))
                with p.stdout:
                    index = 0
                    chroot_index = -99
                    chdir_index = -99
                    for line in p.stdout:
                        res = self.objdump_call_regex.search(line)
                        if not res:
                            continue
                        if b'@plt' not in res.group(1):
                            pass
                        elif b'chroot@plt' in res.group(1):
                            chroot_index = index
                            if abs(chroot_index - chdir_index) <= 2:
                                self.chroot_near_chdir = True
                                break
                        elif b'chdir@plt' in res.group(1):
                            chdir_index = index
                            if abs(chroot_index - chdir_index) <= 2:
                                self.chroot_near_chdir = True
                                break
                        index += 1
                if p.wait() and not self.chroot_near_chdir:
                    self.output.add_info('W', pkg, 'binaryinfo-objdump-failed',
                                         fname)
                    self.chroot_near_chdir = True  # avoid false positive
                elif chroot_index == -99 and chdir_index == -99:
                    self.chroot_near_chdir = True  # avoid false positive

        else:
            self.readelf_error = True
            # Go and others are producing ar archives that don't have ELF
            # headers, so don't complain about it
            if not is_ar:
                self.output.add_info('W', pkg, 'binaryinfo-readelf-failed',
                                     fname, re.sub('\n.*', '', res[1]))

        try:
            with open(path, 'rb') as fobj:
                fobj.seek(-12, os.SEEK_END)
                self.tail = byte_to_string(fobj.read())
        except Exception as e:
            self.output.add_info('W', pkg,
                                 'binaryinfo-tail-failed %s: %s' % (fname, e))

        # Undefined symbol and unused direct dependency checks make sense only
        # for installed packages.
        # skip debuginfo: https://bugzilla.redhat.com/190599
        if not is_ar and not is_debug and isinstance(pkg, Pkg.InstalledPkg):
            # We could do this with objdump, but it's _much_ simpler with ldd.
            res = Pkg.getstatusoutput(('ldd', '-d', '-r', path))
            if not res[0]:
                for line in res[1].splitlines():
                    undef = self.undef_regex.search(line)
                    if undef:
                        self.undef.append(undef.group(1))
                if self.undef:
                    try:
                        res = Pkg.getstatusoutput(['c++filt'] + self.undef)
                        if not res[0]:
                            self.undef = res[1].splitlines()
                    except OSError:
                        pass
            else:
                self.output.add_info('W', pkg, 'ldd-failed', fname)
            res = Pkg.getstatusoutput(('ldd', '-r', '-u', path))
            if res[0]:
                # Either ldd doesn't grok -u (added in glibc 2.3.4) or we have
                # unused direct dependencies
                in_unused = False
                for line in res[1].splitlines():
                    if not line.rstrip():
                        pass
                    elif line.startswith('Unused direct dependencies'):
                        in_unused = True
                    elif in_unused:
                        unused = self.unused_regex.search(line)
                        if unused:
                            self.unused.append(unused.group(1))
                        else:
                            in_unused = False