def check(self, pkg): if pkg.isSource(): return for req in pkg.requires() + pkg.prereq(): if req[0] == 'xinetd': printError(pkg, xinetd_tag)
def check(self, pkg): if pkg.isSource(): return files = pkg.files() dirs = {} for f, pkgfile in files.items(): if f in pkg.ghostFiles(): continue if f.startswith("/etc/logrotate.d/"): try: for n, o in self.parselogrotateconf(pkg.dirName(), f).items(): if n in dirs and dirs[n] != o: printError(pkg, "logrotate-duplicate", n) else: dirs[n] = o except Exception as x: printError(pkg, 'rpmlint-exception', "%(file)s raised an exception: %(x)s" % {'file': f, 'x': x}) for d in sorted(dirs.keys()): if d not in files: if d != '/var/log': printError(pkg, 'suse-logrotate-log-dir-not-packaged', d) continue mode = files[d].mode & 0o777 if files[d].user != 'root' and (dirs[d] is None or dirs[d][0] != files[d].user): printError( pkg, 'suse-logrotate-user-writable-log-dir', "%s %s:%s %04o" % (d, files[d].user, files[d].group, mode)) elif files[d].group != 'root' and mode & 0o20 and (dirs[d] is None or dirs[d][1] != files[d].group): printError( pkg, 'suse-logrotate-user-writable-log-dir', "%s %s:%s %04o" % (d, files[d].user, files[d].group, mode))
def check_spec(self, pkg, spec_file, spec_lines=None): '''SCL spec file checks''' spec = '\n'.join(Pkg.readlines(spec_file)) if global_scl_definition.search(spec): self.check_metapackage(pkg, spec) elif scl_package_definition.search(spec): self.check_scl_spec(pkg, spec) elif scl_use.search(spec): printError(pkg, 'undeclared-scl')
def check_spec(self, pkg, spec_file): """SCL spec file checks""" spec = "\n".join(Pkg.readlines(spec_file)) if global_scl_definition.search(spec): self.check_metapackage(pkg, spec) elif scl_package_definition.search(spec): self.check_scl_spec(pkg, spec) elif scl_use.search(spec): printError(pkg, "undeclared-scl")
def check_spec(self, pkg, spec_file, spec_lines=[]): '''SCL spec file checks''' spec = '\n'.join(Pkg.readlines(spec_file)) if global_scl_definition.search(spec): self.check_metapackage(pkg, spec) elif scl_package_definition.search(spec): self.check_scl_spec(pkg, spec) elif scl_use.search(spec): printError(pkg, 'undeclared-scl')
def check_file(self, pkg, filename): root = pkg.dirName() f = root + filename try: st = getstatusoutput(appdata_checker + (f, )) except OSError: # ignore if the checker is not installed return if st[0]: printError(pkg, 'invalid-appdata-file', filename)
def check_file(self, pkg, filename): root = pkg.dirName() f = root + filename try: st = getstatusoutput(appdata_checker + (f,)) except OSError: # ignore if the checker is not installed return if st[0]: printError(pkg, "invalid-appdata-file", filename)
def check_binary(self, pkg): config_files = pkg.configFiles() noreplace_files = pkg.noreplaceFiles() for c in config_files: if c.startswith("/var/lib/games/"): printError(pkg, "score-file-must-not-be-conffile", c) elif not c.startswith("/etc/") and not c.startswith("/var/"): printWarning(pkg, "non-etc-or-var-file-marked-as-conffile", c) if c not in noreplace_files: printWarning(pkg, "conffile-without-noreplace-flag", c)
def check_summary(self, pkg, lang, ignored_words): summary = pkg.langtag(rpm.RPMTAG_SUMMARY, lang) if use_utf8: if not Pkg.is_utf8_bytestr(summary): printError(pkg, 'tag-not-utf8', 'Summary', lang) summary = Pkg.to_unicode(summary) else: summary = Pkg.b2s(summary) self._unexpanded_macros(pkg, 'Summary(%s)' % lang, summary) spell_check(pkg, summary, 'Summary(%s)', lang, ignored_words) if '\n' in summary: printError(pkg, 'summary-on-multiple-lines', lang) if summary[0] != summary[0].upper(): printWarning(pkg, 'summary-not-capitalized', lang, summary) if summary[-1] == '.': printWarning(pkg, 'summary-ended-with-dot', lang, summary) if len(summary) > max_line_len: printError(pkg, 'summary-too-long', lang, summary) if leading_space_regex.search(summary): printError(pkg, 'summary-has-leading-spaces', lang, summary) res = forbidden_words_regex.search(summary) if res and Config.getOption('ForbiddenWords'): printWarning(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: printWarning(pkg, 'name-repeated-in-summary', lang, res.group(1))
def check_summary(self, pkg, lang, ignored_words): summary = pkg.langtag(rpm.RPMTAG_SUMMARY, lang) if use_utf8: if not Pkg.is_utf8_bytestr(summary): printError(pkg, 'tag-not-utf8', 'Summary', lang) summary = Pkg.to_unicode(summary) else: summary = Pkg.b2s(summary) self._unexpanded_macros(pkg, 'Summary(%s)' % lang, summary) spell_check(pkg, summary, 'Summary(%s)', lang, ignored_words) if '\n' in summary: printError(pkg, 'summary-on-multiple-lines', lang) if summary[0] != summary[0].upper(): printWarning(pkg, 'summary-not-capitalized', lang, summary) if summary[-1] == '.': printWarning(pkg, 'summary-ended-with-dot', lang, summary) if len(summary) > max_line_len: printError(pkg, 'summary-too-long', lang, summary) if leading_space_regex.search(summary): printError(pkg, 'summary-has-leading-spaces', lang, summary) res = forbidden_words_regex.search(summary) if res and Config.getOption('ForbiddenWords'): printWarning(pkg, 'summary-use-invalid-word', lang, res.group(1)) if pkg.name: sepchars = '[\s' + punct + ']' res = re.search('(?:^|\s)(%s)(?:%s|$)' % (re.escape(pkg.name), sepchars), summary, re.IGNORECASE | re.UNICODE) if res: printWarning(pkg, 'name-repeated-in-summary', lang, res.group(1))
def check(self, pkg): res = pkg.checkSignature() if not res or res[0] != 0: if res and res[1]: kres = SignatureCheck.unknown_key_regex.search(res[1]) else: kres = None if kres: printError(pkg, "unknown-key", kres.group(1)) else: Pkg.warn("Error checking signature of %s: %s" % (pkg.filename, res[1])) else: if not SignatureCheck.pgp_regex.search(res[1]): printError(pkg, "no-signature")
def check_source(self, pkg): # process file list spec_file = None for fname, pkgfile in pkg.files().items(): if fname.endswith('.spec'): if spec_file: printError(pkg, 'multiple-specfiles', spec_file, fname) else: spec_file = fname elif source_regex.search(fname) and compress_ext and \ not fname.endswith(compress_ext): printWarning(pkg, 'source-or-patch-not-compressed', compress_ext, fname) perm = pkgfile.mode & 0o7777 if perm not in valid_src_perms: printWarning(pkg, 'strange-permission', fname, "%o" % perm)
def check(self, pkg): for fname, pkgfile in pkg.files().items(): path = pkgfile.path if zip_regex.search(fname) and os.path.exists(path) and \ stat.S_ISREG(os.lstat(path)[stat.ST_MODE]) and \ zipfile.is_zipfile(path): z = None # TODO ZipFile is context manager in 2.7+ try: z = zipfile.ZipFile(path, 'r') badcrc = z.testzip() if badcrc: printError(pkg, 'bad-crc-in-zip', badcrc, fname) except zipfile.error: printWarning(pkg, 'unable-to-read-zip', '%s: %s' % (fname, sys.exc_info()[1])) else: compressed = False for zinfo in z.infolist(): if zinfo.compress_type != zipfile.ZIP_STORED: compressed = True break if not compressed: printWarning(pkg, 'uncompressed-zip', fname) # additional jar checks if jar_regex.search(fname): try: mf = Pkg.b2s(z.read('META-INF/MANIFEST.MF')) if classpath_regex.search(mf): printWarning(pkg, 'class-path-in-manifest', fname) except KeyError: # META-INF/* are optional: # http://java.sun.com/j2se/1.4/docs/guide/jar/jar.html pass try: zinfo = z.getinfo('META-INF/INDEX.LIST') if not want_indexed_jars: printWarning(pkg, 'jar-indexed', fname) except KeyError: if want_indexed_jars: printWarning(pkg, 'jar-not-indexed', fname) pass z and z.close()
def check_binary(self, pkg): '''SCL binary package checks''' # Assume that no dash in package name means no SCL splits = pkg.name.split('-') if len(splits) < 2: return scl_name = splits[0] # While we are here, check if it's a runtime/build package is_runtime = splits[-1] == 'runtime' is_build = splits[-1] == 'build' del splits # Now test if there is /opt/foo/ dir good = False for fname in pkg.files().keys(): if startdir.search(fname): good = True break if not good: return # Test if our dir is named the same way as scl good = True for fname in pkg.files().keys(): if not startdir.search(fname): if allowed_etc.search(fname) or allowed_var.search(fname) or \ fname.startswith('/usr/bin/'): continue if fname.startswith('/etc/rpm/'): if not is_build: printWarning(pkg, 'scl-rpm-macros-outside-of-build', fname) continue if is_runtime and \ fname == os.path.join('/etc/scl/prefixes', scl_name): continue printError(pkg, 'file-outside-of-scl-tree', fname) else: if fname.split('/')[3] != scl_name: good = False if not good: printError(pkg, 'scl-name-screwed-up')
def check(self, pkg): if pkg.isSource(): return files = pkg.files() for path, info in files.items(): parent = path.rpartition("/")[0] if parent not in files: # can't figure out who owns the parent directory if it's part of another RPM :( continue parent_owner = files[parent].user # root user is trusted if info.user != parent_owner and parent_owner not in ('root', '0'): printError(pkg, 'file-parent-ownership-mismatch', path, "owned by", info.user, "is stored in directory owned by different user", parent_owner)
def check(self, pkg): if pkg.isSource(): return files = pkg.files() dirs = {} for f in files: if f in pkg.ghostFiles(): continue if f.startswith("/etc/logrotate.d/"): try: for n, o in self.parselogrotateconf(pkg.dirName(), f).items(): if n in dirs and dirs[n] != o: printError(pkg, "logrotate-duplicate", n) else: dirs[n] = o except Exception as x: printError( pkg, 'rpmlint-exception', "%(file)s raised an exception: %(x)s" % { 'file': f, 'x': x }) for d in sorted(dirs.keys()): if d not in files: if d != '/var/log': printError(pkg, 'suse-logrotate-log-dir-not-packaged', d) continue mode = files[d].mode & 0o777 if files[d].user != 'root' and (dirs[d] is None or dirs[d][0] != files[d].user): printError( pkg, 'suse-logrotate-user-writable-log-dir', "%s %s:%s %04o" % (d, files[d].user, files[d].group, mode)) elif files[d].group != 'root' and mode & 0o20 and ( dirs[d] is None or dirs[d][1] != files[d].group): printError( pkg, 'suse-logrotate-user-writable-log-dir', "%s %s:%s %04o" % (d, files[d].user, files[d].group, mode))
def check_description(self, pkg, lang, ignored_words): description = pkg.langtag(rpm.RPMTAG_DESCRIPTION, lang) self._unexpanded_macros(pkg, '%%description -l %s' % lang, description) utf8desc = description if use_utf8: utf8desc = Pkg.to_utf8(description).decode('utf-8') spell_check(pkg, utf8desc, '%%description -l %s', lang, ignored_words) for l in utf8desc.splitlines(): if len(l) > max_line_len: printError(pkg, 'description-line-too-long', lang, l) res = forbidden_words_regex.search(l) if res and Config.getOption('ForbiddenWords'): printWarning(pkg, 'description-use-invalid-word', lang, res.group(1)) res = tag_regex.search(l) if res: printWarning(pkg, 'tag-in-description', lang, res.group(1)) if use_utf8 and not Pkg.is_utf8_str(description): printError(pkg, 'tag-not-utf8', '%description', lang)
def check_file(self, pkg, filename): root = pkg.dirName() f = root + filename checker = appdata_checker if checker[0] == "appstream-util" and not self.network_enabled: checker += ("--nonet", ) validation_failed = False try: st = getstatusoutput(checker + (f, )) # Return code nonzero? validation_failed = (st[0] != 0) except OSError: # checker is not installed, do a validation manually try: ET.parse(pkg.dirName() + filename) except ET.ParseError: validation_failed = True if validation_failed: printError(pkg, 'invalid-appdata-file', filename)
def check_source(self, pkg): wrong_spec = False # lookup spec file for fname, pkgfile in pkg.files().items(): if fname.endswith('.spec'): self._spec_file = pkgfile.path if fname == pkg.name + ".spec": wrong_spec = False break else: wrong_spec = True if not self._spec_file: printError(pkg, "no-spec-file") else: if wrong_spec: printError(pkg, "invalid-spec-name") # check content of spec file self.check_spec(pkg, self._spec_file)
def check_file(self, pkg, filename): root = pkg.dirName() f = root + filename checker = appdata_checker if checker[0] == "appstream-util" and not self.network_enabled: checker += ("--nonet",) validation_failed = False try: st = getstatusoutput(checker + (f,)) # Return code nonzero? validation_failed = (st[0] != 0) except OSError: # checker is not installed, do a validation manually try: ET.parse(pkg.dirName() + filename) except ET.ParseError: validation_failed = True if validation_failed: printError(pkg, 'invalid-appdata-file', filename)
def check_file(self, pkg, filename): root = pkg.dirName() f = root + filename st = getstatusoutput(('desktop-file-validate', f), True) if st[0]: error_printed = False for line in st[1].splitlines(): if 'error: ' in line: printError(pkg, 'invalid-desktopfile', filename, line.split('error: ')[1]) error_printed = True if not error_printed: printError(pkg, 'invalid-desktopfile', filename) if not is_utf8(f): printError(pkg, 'non-utf8-desktopfile', filename) cfp = RawConfigParser() cfp.read(f) binary = None if cfp.has_option('Desktop Entry', 'Exec'): binary = cfp.get('Desktop Entry', 'Exec').split(' ', 1)[0] if binary: found = False if binary.startswith('/'): found = os.path.exists(root + binary) else: for i in STANDARD_BIN_DIRS: if os.path.exists(root + i + binary): # no need to check if the binary is +x, rpmlint does it # in another place found = True break if not found: printWarning(pkg, 'desktopfile-without-binary', filename, binary)
def check(self, pkg): if pkg.isSource(): return for fname, pkgfile in pkg.files().items(): if '/animations/' in fname: continue res = self.file_size_regex.search(fname) if res: sizes = (res.group(1), res.group(2)) res = self.info_size_regex.search(pkgfile.magic) if res: actualsizes = (res.group(1), res.group(2)) if abs(int(sizes[0])-int(actualsizes[0])) > 2 or \ abs(int(sizes[1])-int(actualsizes[1])) > 2: printError(pkg, "wrong-icon-size", fname, "expected:", "x".join(sizes), "actual:", "x".join(actualsizes))
def check(self, pkg): if pkg.isSource(): return for fname, pkgfile in pkg.files().items(): if '/animations/' in fname: continue res = self.file_size_regex.search(fname) if res: sizes = (res.group(1), res.group(2)) res = self.info_size_regex.search(pkgfile.magic) if res: actualsizes = (res.group(1), res.group(2)) if abs(int(sizes[0]) - int(actualsizes[0])) > 2 or \ abs(int(sizes[1]) - int(actualsizes[1])) > 2: printError(pkg, "wrong-icon-size", fname, "expected:", "x".join(sizes), "actual:", "x".join(actualsizes))
def check(self, pkg): """Checks the given RPM pkg instance against the configured whitelist restriction. Each whitelist violation will be printed with the according error tag. Nothing is returned from this function. """ from Filter import printError if pkg.isSource(): return files = pkg.files() for f, meta in files.items(): if not self._hasRestrictedMeta(meta): continue wl_match = self._getWhitelist(pkg.name, f) if not wl_match: # no whitelist entry exists for this file printError(pkg, self.m_error_map['unauthorized'], f) continue for audit in wl_match.audits(): res, msg = audit.compareMeta(pkg, f, meta) if res: if msg: # a warning only message print("{}: {}".format(f, msg), file=sys.stderr) break print("{}: {}".format(f, msg), file=sys.stderr) printError(pkg, self.m_error_map['mismatch'], f)
def check(self, pkg): """Checks the given RPM pkg instance against the configured whitelist restriction. Each whitelist violation will be printed with the according error tag. Nothing is returned from this function. """ if pkg.isSource(): return files = pkg.files() for f in files: for restricted in self.m_restricted_paths: if f.startswith(restricted): break else: # no match continue if f in pkg.ghostFiles(): printError(pkg, self.m_error_map['ghost'], f) continue entries = self.m_whitelist_entries.get(f, []) wl_match = None for entry in entries: if entry.package() == pkg.name: wl_match = entry break else: # no whitelist entry exists for this file printError(pkg, self.m_error_map['unauthorized'], f) continue # for the case that there's no match of digests, remember the most # recent digest verification result for diagnosis output towards # the user diag_results = None # check the newest (bottom) entry first it is more likely to match # what we have for audit in reversed(wl_match.audits()): digest_matches, results = audit.compareDigests(pkg) if digest_matches: break if not diag_results: diag_results = results else: # none of the digest entries matched self._printVerificationResults(diag_results) printError(pkg, self.m_error_map['changed'], f) continue
def check(self, pkg): name = pkg.name if name and not name_regex.search(name): printError(pkg, 'non-lsb-compliant-package-name', name) version = pkg[rpm.RPMTAG_VERSION] if version and not version_regex.search(version): printError(pkg, 'non-lsb-compliant-version', version) release = pkg[rpm.RPMTAG_RELEASE] if release and not version_regex.search(release): printError(pkg, 'non-lsb-compliant-release', release)
def check(self, pkg): if pkg.isSource(): return for req in pkg.requires() + pkg.prereq(): if req[0] == 'insserv': printError(pkg, insserv_tag) for fn, pkgfile in pkg.files().items(): if not fn.startswith('/etc/init.d'): continue if os.path.basename(fn).startswith('boot.'): printError(pkg, bootscr_tag, fn) else: printError(pkg, etcinit_tag, fn)
def check_file(self, pkg, filename): root = pkg.dirName() f = root + filename st = getstatusoutput(('desktop-file-validate', f), True) if st[0]: error_printed = False for line in st[1].splitlines(): if 'error: ' in line: printError(pkg, 'invalid-desktopfile', filename, line.split('error: ')[1]) error_printed = True if not error_printed: printError(pkg, 'invalid-desktopfile', filename) if not is_utf8(f): printError(pkg, 'non-utf8-desktopfile', filename) self.parse_desktop_file(pkg, root, f, filename)
def parse_desktop_file(self, pkg, root, f, filename): cfp = cfgparser.RawConfigParser() try: with codecs.open(f, encoding='utf-8') as inputf: cfp.readfp(inputf, filename) except cfgparser.DuplicateSectionError as e: printError( pkg, 'desktopfile-duplicate-section', filename, '[%s]' % e.section) except cfgparser.MissingSectionHeaderError: printError( pkg, 'desktopfile-missing-header', filename) except cfgparser.Error as e: # Only in Python >= 3.2 if (hasattr(cfgparser, 'DuplicateOptionError') and isinstance(e, cfgparser.DuplicateOptionError)): printError( pkg, 'desktopfile-duplicate-option', filename, '[%s]/%s' % (e.section, e.option)) else: printWarning( pkg, 'invalid-desktopfile', filename, e.message.partition(':')[0]) except UnicodeDecodeError as e: printWarning( pkg, 'invalid-desktopfile', filename, 'No valid Unicode') else: binary = None if cfp.has_option('Desktop Entry', 'Exec'): binary = cfp.get('Desktop Entry', 'Exec').partition(' ')[0] if binary: found = False if binary.startswith('/'): found = os.path.exists(root + binary) else: for i in STANDARD_BIN_DIRS: if os.path.exists(root + i + '/' + binary): # no need to check if the binary is +x, rpmlint does it # in another place found = True break if not found: printWarning( pkg, 'desktopfile-without-binary', filename, binary)
def parse_desktop_file(self, pkg, root, f, filename): cfp = cfgparser.RawConfigParser() try: with codecs.open(f, encoding='utf-8') as inputf: cfp.readfp(inputf, filename) except cfgparser.DuplicateSectionError as e: printError(pkg, 'desktopfile-duplicate-section', filename, '[%s]' % e.section) except cfgparser.MissingSectionHeaderError: printError(pkg, 'desktopfile-missing-header', filename) except cfgparser.Error as e: # Only in Python >= 3.2 if (hasattr(cfgparser, 'DuplicateOptionError') and isinstance(e, cfgparser.DuplicateOptionError)): printError(pkg, 'desktopfile-duplicate-option', filename, '[%s]/%s' % (e.section, e.option)) else: printWarning(pkg, 'invalid-desktopfile', filename, e.message.partition(':')[0]) except UnicodeDecodeError as e: printWarning(pkg, 'invalid-desktopfile', filename, 'No valid Unicode') else: binary = None if cfp.has_option('Desktop Entry', 'Exec'): binary = cfp.get('Desktop Entry', 'Exec').partition(' ')[0] if binary: found = False if binary.startswith('/'): found = os.path.exists(root + binary) else: for i in STANDARD_BIN_DIRS: if os.path.exists(root + i + '/' + binary): # no need to check if the binary is +x, rpmlint does it # in another place found = True break if not found: printWarning(pkg, 'desktopfile-without-binary', filename, binary)
def check_aux(self, pkg, files, prog, script, tag, prereq): if script: if prog: if prog not in valid_shells: printError(pkg, "invalid-shell-in-" + tag, prog) if prog in empty_shells: printError(pkg, "non-empty-" + tag, prog) if prog in syntaxcheck_shells or prog == "/usr/bin/perl": if percent_regex.search(script): printWarning(pkg, "percent-in-" + tag) if bracket_regex.search(script): printWarning(pkg, "spurious-bracket-in-" + tag) res = dangerous_command_regex.search(script) if res: printWarning(pkg, "dangerous-command-in-" + tag, res.group(2)) res = selinux_regex.search(script) if res: printError(pkg, "forbidden-selinux-command-in-" + tag, res.group(2)) if "update-menus" in script: menu_error = True for f in files: if menu_regex.search(f): menu_error = False break if menu_error: printError(pkg, "update-menus-without-menu-file-in-" + tag) if tmp_regex.search(script): printError(pkg, "use-tmp-in-" + tag) for c in prereq_assoc: if c[0].search(script): found = False for p in c[1]: if p in prereq or p in files: found = True break if not found: printError(pkg, "no-prereq-on", c[1][0]) if prog in syntaxcheck_shells: if incorrect_shell_script(prog, script): printError(pkg, "shell-syntax-error-in-" + tag) if home_regex.search(script): printError(pkg, "use-of-home-in-" + tag) res = bogus_var_regex.search(script) if res: printWarning(pkg, "bogus-variable-use-in-" + tag, res.group(1)) if prog == "/usr/bin/perl": if incorrect_perl_script(prog, script): printError(pkg, "perl-syntax-error-in-" + tag) elif prog.endswith("sh"): res = single_command_regex.search(script) if res: printWarning(pkg, "one-line-command-in-" + tag, res.group(1)) elif prog not in empty_shells and prog in valid_shells: printWarning(pkg, "empty-" + tag)
def check(self, pkg): global _permissions_d_whitelist if pkg.isSource(): return files = pkg.files() permfiles = {} # first pass, find and parse permissions.d files for f in files: if f in pkg.ghostFiles(): continue if f.startswith("/etc/permissions.d/"): bn = f[19:] if bn not in _permissions_d_whitelist: printError(pkg, "permissions-unauthorized-file", f) bn = bn.split('.')[0] if bn not in permfiles: permfiles[bn] = 1 for f in permfiles: f = pkg.dirName() + "/etc/permissions.d/" + f if os.path.exists(f + ".secure"): self._parsefile(f + ".secure") else: self._parsefile(f) need_set_permissions = False found_suseconfig = False # second pass, find permissions violations for f, pkgfile in files.items(): if pkgfile.filecaps: printError(pkg, 'permissions-fscaps', '%(fname)s has fscaps "%(caps)s"' % {'fname': f, 'caps': pkgfile.filecaps}) mode = pkgfile.mode owner = pkgfile.user + ':' + pkgfile.group # S_IFSOCK 014 socket # S_IFLNK 012 symbolic link # S_IFREG 010 regular file # S_IFBLK 006 block device # S_IFDIR 004 directory # S_IFCHR 002 character device # S_IFIFO 001 FIFO type = (mode >> 12) & 0o17 mode &= 0o7777 need_verifyscript = False if f in self.perms or (type == 4 and f + "/" in self.perms): if type == 0o12: printWarning(pkg, "permissions-symlink", f) continue need_verifyscript = True m = 0 o = "invalid" if type == 4: if f in self.perms: printWarning(pkg, 'permissions-dir-without-slash', f) else: f += '/' if type == 0o10 and mode & 0o111: # pie binaries have 'shared object' here if (pkgfile.magic.startswith('ELF ') and ('shared object' not in pkgfile.magic) and ('pie executable' not in pkgfile.magic)): printError(pkg, 'non-position-independent-executable', f) m = self.perms[f]['mode'] o = self.perms[f]['owner'] if mode != m: printError( pkg, 'permissions-incorrect', '%(file)s has mode 0%(mode)o but should be 0%(m)o' % {'file': f, 'mode': mode, 'm': m}) if owner != o: printError( pkg, 'permissions-incorrect-owner', '%(file)s belongs to %(owner)s but should be %(o)s' % {'file': f, 'owner': owner, 'o': o}) elif type != 0o12: if f + '/' in self.perms: printWarning( pkg, 'permissions-file-as-dir', f + ' is a file but listed as directory') if mode & 0o6000: need_verifyscript = True msg = '%(file)s is packaged with ' \ 'setuid/setgid bits (0%(mode)o)' % \ {'file': f, 'mode': mode} if type != 0o4: printError(pkg, 'permissions-file-setuid-bit', msg) else: printWarning(pkg, 'permissions-directory-setuid-bit', msg) if type == 0o10: if ('shared object' not in pkgfile.magic and 'pie executable' not in pkgfile.magic): printError(pkg, 'non-position-independent-executable', f) if mode & 0o2: need_verifyscript = True printError(pkg, 'permissions-world-writable', '%(file)s is packaged with world writable permissions (0%(mode)o)' % {'file': f, 'mode': mode}) script = pkg[rpm.RPMTAG_POSTIN] or pkg.scriptprog(rpm.RPMTAG_POSTINPROG) found = False if script: for line in script.split("\n"): if "chkstat -n" in line and f in line: found = True break if "SuSEconfig --module permissions" in line \ or "run_permissions is obsolete" in line: found = True found_suseconfig = True break if need_verifyscript and \ (f not in self.perms or 'static' not in self.perms[f]): if not script or not found: printError(pkg, 'permissions-missing-postin', "missing %%set_permissions %s in %%post" % f) need_set_permissions = True script = pkg[rpm.RPMTAG_VERIFYSCRIPT] or pkg[rpm.RPMTAG_VERIFYSCRIPTPROG] found = False if script: for line in script.split("\n"): if "/chkstat" in line and f in line: found = True break if not script or not found: printWarning(pkg, 'permissions-missing-verifyscript', "missing %%verify_permissions -e %s" % f) if need_set_permissions: if 'permissions' not in map(lambda x: x[0], pkg.prereq()): printError(pkg, 'permissions-missing-requires', "missing 'permissions' in PreReq") if found_suseconfig: printInfo(pkg, 'permissions-suseconfig-obsolete', "%run_permissions is obsolete")
def check_scl_spec(self, pkg, spec): '''SCL ready spec checks''' # For the entire spec if not pkg_name.search(spec): printWarning(pkg, 'missing-pkg_name-definition') if scl_prefix_noncond.search(self.remove_scl_conds(spec)): printWarning(pkg, 'scl-prefix-without-condition') if not scl_prefix.search(self.get_name(spec)): printError(pkg, 'name-without-scl-prefix') for item in self.get_obsoletes_and_conflicts(spec): if not scl_prefix.search(item): printError(pkg, 'obsoletes-or-conflicts-without-scl-prefix') break for item in self.get_provides(spec): if not scl_prefix.search(item): printError(pkg, 'provides-without-scl-prefix') break setup_opts = setup.search(spec) if setup_opts: if '-n' not in setup_opts.groups()[0]: printError(pkg, 'scl-setup-without-n') # Examine main package and subpackages one by one borders = [] borders.append(0) # main package starts at the beginning while True: more = subpackage_any.search(spec[borders[-1]:]) if not more: break splits = more.groups()[1].split() if len(splits) > 1 and splits[0] == '-n': if not scl_prefix_start.search(splits[-1]): printError(pkg, 'subpackage-with-n-without-scl-prefix') # current end is counted only from last one borders.append(borders[-1]+more.end()) subpackages = [(borders[i], borders[i+1]) for i in range(len(borders)-1)] for subpackage in subpackages: ok = False for require in self.get_requires(spec[subpackage[0]:subpackage[1]]): # Remove flase entries if not require or require == ':': continue # If it starts with %{name}, it,s fine # If it starts with SCL prefix, it's fine # If it is scl-runtime, it's the best if name_small.search(require) or \ scl_prefix_start.search(require) or \ scl_runtime.match(require): ok = True break if not ok: printError(pkg, 'doesnt-require-scl-runtime-or-other-scl-package') break
def check_metapackage(self, pkg, spec): '''SCL metapackage spec checks''' # Examine subpackages runtime = subpackage_runtime.search(spec) if not runtime: printError(pkg, 'no-runtime-in-scl-metapackage') build = subpackage_build.search(spec) if not build: printError(pkg, 'no-build-in-scl-metapackage') else: # Get (B)Rs section for build subpackage end = index_or_sub(spec[build.end():], '%package', -1) if 'scl-utils-build' not in \ ' '.join(self.get_requires(spec[build.end():end])): printWarning(pkg, 'scl-build-without-requiring-scl-utils-build') alien = subpackage_alien.search(spec) if alien: printError(pkg, 'weird-subpackage-in-scl-metapackage', alien.group()[9:]) # Get (B)Rs section for main package end = index_or_sub(spec, '%package', -1) if 'scl-utils-build' not in \ ' '.join(self.get_build_requires(spec[:end])): printError(pkg, 'scl-metapackage-without-scl-utils-build-br') # Enter %install section install_start = index_or_sub(spec, '%install') install_end = index_or_sub(spec, '%check') if not install_end: install_end = index_or_sub(spec, '%clean') if not install_end: install_end = index_or_sub(spec, '%files') if not install_end: install_end = index_or_sub(spec, '%changelog', -1) # Search %scl_install if not scl_install.search(spec[install_start:install_end]): printError(pkg, 'scl-metapackage-without-%scl_install') if noarch.search(spec[:install_start]) and \ libdir.search(spec[install_start:install_end]): printError(pkg, 'noarch-scl-metapackage-with-libdir') # Analyze %files files = self.get_files(spec) if files: printWarning(pkg, 'scl-main-metapackage-contains-files', ', '.join(files)) if runtime: if not scl_files.search( '\n'.join(self.get_files(spec, 'runtime'))): printError(pkg, 'scl-runtime-package-without-%scl_files') if build: if not scl_macros.search( '\n'.join(self.get_files(spec, 'build'))): printError(pkg, 'scl-build-package-without-rpm-macros')
def check_aux(self, pkg, files, prog, script, tag, prereq): if script: if prog: if prog not in valid_shells: printError(pkg, 'invalid-shell-in-' + tag, prog) if prog in empty_shells: printError(pkg, 'non-empty-' + tag, prog) if prog in syntaxcheck_shells or prog == '/usr/bin/perl': if percent_regex.search(script): printWarning(pkg, 'percent-in-' + tag) if bracket_regex.search(script): printWarning(pkg, 'spurious-bracket-in-' + tag) res = dangerous_command_regex.search(script) if res: printWarning(pkg, 'dangerous-command-in-' + tag, res.group(2)) res = selinux_regex.search(script) if res: printError(pkg, 'forbidden-selinux-command-in-' + tag, res.group(2)) if 'update-menus' in script: menu_error = True for f in files: if menu_regex.search(f): menu_error = False break if menu_error: printError(pkg, 'update-menus-without-menu-file-in-' + tag) if tmp_regex.search(script): printError(pkg, 'use-tmp-in-' + tag) for c in prereq_assoc: if c[0].search(script): found = False for p in c[1]: if p in prereq or p in files: found = True break if not found: printError(pkg, 'no-prereq-on', c[1][0]) if prog in syntaxcheck_shells: if incorrect_shell_script(prog, script): printError(pkg, 'shell-syntax-error-in-' + tag) if home_regex.search(script): printError(pkg, 'use-of-home-in-' + tag) res = bogus_var_regex.search(script) if res: printWarning(pkg, 'bogus-variable-use-in-' + tag, res.group(1)) if prog == '/usr/bin/perl': if incorrect_perl_script(prog, script): printError(pkg, 'perl-syntax-error-in-' + tag) elif prog.endswith('sh'): res = single_command_regex.search(script) if res: printWarning(pkg, 'one-line-command-in-' + tag, res.group(1)) elif prog not in empty_shells and prog in valid_shells: printWarning(pkg, 'empty-' + tag)
def check_binary(self, pkg): files = pkg.files() menus = [] for fname, pkgfile in files.items(): # Check menu files res = menu_file_regex.search(fname) mode = pkgfile.mode if res: basename = res.group(1) if not stat.S_ISREG(mode): printError(pkg, 'non-file-in-menu-dir', fname) else: if basename != pkg.name: printWarning(pkg, 'non-coherent-menu-filename', fname) if mode & 0o444 != 0o444: printError(pkg, 'non-readable-menu-file', fname) if mode & 0o111: printError(pkg, 'executable-menu-file', fname) menus.append(fname) else: # Check old menus from KDE and GNOME res = old_menu_file_regex.search(fname) if res: if stat.S_ISREG(mode): printError(pkg, 'old-menu-entry', fname) else: # Check non transparent xpm files res = xpm_ext_regex.search(fname) if res: if stat.S_ISREG(mode) and not pkg.grep( 'None",', fname): printWarning(pkg, 'non-transparent-xpm', fname) if fname.startswith('/usr/lib64/menu'): printError(pkg, 'menu-in-wrong-dir', fname) if menus: postin = pkg[rpm.RPMTAG_POSTIN] or \ pkg.scriptprog(rpm.RPMTAG_POSTINPROG) if not postin: printError(pkg, 'menu-without-postin') elif not update_menus_regex.search(postin): printError(pkg, 'postin-without-update-menus') postun = pkg[rpm.RPMTAG_POSTUN] or \ pkg.scriptprog(rpm.RPMTAG_POSTUNPROG) if not postun: printError(pkg, 'menu-without-postun') elif not update_menus_regex.search(postun): printError(pkg, 'postun-without-update-menus') directory = pkg.dirName() for f in menus: # remove comments and handle cpp continuation lines cmd = Pkg.getstatusoutput(('/lib/cpp', directory + f), True)[1] for line in cmd.splitlines(): if not line.startswith('?'): continue res = package_regex.search(line) if res: package = res.group(1) if package != pkg.name: printWarning(pkg, 'incoherent-package-value-in-menu', package, f) else: printInfo(pkg, 'unable-to-parse-menu-entry', line) command = True res = command_regex.search(line) if res: command_line = (res.group(1) or res.group(2)).split() command = command_line[0] for launcher in launchers: if not launcher[0].search(command): continue found = False if launcher[1]: found = '/bin/' + command_line[0] in files or \ '/usr/bin/' + command_line[0] in files or \ '/usr/X11R6/bin/' + command_line[0] \ in files if not found: for l in launcher[1]: if l in pkg.req_names(): found = True break if not found: printError( pkg, 'use-of-launcher-in-menu-but-no-requires-on', launcher[1][0]) command = command_line[1] break if command[0] == '/': if command not in files: printWarning(pkg, 'menu-command-not-in-package', command) elif not ('/bin/' + command in files or '/usr/bin/' + command in files or '/usr/X11R6/bin/' + command in files): printWarning(pkg, 'menu-command-not-in-package', command) else: printWarning(pkg, 'missing-menu-command') command = False res = longtitle_regex.search(line) if res: grp = res.groups() title = grp[1] or grp[2] if title[0] != title[0].upper(): printWarning(pkg, 'menu-longtitle-not-capitalized', title) res = version_regex.search(title) if res: printWarning(pkg, 'version-in-menu-longtitle', title) else: printError(pkg, 'no-longtitle-in-menu', f) title = None res = title_regex.search(line) if res: grp = res.groups() title = grp[1] or grp[2] if title[0] != title[0].upper(): printWarning(pkg, 'menu-title-not-capitalized', title) res = version_regex.search(title) if res: printWarning(pkg, 'version-in-menu-title', title) if '/' in title: printError(pkg, 'invalid-title', title) else: printError(pkg, 'no-title-in-menu', f) title = None res = needs_regex.search(line) if res: grp = res.groups() needs = (grp[1] or grp[2]).lower() if needs in ('x11', 'text', 'wm'): res = section_regex.search(line) if res: grp = res.groups() section = grp[1] or grp[2] # don't warn entries for sections if command and section not in valid_sections: printError(pkg, 'invalid-menu-section', section, f) else: printInfo(pkg, 'unable-to-parse-menu-section', line) elif needs not in standard_needs: printInfo(pkg, 'strange-needs', needs, f) else: printInfo(pkg, 'unable-to-parse-menu-needs', line) res = icon_regex.search(line) if res: icon = res.group(1) if not icon_ext_regex.search(icon): printWarning(pkg, 'invalid-menu-icon-type', icon) if icon[0] == '/' and needs == 'x11': printWarning(pkg, 'hardcoded-path-in-menu-icon', icon) else: for path in icon_paths: if (path[0] + icon) not in files: printError( pkg, path[1] + '-icon-not-in-package', icon, f) else: printWarning(pkg, 'no-icon-in-menu', title) res = xdg_migrated_regex.search(line) if res: if not res.group(1).lower() == "true": printError(pkg, 'non-xdg-migrated-menu') else: printError(pkg, 'non-xdg-migrated-menu')
def check(self, pkg): packager = pkg[rpm.RPMTAG_PACKAGER] if packager: self._unexpanded_macros(pkg, 'Packager', packager) if Config.getOption('Packager') and \ not packager_regex.search(packager): printWarning(pkg, 'invalid-packager', packager) else: printError(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: printError(pkg, 'invalid-version', version) else: printError(pkg, 'no-version-tag') release = pkg[rpm.RPMTAG_RELEASE] if release: self._unexpanded_macros(pkg, 'Release', release) if release_ext and not extension_regex.search(release): printWarning(pkg, 'not-standard-release-extension', release) else: printError(pkg, 'no-release-tag') epoch = pkg[rpm.RPMTAG_EPOCH] if epoch is None: if use_epoch: printError(pkg, 'no-epoch-tag') else: if epoch > 99: printWarning(pkg, 'unreasonable-epoch', epoch) epoch = str(epoch) if 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): printWarning(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.isSource() for d in deps: value = Pkg.formatRequire(*d) if use_epoch and d[1] and d[2][0] is None and \ not d[0].startswith('rpmlib('): printWarning(pkg, 'no-epoch-in-dependency', value) for r in INVALID_REQUIRES: if r.search(d[0]): printError(pkg, 'invalid-dependency', d[0]) if d[0].startswith('/usr/local/'): printError(pkg, 'invalid-dependency', d[0]) if is_source: if lib_devel_number_regex.search(d[0]): printError(pkg, 'invalid-build-requires', d[0]) elif not is_devel: if not devel_depend and FilesCheck.devel_regex.search(d[0]): printError(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): printError(pkg, 'explicit-lib-dependency', d[0]) if d[1] == rpm.RPMSENSE_EQUAL and d[2][2] is not None: printWarning(pkg, 'requires-on-release', value) self._unexpanded_macros(pkg, 'dependency %s' % (value,), value) self._unexpanded_macros(pkg, 'Name', name) if not name: printError(pkg, 'no-name-tag') else: if is_devel and not is_source: base = is_devel.group(1) dep = None has_so = False for fname in pkg.files(): if fname.endswith('.so'): has_so = True break 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)?(\(\w+-\d+\))?$' % re.escape(base)) for d in deps: if base_or_libs_re.match(d[0]): dep = d break if not dep: printWarning(pkg, 'no-dependency-on', base_or_libs) elif version: exp = (epoch, version, None) sexp = Pkg.versionToString(exp) if not dep[1]: printWarning(pkg, 'no-version-dependency-on', base_or_libs, sexp) elif dep[2][:2] != exp[:2]: printWarning(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: printWarning(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()): printWarning(pkg, 'no-provides', prov) # 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 = pkg[rpm.RPMTAG_SUMMARY] if summary: if not langs: self._unexpanded_macros(pkg, 'Summary', Pkg.b2s(summary)) else: for lang in langs: self.check_summary(pkg, lang, ignored_words) else: printError(pkg, 'no-summary-tag') description = pkg[rpm.RPMTAG_DESCRIPTION] if description: if not langs: self._unexpanded_macros(pkg, '%description', Pkg.b2s(description)) else: for lang in langs: self.check_description(pkg, lang, ignored_words) else: printError(pkg, 'no-description-tag') group = pkg[rpm.RPMTAG_GROUP] self._unexpanded_macros(pkg, 'Group', group) if not group: printError(pkg, 'no-group-tag') elif VALID_GROUPS and group not in VALID_GROUPS: printWarning(pkg, 'non-standard-group', group) buildhost = pkg[rpm.RPMTAG_BUILDHOST] self._unexpanded_macros(pkg, 'BuildHost', buildhost) if not buildhost: printError(pkg, 'no-buildhost-tag') elif Config.getOption('ValidBuildHost') and \ not valid_buildhost_regex.search(buildhost): printWarning(pkg, 'invalid-buildhost', buildhost) changelog = pkg[rpm.RPMTAG_CHANGELOGNAME] if not changelog: printError(pkg, 'no-changelogname-tag') else: clt = pkg[rpm.RPMTAG_CHANGELOGTEXT] if use_version_in_changelog: ret = changelog_version_regex.search(Pkg.b2s(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(Pkg.b2s(clt[0])) if not ret: printWarning(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 release_ext: expected.append( extension_regex.sub('', expected[0])) if ret.group(1) not in expected: if len(expected) == 1: expected = expected[0] printWarning(pkg, 'incoherent-version-in-changelog', ret.group(1), expected) if use_utf8: if clt: changelog = changelog + clt for s in changelog: if not Pkg.is_utf8_bytestr(s): printError(pkg, 'tag-not-utf8', '%changelog') 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: printWarning(pkg, 'changelog-time-overflow', time.strftime("%Y-%m-%d", time.gmtime(clt))) elif clt > time.time(): printError(pkg, 'changelog-time-in-future', time.strftime("%Y-%m-%d", time.gmtime(clt))) # for provide_name in (x[0] for x in pkg.provides()): # if name == provide_name: # printWarning(pkg, 'package-provides-itself') # break def split_license(license): return (x.strip() for x in (l for l in license_regex.split(license) if l)) rpm_license = pkg[rpm.RPMTAG_LICENSE] if not rpm_license: printError(pkg, 'no-license') else: valid_license = True if rpm_license not in VALID_LICENSES: for l1 in split_license(rpm_license): if l1 in VALID_LICENSES: continue for l2 in split_license(l1): if l2 not in VALID_LICENSES: printWarning(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 = Pkg.b2s(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 \ (Config.getOption('InvalidURL') and invalid_url_regex.search(url)): printWarning(pkg, 'invalid-url', tag, url) else: self.check_url(pkg, tag, url) elif tag == 'URL': printWarning(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): printWarning(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 = [] for p in prov_names: if prov_names.count(p) != 1 and p not in useless_provides: useless_provides.append(p) for p in useless_provides: printError(pkg, 'useless-provides', p) for p in pkg.provides(): value = Pkg.formatRequire(*p) self._unexpanded_macros(pkg, 'Provides %s' % (value,), value) for c in pkg.conflicts(): value = Pkg.formatRequire(*c) self._unexpanded_macros(pkg, 'Conflicts %s' % (value,), value) obss = pkg.obsoletes() if obss: provs = pkg.provides() for prov in provs: for obs in obss: if Pkg.rangeCompare(obs, prov): printWarning(pkg, 'self-obsoletion', '%s obsoletes %s' % (Pkg.formatRequire(*obs), Pkg.formatRequire(*prov))) expfmt = rpm.expandMacro("%{_build_name_fmt}") if pkg.isSource(): # _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 = os.path.basename(pkg.filename) if basename != expected: printWarning(pkg, 'non-coherent-filename', basename, expected) for tag in ('Distribution', 'DistTag', 'ExcludeArch', 'ExcludeOS', 'Vendor'): if hasattr(rpm, 'RPMTAG_%s' % tag.upper()): res = Pkg.b2s(pkg[getattr(rpm, 'RPMTAG_%s' % tag.upper())]) self._unexpanded_macros(pkg, tag, res) for path in private_so_paths: for fname, pkgfile in pkg.files().items(): if fname.startswith(path): for prov in pkgfile.provides: if so_dep_regex.search(prov[0]): printWarning(pkg, "private-shared-object-provides", fname, Pkg.formatRequire(*prov))
def check_scl_spec(self, pkg, spec): '''SCL ready spec checks''' # For the entire spec if not pkg_name.search(spec): printWarning(pkg, 'missing-pkg_name-definition') if scl_prefix_noncond.search(self.remove_scl_conds(spec)): printWarning(pkg, 'scl-prefix-without-condition') if not scl_prefix.search(self.get_name(spec)): printError(pkg, 'name-without-scl-prefix') for item in self.get_obsoletes_and_conflicts(spec): if not scl_prefix.search(item): printError(pkg, 'obsoletes-or-conflicts-without-scl-prefix') break for item in self.get_provides(spec): if not scl_prefix.search(item): printError(pkg, 'provides-without-scl-prefix') break setup_opts = setup.search(spec) if setup_opts: if '-n' not in setup_opts.groups()[0]: printError(pkg, 'scl-setup-without-n') # Examine main package and subpackages one by one borders = [] borders.append(0) # main package starts at the beginning while True: more = subpackage_any.search(spec[borders[-1]:]) if not more: break splits = more.groups()[1].split() if len(splits) > 1 and splits[0] == '-n': if not scl_prefix_start.search(splits[-1]): printError(pkg, 'subpackage-with-n-without-scl-prefix') # current end is counted only from last one borders.append(borders[-1] + more.end()) subpackages = [(borders[i], borders[i + 1]) for i in range(len(borders) - 1)] for subpackage in subpackages: ok = False for require in self.get_requires( spec[subpackage[0]:subpackage[1]]): # Remove flase entries if not require or require == ':': continue # If it starts with %{name}, it,s fine # If it starts with SCL prefix, it's fine # If it is scl-runtime, it's the best if name_small.search(require) or \ scl_prefix_start.search(require) or \ scl_runtime.match(require): ok = True break if not ok: printError(pkg, 'doesnt-require-scl-runtime-or-other-scl-package') break
def check_binary(self, pkg): files = pkg.files() exec_files = [] has_lib = False version = None binary = False binary_in_usr_lib = False has_usr_lib_file = False multi_pkg = False srpm = pkg[rpm.RPMTAG_SOURCERPM] if srpm: res = srcname_regex.search(srpm) if res: multi_pkg = (pkg.name != res.group(1)) for fname, pkgfile in files.items(): if not stat.S_ISDIR(pkgfile.mode) and usr_lib_regex.search(fname): has_usr_lib_file = True if not binary_in_usr_lib and \ usr_lib_exception_regex.search(fname): # Fake that we have binaries there to avoid # only-non-binary-in-usr-lib false positives binary_in_usr_lib = True is_elf = pkgfile.magic.startswith('ELF ') is_ar = 'current ar archive' in pkgfile.magic is_ocaml_native = 'Objective caml native' in pkgfile.magic is_lua_bytecode = 'Lua bytecode' in pkgfile.magic is_binary = is_elf or is_ar or is_ocaml_native or is_lua_bytecode if not is_binary: if reference_regex.search(fname): lines = pkg.grep(invalid_dir_ref_regex, fname) if lines: printError(pkg, 'invalid-directory-reference', fname, '(line %s)' % ", ".join(lines)) continue # binary files only from here on binary = True if has_usr_lib_file and not binary_in_usr_lib and \ usr_lib_regex.search(fname): binary_in_usr_lib = True if pkg.arch == 'noarch': printError( pkg, 'arch-independent-package-contains-binary-or-object', fname) continue # arch dependent packages only from here on # in /usr/share ? if fname.startswith('/usr/share/'): printError(pkg, 'arch-dependent-file-in-usr-share', fname) # in /etc ? if fname.startswith('/etc/'): printError(pkg, 'binary-in-etc', fname) if pkg.arch == 'sparc' and sparc_regex.search(pkgfile.magic): printError(pkg, 'non-sparc32-binary', fname) if is_ocaml_native or is_lua_bytecode or fname.endswith('.o') or \ fname.endswith('.static'): continue # stripped ? if 'not stripped' in pkgfile.magic: printWarning(pkg, 'unstripped-binary-or-object', fname) # inspect binary file is_shlib = so_regex.search(fname) bin_info = BinaryInfo(pkg, pkgfile.path, fname, is_ar, is_shlib) if is_shlib: has_lib = True # shared libs if is_shlib and not bin_info.readelf_error: # so name in library if not bin_info.soname: printWarning(pkg, 'no-soname', fname) else: if not validso_regex.search(bin_info.soname): printError(pkg, 'invalid-soname', fname, bin_info.soname) else: (directory, base) = dir_base(fname) try: symlink = directory + bin_info.soname link = files[symlink].linkto if link not in (fname, base, ''): printError(pkg, 'invalid-ldconfig-symlink', fname, link) except KeyError: if base.startswith("lib") or \ base.startswith("ld-"): printError(pkg, 'no-ldconfig-symlink', fname) res = soversion_regex.search(bin_info.soname) if res: soversion = res.group(1) or res.group(2) if version is None: version = soversion elif version != soversion: version = -1 if bin_info.non_pic: printError(pkg, 'shlib-with-non-pic-code', fname) # It could be useful to check these for others than shared # libs only, but that has potential to generate lots of # false positives and noise. for s in bin_info.undef: printWarning(pkg, 'undefined-non-weak-symbol', fname, s) for s in bin_info.unused: printWarning(pkg, 'unused-direct-shlib-dependency', fname, s) # calls exit() or _exit()? for ec in bin_info.exit_calls: printWarning(pkg, 'shared-lib-calls-exit', fname, ec) for ec in bin_info.forbidden_calls: printWarning(pkg, ec, fname, BinaryInfo.forbidden_functions[ec]['f_name']) # rpath ? if bin_info.rpath: for p in bin_info.rpath: if p in system_lib_paths or not usr_lib_regex.search(p): printError(pkg, 'binary-or-shlib-defines-rpath', fname, bin_info.rpath) break is_exec = 'executable' in pkgfile.magic is_shobj = 'shared object' in pkgfile.magic if not is_exec and not is_shobj: continue if is_shobj and not is_exec and '.so' not in fname and \ bin_regex.search(fname): # pkgfile.magic does not contain "executable" for PIEs is_exec = True if is_exec: if bin_regex.search(fname): exec_files.append(fname) if ocaml_mixed_regex.search(bin_info.tail): printWarning(pkg, 'ocaml-mixed-executable', fname) if not is_shobj and pie_exec_re and pie_exec_re.search(fname): printError(pkg, 'non-position-independent-executable', fname) if bin_info.readelf_error: continue if not bin_info.needed and not ( bin_info.soname and ldso_soname_regex.search(bin_info.soname)): if is_shobj: printError(pkg, 'shared-lib-without-dependency-information', fname) else: printError(pkg, 'statically-linked-binary', fname) else: # linked against libc ? if "libc." not in fname and \ (not bin_info.soname or ("libc." not in bin_info.soname and not ldso_soname_regex.search(bin_info.soname))): found_libc = False for lib in bin_info.needed: if "libc." in lib: found_libc = True break if not found_libc: if is_shobj: printError(pkg, 'library-not-linked-against-libc', fname) else: printError(pkg, 'program-not-linked-against-libc', fname) if bin_info.stack: if bin_info.exec_stack: printWarning(pkg, 'executable-stack', fname) elif not bin_info.readelf_error and ( pkg.arch.endswith("86") or pkg.arch.startswith("pentium") or pkg.arch in ("athlon", "x86_64")): printError(pkg, 'missing-PT_GNU_STACK-section', fname) if bin_info.setgid and bin_info.setuid and not bin_info.setgroups: printError(pkg, 'missing-call-to-setgroups-before-setuid', fname) if bin_info.chroot and not bin_info.chroot_near_chdir: printError(pkg, 'missing-call-to-chdir-with-chroot', fname) if bin_info.mktemp: printError(pkg, 'call-to-mktemp', fname) if has_lib: for f in exec_files: printError(pkg, 'executable-in-library-package', f) for f in files: res = numeric_dir_regex.search(f) fn = res and res.group(1) or f if f not in exec_files and not so_regex.search(f) and \ not versioned_dir_regex.search(fn): printError(pkg, 'non-versioned-file-in-library-package', f) if version and version != -1 and version not in pkg.name: printError(pkg, 'incoherent-version-in-name', version) if not binary and not multi_pkg and pkg.arch != 'noarch': printError(pkg, 'no-binary') if has_usr_lib_file and not binary_in_usr_lib: printWarning(pkg, 'only-non-binary-in-usr-lib')
def check(self, pkg): """Checks the given RPM pkg instance against the configured whitelist restriction. Each whitelist violation will be printed with the according error tag. Nothing is returned from this function. """ from Filter import printError if pkg.isSource(): return files = pkg.files() already_tested = set() for f in files: if not self._isRestrictedPath(f): continue if f in pkg.ghostFiles(): printError(pkg, self.m_error_map['ghost'], f) continue wl_match = self._getWhitelist(pkg.name, f) if not wl_match: # no whitelist entry exists for this file printError(pkg, self.m_error_map['unauthorized'], f) continue # avoid testing the same paths multiple times thereby avoiding # duplicate error messages or unnecessary re-checks of the same # files. # this is necessary since whitelisting entries can consist of # groups of files that are all checked in one go below. if f in already_tested: continue # for the case that there's no match of digests, remember the most # recent digest verification result for diagnosis output towards # the user diag_results = None # check the newest (bottom) entry first it is more likely to match # what we have for audit in reversed(wl_match.audits()): digest_matches, results = audit.compareDigests(pkg) if digest_matches: for r in results: already_tested.add(r.path()) break if not diag_results: diag_results = results else: for r in diag_results: already_tested.add(r.path()) # none of the digest entries matched self._printVerificationResults(diag_results) printError(pkg, self.m_error_map['changed'], f) continue
def check_spec(self, pkg, spec_file): self._spec_file = spec_file spec_only = isinstance(pkg, Pkg.FakePkg) patches = {} applied_patches = [] applied_patches_ifarch = [] patches_auto_applied = False source_dir = False buildroot = False configure_linenum = None configure_cmdline = "" mklibname = False is_lib_pkg = False if_depth = 0 ifarch_depth = -1 current_section = 'package' buildroot_clean = {'clean': False, 'install': False} depscript_override = False depgen_disabled = False patch_fuzz_override = False indent_spaces = 0 indent_tabs = 0 section = {} # None == main package current_package = None package_noarch = {} is_utf8 = False if self._spec_file and use_utf8: if Pkg.is_utf8(self._spec_file): is_utf8 = True else: printError(pkg, "non-utf8-spec-file", self._spec_name or self._spec_file) # gather info from spec lines pkg.current_linenum = 0 nbsp = UNICODE_NBSP if is_utf8 else chr(0xA0) do_unicode = is_utf8 and sys.version_info[0] <= 2 for line in Pkg.readlines(spec_file): pkg.current_linenum += 1 if do_unicode: line = unicode(line, "utf-8", "replace") # noqa false positive char = line.find(nbsp) if char != -1: printWarning(pkg, "non-break-space", "line %s, char %d" % (pkg.current_linenum, char)) section_marker = False for sec, regex in section_regexs.items(): res = regex.search(line) if res: current_section = sec section_marker = True section[sec] = section.get(sec, 0) + 1 if sec in ('package', 'files'): rest = filelist_regex.sub('', line[res.end() - 1:]) res = pkgname_regex.search(rest) if res: current_package = res.group(1) else: current_package = None break if section_marker: if not is_lib_pkg and lib_package_regex.search(line): is_lib_pkg = True continue if current_section in ('prep', 'build') and \ contains_buildroot(line): printWarning(pkg, 'rpm-buildroot-usage', '%' + current_section, line[:-1].strip()) if make_check_regex.search(line) and current_section not in \ ('check', 'changelog', 'package', 'description'): printWarning(pkg, 'make-check-outside-check-section', line[:-1]) if current_section in buildroot_clean and \ not buildroot_clean[current_section] and \ contains_buildroot(line) and rm_regex.search(line): buildroot_clean[current_section] = True if ifarch_regex.search(line): if_depth = if_depth + 1 ifarch_depth = if_depth if if_regex.search(line): if_depth = if_depth + 1 if setup_regex.match(line): if not setup_q_regex.search(line): # Don't warn if there's a -T without -a or -b if setup_t_regex.search(line): if setup_ab_regex.search(line): printWarning(pkg, 'setup-not-quiet') else: printWarning(pkg, 'setup-not-quiet') if current_section != 'prep': printWarning(pkg, 'setup-not-in-prep') elif autopatch_regex.search(line): patches_auto_applied = True if current_section != 'prep': printWarning(pkg, '%autopatch-not-in-prep') else: res = autosetup_regex.search(line) if res: if not autosetup_n_regex.search(res.group(1)): patches_auto_applied = True if current_section != 'prep': printWarning(pkg, '%autosetup-not-in-prep') if endif_regex.search(line): if ifarch_depth == if_depth: ifarch_depth = -1 if_depth = if_depth - 1 res = applied_patch_regex.search(line) if res: pnum = res.group(1) or 0 for tmp in applied_patch_p_regex.findall(line) or [pnum]: pnum = int(tmp) applied_patches.append(pnum) if ifarch_depth > 0: applied_patches_ifarch.append(pnum) else: res = applied_patch_pipe_regex.search(line) if res: pnum = int(res.group(1)) applied_patches.append(pnum) if ifarch_depth > 0: applied_patches_ifarch.append(pnum) if not res and not source_dir: res = source_dir_regex.search(line) if res: source_dir = True printError(pkg, "use-of-RPM_SOURCE_DIR") if configure_linenum: if configure_cmdline[-1] == "\\": configure_cmdline = configure_cmdline[:-1] + line.strip() else: res = configure_libdir_spec_regex.search(configure_cmdline) if not res: # Hack to get the correct (start of ./configure) line # number displayed: real_linenum = pkg.current_linenum pkg.current_linenum = configure_linenum printWarning(pkg, "configure-without-libdir-spec") pkg.current_linenum = real_linenum elif res.group(1): res = re.match(hardcoded_library_paths, res.group(1)) if res: printError(pkg, "hardcoded-library-path", res.group(1), "in configure options") configure_linenum = None hashPos = line.find("#") if current_section != 'changelog': cfgPos = line.find('./configure') if cfgPos != -1 and (hashPos == -1 or hashPos > cfgPos): # store line where it started configure_linenum = pkg.current_linenum configure_cmdline = line.strip() res = hardcoded_library_path_regex.search(line) if current_section != 'changelog' and res and not \ (biarch_package_regex.match(pkg.name) or hardcoded_lib_path_exceptions_regex.search( res.group(1).lstrip())): printError(pkg, "hardcoded-library-path", "in", res.group(1).lstrip()) if '%mklibname' in line: mklibname = True if current_section == 'package': # Would be cleaner to get sources and patches from the # specfile parsed in Python (see below), but we want to # catch %ifarch'd etc ones as well, and also catch these when # the specfile is not parseable. res = patch_regex.search(line) if res: pnum = int(res.group(1) or 0) patches[pnum] = res.group(2) res = obsolete_tags_regex.search(line) if res: printWarning(pkg, "obsolete-tag", res.group(1)) res = buildroot_regex.search(line) if res: buildroot = True if res.group(1).startswith('/'): printWarning(pkg, 'hardcoded-path-in-buildroot-tag', res.group(1)) res = buildarch_regex.search(line) if res: if res.group(1) != "noarch": printError(pkg, 'buildarch-instead-of-exclusivearch-tag', res.group(1)) else: package_noarch[current_package] = True res = packager_regex.search(line) if res: printWarning(pkg, 'hardcoded-packager-tag', res.group(1)) res = prefix_regex.search(line) if res: if not res.group(1).startswith('%'): printWarning(pkg, 'hardcoded-prefix-tag', res.group(1)) res = prereq_regex.search(line) if res: printError(pkg, 'prereq-use', res.group(2)) res = buildprereq_regex.search(line) if res: printError(pkg, 'buildprereq-use', res.group(1)) if scriptlet_requires_regex.search(line): printError(pkg, 'broken-syntax-in-scriptlet-requires', line.strip()) res = requires_regex.search(line) if res: reqs = Pkg.parse_deps(res.group(1)) for req in unversioned(reqs): if compop_regex.search(req): printWarning(pkg, 'comparison-operator-in-deptoken', req) res = provides_regex.search(line) if res: provs = Pkg.parse_deps(res.group(1)) for prov in unversioned(provs): if not prov.startswith('/'): printWarning(pkg, 'unversioned-explicit-provides', prov) if compop_regex.search(prov): printWarning(pkg, 'comparison-operator-in-deptoken', prov) res = obsoletes_regex.search(line) if res: obses = Pkg.parse_deps(res.group(1)) for obs in unversioned(obses): if not obs.startswith('/'): printWarning(pkg, 'unversioned-explicit-obsoletes', obs) if compop_regex.search(obs): printWarning(pkg, 'comparison-operator-in-deptoken', obs) res = conflicts_regex.search(line) if res: confs = Pkg.parse_deps(res.group(1)) for conf in unversioned(confs): if compop_regex.search(conf): printWarning(pkg, 'comparison-operator-in-deptoken', conf) if current_section == 'changelog': for match in AbstractCheck.macro_regex.findall(line): res = re.match('%+', match) if len(res.group(0)) % 2: printWarning(pkg, 'macro-in-%changelog', match) else: if not depscript_override: depscript_override = \ depscript_override_regex.search(line) is not None if not depgen_disabled: depgen_disabled = \ depgen_disable_regex.search(line) is not None if not patch_fuzz_override: patch_fuzz_override = \ patch_fuzz_override_regex.search(line) is not None if current_section == 'files': # TODO: check scriptlets for these too? if package_noarch.get(current_package) or \ (current_package not in package_noarch and package_noarch.get(None)): res = libdir_regex.search(line) if res: pkgname = current_package if pkgname is None: pkgname = '(main package)' printWarning(pkg, 'libdir-macro-in-noarch-package', pkgname, line.rstrip()) if not indent_tabs and '\t' in line: indent_tabs = pkg.current_linenum if not indent_spaces and indent_spaces_regex.search(line): indent_spaces = pkg.current_linenum # Check if egrep or fgrep is used if current_section not in \ ('package', 'changelog', 'description', 'files'): greps = deprecated_grep_regex.findall(line) if greps: printWarning(pkg, "deprecated-grep", greps) # If not checking spec file only, we're checking one inside a # SRPM -> skip this check to avoid duplicate warnings (#167) if spec_only and VALID_GROUPS and \ line.lower().startswith("group:"): group = line[6:].strip() if group not in VALID_GROUPS: printWarning(pkg, 'non-standard-group', group) # Test if there are macros in comments if hashPos != -1 and \ (hashPos == 0 or line[hashPos - 1] in (" ", "\t")): for match in AbstractCheck.macro_regex.findall( line[hashPos + 1:]): res = re.match('%+', match) if len(res.group(0)) % 2: printWarning(pkg, 'macro-in-comment', match) # Last line read is not useful after this point pkg.current_linenum = None for sect in (x for x in buildroot_clean if not buildroot_clean[x]): printWarning(pkg, 'no-cleaning-of-buildroot', '%' + sect) if not buildroot: printWarning(pkg, 'no-buildroot-tag') for sec in ('prep', 'build', 'install', 'clean'): if not section.get(sec): printWarning(pkg, 'no-%%%s-section' % sec) for sec in ('changelog',): # prep, build, install, clean, check prevented by rpmbuild 4.4 if section.get(sec, 0) > 1: printWarning(pkg, 'more-than-one-%%%s-section' % sec) if is_lib_pkg and not mklibname: printError(pkg, 'lib-package-without-%mklibname') if depscript_override and not depgen_disabled: printWarning(pkg, 'depscript-without-disabling-depgen') if patch_fuzz_override: printWarning(pkg, 'patch-fuzz-is-changed') if indent_spaces and indent_tabs: pkg.current_linenum = max(indent_spaces, indent_tabs) printWarning(pkg, 'mixed-use-of-spaces-and-tabs', '(spaces: line %d, tab: line %d)' % (indent_spaces, indent_tabs)) pkg.current_linenum = None # process gathered info if not patches_auto_applied: for pnum, pfile in patches.items(): if pnum in applied_patches_ifarch: printWarning(pkg, "%ifarch-applied-patch", "Patch%d:" % pnum, pfile) if pnum not in applied_patches: printWarning(pkg, "patch-not-applied", "Patch%d:" % pnum, pfile) # Rest of the checks require a real spec file if not self._spec_file: return # We'd like to parse the specfile only once using python bindings, # but it seems errors from rpmlib get logged to stderr and we can't # capture and print them nicely, so we do it once each way :P out = Pkg.getstatusoutput(('env', 'LC_ALL=C', 'rpm', '-q', '--qf=', '--specfile', self._spec_file)) parse_error = False for line in out[1].splitlines(): # No such file or dir hack: https://bugzilla.redhat.com/487855 if 'No such file or directory' not in line: parse_error = True printError(pkg, 'specfile-error', line) if not parse_error: # grab sources and patches from parsed spec object to get # them with macros expanded for URL checking spec_obj = None try: ts = rpm.TransactionSet() spec_obj = ts.parseSpec(self._spec_file) except: # errors logged above already pass if spec_obj: try: # rpm < 4.8.0 sources = spec_obj.sources() except TypeError: # rpm >= 4.8.0 sources = spec_obj.sources for src in sources: (url, num, flags) = src (scheme, netloc) = urlparse(url)[0:2] if flags & 1: # rpmspec.h, rpm.org ticket #123 srctype = "Source" else: srctype = "Patch" tag = '%s%s' % (srctype, num) if scheme and netloc: info = self.check_url(pkg, tag, url) if not info or not hasattr(pkg, 'files'): continue clen = info.get("Content-Length") if clen is not None: clen = int(clen) cmd5 = info.get("Content-MD5") if cmd5 is not None: cmd5 = cmd5.lower() if clen is not None or cmd5 is not None: # Not using path from urlparse results to match how # rpm itself parses the basename. pkgfile = pkg.files().get(url.split("/")[-1]) if pkgfile: if clen is not None and pkgfile.size != clen: printWarning(pkg, 'file-size-mismatch', '%s = %s, %s = %s' % (pkgfile.name, pkgfile.size, url, clen)) # pkgfile.md5 could be some other digest than # MD5, treat as MD5 only if it's 32 chars long if cmd5 and len(pkgfile.md5) == 32 \ and pkgfile.md5 != cmd5: printWarning(pkg, 'file-md5-mismatch', '%s = %s, %s = %s' % (pkgfile.name, pkgfile.md5, url, cmd5)) elif srctype == "Source" and tarball_regex.search(url): printWarning(pkg, 'invalid-url', '%s:' % tag, url)
def check(self, pkg): global _policy_legacy_exceptions if pkg.isSource(): return # Only check unsuffixed lib* packages if pkg.name.endswith("-devel") or pkg.name.endswith("-doc"): return files = pkg.files() # Search for shared libraries in this package libs = set() libs_needed = set() libs_to_dir = dict() dirs = set() reqlibs = set() pkg_requires = set(map(lambda x: string.split(x[0], "(")[0], pkg.requires())) for f, pkgfile in files.items(): if f.find(".so.") != -1 or f.endswith(".so"): filename = pkg.dirName() + "/" + f try: if stat.S_ISREG(files[f].mode) and "ELF" in pkgfile.magic: bi = BinaryInfo(pkg, filename, f, False, True) libs_needed = libs_needed.union(bi.needed) if bi.soname != 0: lib_dir = string.join(f.split("/")[:-1], "/") libs.add(bi.soname) libs_to_dir[bi.soname] = lib_dir dirs.add(lib_dir) if bi.soname in pkg_requires: # But not if the library is used by the pkg itself # This avoids program packages with their own # private lib # FIXME: we'd need to check if somebody else links # to this lib reqlibs.add(bi.soname) except Exception: pass pass std_dirs = dirs.intersection(("/lib", "/lib64", "/usr/lib", "/usr/lib64", "/opt/kde3/lib", "/opt/kde3/lib64")) # If this is a program package (all libs it provides are # required by itself), bail out if not pkg.name.startswith("lib") and len(libs.difference(reqlibs)) == 0: return std_lib_package = False if pkg.name.startswith("lib") and pkg.name[-1].isdigit(): std_lib_package = True # ignore libs in a versioned non_std_dir if std_lib_package: for lib in libs.copy(): lib_dir = libs_to_dir[lib] if lib_dir.startswith("/opt/kde3"): continue for lib_part in lib_dir.split("/"): if len(lib_part) == 0: continue if lib_part[-1].isdigit() and not lib_part.endswith("lib64"): libs.remove(lib) break # Check for non-versioned libs in a std lib package if std_lib_package: for lib in libs.copy(): if not lib[-1].isdigit(): printWarning(pkg, "shlib-unversioned-lib", lib) libs.remove(lib) # If this package should be or should be splitted into shlib # package(s) if len(libs) > 0 and len(std_dirs) > 0: # If the package contains a single shlib, name after soname if len(libs) == 1: soname = libs.copy().pop() libname = libname_from_soname(soname) if libname.startswith("lib") and pkg.name != libname and pkg.name != libname + "-mini": if libname in _policy_legacy_exceptions: printWarning(pkg, "shlib-legacy-policy-name-error", libname) else: printError(pkg, "shlib-policy-name-error", libname) elif not pkg.name[-1:].isdigit(): printError(pkg, "shlib-policy-missing-suffix") if (not pkg.name.startswith("lib")) or pkg.name.endswith("-lang"): return if not libs: if pkg.name in _policy_legacy_exceptions: printWarning(pkg, "shlib-legacy-policy-missing-lib", pkg.name) else: printError(pkg, "shlib-policy-missing-lib") # Verify no non-lib stuff is in the package dirs = set() for f in files: if os.path.isdir(pkg.dirName() + f): dirs.add(f) # Verify shared lib policy package doesn't have hard dependency on non-lib packages if std_lib_package: for dep in pkg.requires(): if dep[0].startswith("rpmlib(") or dep[0].startswith("config("): continue if (dep[1] & (rpm.RPMSENSE_GREATER | rpm.RPMSENSE_EQUAL)) == rpm.RPMSENSE_EQUAL: printWarning(pkg, "shlib-fixed-dependency", Pkg.formatRequire(dep[0], dep[1], dep[2])) # Verify non-lib stuff does not add dependencies if libs: for dep in pkg_requires.difference(_essential_dependencies): if dep.find(".so.") != -1 and not dep in libs and not dep in libs_needed: printError(pkg, "shlib-policy-excessive-dependency", dep) # Check for non-versioned directories beyond sysdirs in package sysdirs = ["/lib", "/lib64", "/usr/lib", "/usr/lib64", "/usr/share/doc/packages", "/usr/share"] cdirs = set() for sysdir in sysdirs: done = set() for dir in dirs: if dir.startswith(sysdir + "/"): ssdir = string.split(dir[len(sysdir) + 1 :], "/")[0] if not ssdir[-1].isdigit(): cdirs.add(sysdir + "/" + ssdir) done.add(dir) dirs = dirs.difference(done) map(lambda dir: printError(pkg, "shlib-policy-nonversioned-dir", dir), cdirs)
def check_spec(self, pkg, spec_file): self._spec_file = spec_file spec_only = isinstance(pkg, Pkg.FakePkg) patches = {} applied_patches = [] applied_patches_ifarch = [] patches_auto_applied = False source_dir = False buildroot = False configure_linenum = None configure_cmdline = "" mklibname = False is_lib_pkg = False if_depth = 0 ifarch_depth = -1 current_section = 'package' buildroot_clean = {'clean': False, 'install': False} depscript_override = False depgen_disabled = False patch_fuzz_override = False indent_spaces = 0 indent_tabs = 0 files_has_defattr = False section = {} # None == main package current_package = None package_noarch = {} is_utf8 = False if self._spec_file and use_utf8: if Pkg.is_utf8(self._spec_file): is_utf8 = True else: printError(pkg, "non-utf8-spec-file", self._spec_file) # gather info from spec lines pkg.current_linenum = 0 nbsp = chr(0xA0) if is_utf8: nbsp = UNICODE_NBSP do_unicode = is_utf8 and sys.version_info[0] <= 2 for line in Pkg.readlines(spec_file): pkg.current_linenum += 1 if do_unicode: line = unicode(line, "utf-8", "replace") # noqa false positive char = line.find(nbsp) if char != -1: printWarning(pkg, "non-break-space", "line %s, char %d" % (pkg.current_linenum, char)) section_marker = False for sec, regex in section_regexs.items(): res = regex.search(line) if res: current_section = sec section_marker = True section[sec] = section.get(sec, 0) + 1 if sec in ('package', 'files'): rest = filelist_regex.sub('', line[res.end() - 1:]) res = pkgname_regex.search(rest) if res: current_package = res.group(1) else: current_package = None break if section_marker: if current_section == 'files': files_has_defattr = False if not is_lib_pkg and lib_package_regex.search(line): is_lib_pkg = True continue if current_section in ('prep', 'build') and \ contains_buildroot(line): printWarning(pkg, 'rpm-buildroot-usage', '%' + current_section, line[:-1].strip()) if make_check_regex.search(line) and current_section not in \ ('check', 'changelog', 'package', 'description'): printWarning(pkg, 'make-check-outside-check-section', line[:-1]) if current_section in buildroot_clean and \ not buildroot_clean[current_section] and \ contains_buildroot(line) and rm_regex.search(line): buildroot_clean[current_section] = True if ifarch_regex.search(line): if_depth = if_depth + 1 ifarch_depth = if_depth if if_regex.search(line): if_depth = if_depth + 1 if setup_regex.match(line): if not setup_q_regex.search(line): # Don't warn if there's a -T without -a or -b if setup_t_regex.search(line): if setup_ab_regex.search(line): printWarning(pkg, 'setup-not-quiet') else: printWarning(pkg, 'setup-not-quiet') if current_section != 'prep': printWarning(pkg, 'setup-not-in-prep') elif autopatch_regex.search(line): patches_auto_applied = True if current_section != 'prep': printWarning(pkg, '%autopatch-not-in-prep') else: res = autosetup_regex.search(line) if res: if not autosetup_n_regex.search(res.group(1)): patches_auto_applied = True if current_section != 'prep': printWarning(pkg, '%autosetup-not-in-prep') if endif_regex.search(line): if ifarch_depth == if_depth: ifarch_depth = -1 if_depth = if_depth - 1 res = applied_patch_regex.search(line) if res: pnum = res.group(1) or 0 for tmp in applied_patch_p_regex.findall(line) or [pnum]: pnum = int(tmp) applied_patches.append(pnum) if ifarch_depth > 0: applied_patches_ifarch.append(pnum) else: res = applied_patch_pipe_regex.search(line) if res: pnum = int(res.group(1)) applied_patches.append(pnum) if ifarch_depth > 0: applied_patches_ifarch.append(pnum) if not res and not source_dir: res = source_dir_regex.search(line) if res: source_dir = True printError(pkg, "use-of-RPM_SOURCE_DIR") if configure_linenum: if configure_cmdline[-1] == "\\": configure_cmdline = configure_cmdline[:-1] + line.strip() else: res = configure_libdir_spec_regex.search(configure_cmdline) if not res: # Hack to get the correct (start of ./configure) line # number displayed: real_linenum = pkg.current_linenum pkg.current_linenum = configure_linenum printWarning(pkg, "configure-without-libdir-spec") pkg.current_linenum = real_linenum elif res.group(1): res = re.match(hardcoded_library_paths, res.group(1)) if res: printError(pkg, "hardcoded-library-path", res.group(1), "in configure options") configure_linenum = None hashPos = line.find("#") if current_section != 'changelog': cfgPos = line.find('./configure') if cfgPos != -1 and (hashPos == -1 or hashPos > cfgPos): # store line where it started configure_linenum = pkg.current_linenum configure_cmdline = line.strip() res = hardcoded_library_path_regex.search(line) if current_section != 'changelog' and res and not \ (biarch_package_regex.match(pkg.name) or hardcoded_lib_path_exceptions_regex.search( res.group(1).lstrip())): printError(pkg, "hardcoded-library-path", "in", res.group(1).lstrip()) if '%mklibname' in line: mklibname = True if current_section == 'package': # Would be cleaner to get sources and patches from the # specfile parsed in Python (see below), but we want to # catch %ifarch'd etc ones as well, and also catch these when # the specfile is not parseable. res = patch_regex.search(line) if res: pnum = int(res.group(1) or 0) patches[pnum] = res.group(2) res = obsolete_tags_regex.search(line) if res: printWarning(pkg, "obsolete-tag", res.group(1)) res = buildroot_regex.search(line) if res: buildroot = True if res.group(1).startswith('/'): printWarning(pkg, 'hardcoded-path-in-buildroot-tag', res.group(1)) res = buildarch_regex.search(line) if res: if res.group(1) != "noarch": printError(pkg, 'buildarch-instead-of-exclusivearch-tag', res.group(1)) else: package_noarch[current_package] = True res = packager_regex.search(line) if res: printWarning(pkg, 'hardcoded-packager-tag', res.group(1)) res = prefix_regex.search(line) if res: if not res.group(1).startswith('%'): printWarning(pkg, 'hardcoded-prefix-tag', res.group(1)) res = prereq_regex.search(line) if res: printError(pkg, 'prereq-use', res.group(2)) res = buildprereq_regex.search(line) if res: printError(pkg, 'buildprereq-use', res.group(1)) if scriptlet_requires_regex.search(line): printError(pkg, 'broken-syntax-in-scriptlet-requires', line.strip()) res = requires_regex.search(line) if res: reqs = Pkg.parse_deps(res.group(1)) for req in unversioned(reqs): if compop_regex.search(req): printWarning(pkg, 'comparison-operator-in-deptoken', req) res = provides_regex.search(line) if res: provs = Pkg.parse_deps(res.group(1)) for prov in unversioned(provs): printWarning(pkg, 'unversioned-explicit-provides', prov) if compop_regex.search(prov): printWarning(pkg, 'comparison-operator-in-deptoken', prov) res = obsoletes_regex.search(line) if res: obses = Pkg.parse_deps(res.group(1)) for obs in unversioned(obses): printWarning(pkg, 'unversioned-explicit-obsoletes', obs) if compop_regex.search(obs): printWarning(pkg, 'comparison-operator-in-deptoken', obs) res = conflicts_regex.search(line) if res: confs = Pkg.parse_deps(res.group(1)) for conf in unversioned(confs): if compop_regex.search(conf): printWarning(pkg, 'comparison-operator-in-deptoken', conf) if current_section == 'changelog': for match in AbstractCheck.macro_regex.findall(line): res = re.match('%+', match) if len(res.group(0)) % 2: printWarning(pkg, 'macro-in-%changelog', match) else: if not depscript_override: depscript_override = \ depscript_override_regex.search(line) is not None if not depgen_disabled: depgen_disabled = \ depgen_disable_regex.search(line) is not None if not patch_fuzz_override: patch_fuzz_override = \ patch_fuzz_override_regex.search(line) is not None if current_section == 'files': if not (comment_or_empty_regex.search(line) or ifarch_regex.search(line) or if_regex.search(line) or endif_regex.search(line)): if defattr_regex.search(line): files_has_defattr = True elif not (files_has_defattr or attr_regex.search(line)): printWarning(pkg, 'files-attr-not-set') # TODO: check scriptlets for these too? if package_noarch.get(current_package) or \ (current_package not in package_noarch and package_noarch.get(None)): res = libdir_regex.search(line) if res: pkgname = current_package if pkgname is None: pkgname = '(main package)' printWarning(pkg, 'libdir-macro-in-noarch-package', pkgname, line.rstrip()) if not indent_tabs and '\t' in line: indent_tabs = pkg.current_linenum if not indent_spaces and indent_spaces_regex.search(line): indent_spaces = pkg.current_linenum # Check if egrep or fgrep is used if current_section not in \ ('package', 'changelog', 'description', 'files'): greps = deprecated_grep_regex.findall(line) if greps: printWarning(pkg, "deprecated-grep", greps) # If not checking spec file only, we're checking one inside a # SRPM -> skip this check to avoid duplicate warnings (#167) if spec_only and VALID_GROUPS and \ line.lower().startswith("group:"): group = line[6:].strip() if group not in VALID_GROUPS: printWarning(pkg, 'non-standard-group', group) # Test if there are macros in comments if hashPos != -1 and \ (hashPos == 0 or line[hashPos - 1] in (" ", "\t")): for match in AbstractCheck.macro_regex.findall( line[hashPos + 1:]): res = re.match('%+', match) if len(res.group(0)) % 2: printWarning(pkg, 'macro-in-comment', match) # Last line read is not useful after this point pkg.current_linenum = None for sect in (x for x in buildroot_clean if not buildroot_clean[x]): printWarning(pkg, 'no-cleaning-of-buildroot', '%' + sect) if not buildroot: printWarning(pkg, 'no-buildroot-tag') for sec in ('prep', 'build', 'install', 'clean'): if not section.get(sec): printWarning(pkg, 'no-%%%s-section' % sec) for sec in ('changelog',): # prep, build, install, clean, check prevented by rpmbuild 4.4 if section.get(sec, 0) > 1: printWarning(pkg, 'more-than-one-%%%s-section' % sec) if is_lib_pkg and not mklibname: printError(pkg, 'lib-package-without-%mklibname') if depscript_override and not depgen_disabled: printWarning(pkg, 'depscript-without-disabling-depgen') if patch_fuzz_override: printWarning(pkg, 'patch-fuzz-is-changed') if indent_spaces and indent_tabs: pkg.current_linenum = max(indent_spaces, indent_tabs) printWarning(pkg, 'mixed-use-of-spaces-and-tabs', '(spaces: line %d, tab: line %d)' % (indent_spaces, indent_tabs)) pkg.current_linenum = None # process gathered info if not patches_auto_applied: for pnum, pfile in patches.items(): if pnum in applied_patches_ifarch: printWarning(pkg, "%ifarch-applied-patch", "Patch%d:" % pnum, pfile) if pnum not in applied_patches: printWarning(pkg, "patch-not-applied", "Patch%d:" % pnum, pfile) # Rest of the checks require a real spec file if not self._spec_file: return # We'd like to parse the specfile only once using python bindings, # but it seems errors from rpmlib get logged to stderr and we can't # capture and print them nicely, so we do it once each way :P out = Pkg.getstatusoutput(('env', 'LC_ALL=C', 'rpm', '-q', '--qf=', '--specfile', self._spec_file)) parse_error = False for line in out[1].splitlines(): # No such file or dir hack: https://bugzilla.redhat.com/487855 if 'No such file or directory' not in line: parse_error = True printError(pkg, 'specfile-error', line) if not parse_error: # grab sources and patches from parsed spec object to get # them with macros expanded for URL checking spec_obj = None try: ts = rpm.TransactionSet() spec_obj = ts.parseSpec(self._spec_file) except: # errors logged above already pass if spec_obj: try: # rpm < 4.8.0 sources = spec_obj.sources() except TypeError: # rpm >= 4.8.0 sources = spec_obj.sources for src in sources: (url, num, flags) = src (scheme, netloc) = urlparse(url)[0:2] if flags & 1: # rpmspec.h, rpm.org ticket #123 srctype = "Source" else: srctype = "Patch" tag = '%s%s' % (srctype, num) if scheme and netloc: info = self.check_url(pkg, tag, url) if not info or not hasattr(pkg, 'files'): continue clen = info.get("Content-Length") if clen is not None: clen = int(clen) cmd5 = info.get("Content-MD5") if cmd5 is not None: cmd5 = cmd5.lower() if clen is not None or cmd5 is not None: # Not using path from urlparse results to match how # rpm itself parses the basename. pkgfile = pkg.files().get(url.split("/")[-1]) if pkgfile: if clen is not None and pkgfile.size != clen: printWarning(pkg, 'file-size-mismatch', '%s = %s, %s = %s' % (pkgfile.name, pkgfile.size, url, clen)) # pkgfile.md5 could be some other digest than # MD5, treat as MD5 only if it's 32 chars long if cmd5 and len(pkgfile.md5) == 32 \ and pkgfile.md5 != cmd5: printWarning(pkg, 'file-md5-mismatch', '%s = %s, %s = %s' % (pkgfile.name, pkgfile.md5, url, cmd5)) elif srctype == "Source" and tarball_regex.search(url): printWarning(pkg, 'invalid-url', '%s:' % tag, url)
def check_binary(self, pkg): initscript_list = [] for fname, pkgfile in pkg.files().items(): if not fname.startswith('/etc/init.d/') and \ not fname.startswith('/etc/rc.d/init.d/'): continue basename = os.path.basename(fname) initscript_list.append(basename) if pkgfile.mode & 0o500 != 0o500: printError(pkg, 'init-script-non-executable', fname) if "." in basename: printError(pkg, 'init-script-name-with-dot', fname) # check chkconfig call in %post and %preun postin = pkg[rpm.RPMTAG_POSTIN] or \ pkg.scriptprog(rpm.RPMTAG_POSTINPROG) if not postin: printError(pkg, 'init-script-without-chkconfig-postin', fname) elif not chkconfig_regex.search(postin): printError(pkg, 'postin-without-chkconfig', fname) preun = pkg[rpm.RPMTAG_PREUN] or \ pkg.scriptprog(rpm.RPMTAG_PREUNPROG) if not preun: printError(pkg, 'init-script-without-chkconfig-preun', fname) elif not chkconfig_regex.search(preun): printError(pkg, 'preun-without-chkconfig', fname) status_found = False reload_found = False chkconfig_content_found = False subsys_regex_found = False in_lsb_tag = False in_lsb_description = False lastline = '' lsb_tags = {} # check common error in file content content = None try: content = [x for x in Pkg.readlines(pkgfile.path)] except Exception as e: printWarning(pkg, 'read-error', e) continue content_str = "".join(content) for line in content: line = line[:-1] # chomp # TODO check if there is only one line like this if line.startswith('### BEGIN INIT INFO'): in_lsb_tag = True continue if line.endswith('### END INIT INFO'): in_lsb_tag = False for kw, vals in lsb_tags.items(): if len(vals) != 1: printError(pkg, 'redundant-lsb-keyword', kw) for kw in RECOMMENDED_LSB_KEYWORDS: if kw not in lsb_tags: printWarning(pkg, 'missing-lsb-keyword', "%s in %s" % (kw, fname)) if in_lsb_tag: # TODO maybe we do not have to handle this ? if lastline.endswith('\\'): line = lastline + line else: res = lsb_tags_regex.search(line) if not res: cres = lsb_cont_regex.search(line) if not (in_lsb_description and cres): in_lsb_description = False printError( pkg, 'malformed-line-in-lsb-comment-block', line) else: lsb_tags["Description"][-1] += \ " " + cres.group(1) else: tag = res.group(1) if not tag.startswith('X-') and \ tag not in LSB_KEYWORDS: printError(pkg, 'unknown-lsb-keyword', line) else: in_lsb_description = (tag == 'Description') if tag not in lsb_tags: lsb_tags[tag] = [] lsb_tags[tag].append(res.group(2)) lastline = line if not status_found and status_regex.search(line): status_found = True if not reload_found and reload_regex.search(line): reload_found = True res = chkconfig_content_regex.search(line) if res: chkconfig_content_found = True if use_deflevels: if res.group(1) == '-': printWarning(pkg, 'no-default-runlevel', fname) elif res.group(1) != '-': printWarning(pkg, 'service-default-enabled', fname) res = subsys_regex.search(line) if res: subsys_regex_found = True name = res.group(1) if use_subsys and name != basename: error = True if name[0] == '$': value = Pkg.substitute_shell_vars( name, content_str) if value == basename: error = False else: i = name.find('}') if i != -1: name = name[0:i] error = name != basename if error and len(name): if name[0] == '$': printWarning(pkg, 'incoherent-subsys', fname, name) else: printError(pkg, 'incoherent-subsys', fname, name) if "Default-Start" in lsb_tags: if "".join(lsb_tags["Default-Start"]): printWarning(pkg, 'service-default-enabled', fname) if not status_found: printError(pkg, 'no-status-entry', fname) if not reload_found: printWarning(pkg, 'no-reload-entry', fname) if not chkconfig_content_found: printError(pkg, 'no-chkconfig-line', fname) if not subsys_regex_found and use_subsys: printError(pkg, 'subsys-not-used', fname) elif subsys_regex_found and not use_subsys: printError(pkg, 'subsys-unsupported', fname) if len(initscript_list) == 1: pkgname = re.sub("-sysvinit$", "", pkg.name.lower()) goodnames = (pkgname, pkgname + 'd') if initscript_list[0] not in goodnames: printWarning(pkg, 'incoherent-init-script-name', initscript_list[0], str(goodnames))
def check_binary(self, pkg): files = pkg.files() exec_files = [] has_lib = False version = None binary = False binary_in_usr_lib = False has_usr_lib_file = False multi_pkg = False srpm = pkg[rpm.RPMTAG_SOURCERPM] if srpm: res = srcname_regex.search(Pkg.b2s(srpm)) if res: multi_pkg = (pkg.name != res.group(1)) for fname, pkgfile in files.items(): if not stat.S_ISDIR(pkgfile.mode) and usr_lib_regex.search(fname): has_usr_lib_file = True if not binary_in_usr_lib and \ usr_lib_exception_regex.search(fname): # Fake that we have binaries there to avoid # only-non-binary-in-usr-lib false positives binary_in_usr_lib = True is_elf = 'ELF' in pkgfile.magic is_ar = 'current ar archive' in pkgfile.magic is_ocaml_native = 'Objective caml native' in pkgfile.magic is_lua_bytecode = 'Lua bytecode' in pkgfile.magic is_binary = is_elf or is_ar or is_ocaml_native or is_lua_bytecode if not is_binary: if reference_regex.search(fname): lines = pkg.grep(invalid_dir_ref_regex, fname) if lines: printError(pkg, 'invalid-directory-reference', fname, '(line %s)' % ", ".join(lines)) continue # binary files only from here on binary = True if has_usr_lib_file and not binary_in_usr_lib and \ usr_lib_regex.search(fname): binary_in_usr_lib = True if pkg.arch == 'noarch': printError( pkg, 'arch-independent-package-contains-binary-or-object', fname) continue # arch dependent packages only from here on # in /usr/share ? if fname.startswith('/usr/share/'): printError(pkg, 'arch-dependent-file-in-usr-share', fname) # in /etc ? if fname.startswith('/etc/'): printError(pkg, 'binary-in-etc', fname) if pkg.arch == 'sparc' and sparc_regex.search(pkgfile.magic): printError(pkg, 'non-sparc32-binary', fname) if is_ocaml_native or is_lua_bytecode or fname.endswith('.o') or \ fname.endswith('.static'): continue # stripped ? if 'not stripped' in pkgfile.magic: printWarning(pkg, 'unstripped-binary-or-object', fname) # inspect binary file is_shlib = so_regex.search(fname) bin_info = BinaryInfo(pkg, pkgfile.path, fname, is_ar, is_shlib) if is_shlib: has_lib = True # shared libs if is_shlib and not bin_info.readelf_error: # so name in library if not bin_info.soname: printWarning(pkg, 'no-soname', fname) else: if not validso_regex.search(bin_info.soname): printError(pkg, 'invalid-soname', fname, bin_info.soname) else: (directory, base) = dir_base(fname) try: symlink = directory + bin_info.soname link = files[symlink].linkto if link not in (fname, base, ''): printError(pkg, 'invalid-ldconfig-symlink', fname, link) except KeyError: if base.startswith("lib") or \ base.startswith("ld-"): printError(pkg, 'no-ldconfig-symlink', fname) res = soversion_regex.search(bin_info.soname) if res: soversion = res.group(1) or res.group(2) if version is None: version = soversion elif version != soversion: version = -1 if bin_info.non_pic: printError(pkg, 'shlib-with-non-pic-code', fname) # It could be useful to check these for others than shared # libs only, but that has potential to generate lots of # false positives and noise. for s in bin_info.undef: printWarning(pkg, 'undefined-non-weak-symbol', fname, s) for s in bin_info.unused: printWarning(pkg, 'unused-direct-shlib-dependency', fname, s) # calls exit() or _exit()? for ec in bin_info.exit_calls: printWarning(pkg, 'shared-lib-calls-exit', fname, ec) # rpath ? if bin_info.rpath: for p in bin_info.rpath: if p in system_lib_paths or not usr_lib_regex.search(p): printError(pkg, 'binary-or-shlib-defines-rpath', fname, bin_info.rpath) break is_exec = 'executable' in pkgfile.magic is_shobj = 'shared object' in pkgfile.magic if not is_exec and not is_shobj: continue if is_shobj and not is_exec and '.so' not in fname and \ bin_regex.search(fname): # pkgfile.magic does not contain "executable" for PIEs is_exec = True if is_exec: if bin_regex.search(fname): exec_files.append(fname) if ocaml_mixed_regex.search(bin_info.tail): printWarning(pkg, 'ocaml-mixed-executable', fname) if not is_shobj and pie_exec_re and pie_exec_re.search(fname): printError(pkg, 'non-position-independent-executable', fname) if bin_info.readelf_error: continue if not bin_info.needed and not (bin_info.soname and ldso_soname_regex.search( bin_info.soname)): if is_shobj: printError(pkg, 'shared-lib-without-dependency-information', fname) else: printError(pkg, 'statically-linked-binary', fname) else: # linked against libc ? if "libc." not in fname and \ (not bin_info.soname or ("libc." not in bin_info.soname and not ldso_soname_regex.search(bin_info.soname))): found_libc = False for lib in bin_info.needed: if "libc." in lib: found_libc = True break if not found_libc: if is_shobj: printError(pkg, 'library-not-linked-against-libc', fname) else: printError(pkg, 'program-not-linked-against-libc', fname) if bin_info.stack: if bin_info.exec_stack: printWarning(pkg, 'executable-stack', fname) elif not bin_info.readelf_error and ( pkg.arch.endswith("86") or pkg.arch.startswith("pentium") or pkg.arch in ("athlon", "x86_64")): printError(pkg, 'missing-PT_GNU_STACK-section', fname) if bin_info.setgid and bin_info.setuid and not bin_info.setgroups: printError(pkg, 'missing-call-to-setgroups-before-setuid', fname) if bin_info.chroot: if not bin_info.chdir or not bin_info.chroot_near_chdir: printError(pkg, 'missing-call-to-chdir-with-chroot', fname) if bin_info.mktemp: printError(pkg, 'call-to-mktemp', fname) if has_lib: for f in exec_files: printError(pkg, 'executable-in-library-package', f) for f in files: res = numeric_dir_regex.search(f) fn = res and res.group(1) or f if f not in exec_files and not so_regex.search(f) and \ not versioned_dir_regex.search(fn): printError(pkg, 'non-versioned-file-in-library-package', f) if version and version != -1 and version not in pkg.name: printError(pkg, 'incoherent-version-in-name', version) if not binary and not multi_pkg and pkg.arch != 'noarch': printError(pkg, 'no-binary') if has_usr_lib_file and not binary_in_usr_lib: printWarning(pkg, 'only-non-binary-in-usr-lib')
def check_binary(self, pkg): initscript_list = [] for fname, pkgfile in pkg.files().items(): if not fname.startswith('/etc/init.d/') and \ not fname.startswith('/etc/rc.d/init.d/'): continue basename = os.path.basename(fname) initscript_list.append(basename) if pkgfile.mode & 0o500 != 0o500: printError(pkg, 'init-script-non-executable', fname) if "." in basename: printError(pkg, 'init-script-name-with-dot', fname) # check chkconfig call in %post and %preun postin = pkg[rpm.RPMTAG_POSTIN] or \ pkg.scriptprog(rpm.RPMTAG_POSTINPROG) if not postin: printError(pkg, 'init-script-without-chkconfig-postin', fname) elif not chkconfig_regex.search(postin): printError(pkg, 'postin-without-chkconfig', fname) preun = pkg[rpm.RPMTAG_PREUN] or \ pkg.scriptprog(rpm.RPMTAG_PREUNPROG) if not preun: printError(pkg, 'init-script-without-chkconfig-preun', fname) elif not chkconfig_regex.search(preun): printError(pkg, 'preun-without-chkconfig', fname) status_found = False reload_found = False chkconfig_content_found = False subsys_regex_found = False in_lsb_tag = False in_lsb_description = False lastline = '' lsb_tags = {} # check common error in file content content = None try: content = [x for x in Pkg.readlines(pkgfile.path)] except Exception as e: printWarning(pkg, 'read-error', e) continue content_str = "".join(content) for line in content: line = line[:-1] # chomp # TODO check if there is only one line like this if line.startswith('### BEGIN INIT INFO'): in_lsb_tag = True continue if line.endswith('### END INIT INFO'): in_lsb_tag = False for kw, vals in lsb_tags.items(): if len(vals) != 1: printError(pkg, 'redundant-lsb-keyword', kw) for kw in RECOMMENDED_LSB_KEYWORDS: if kw not in lsb_tags: printWarning(pkg, 'missing-lsb-keyword', "%s in %s" % (kw, fname)) if in_lsb_tag: # TODO maybe we do not have to handle this ? if lastline.endswith('\\'): line = lastline + line else: res = lsb_tags_regex.search(line) if not res: cres = lsb_cont_regex.search(line) if not (in_lsb_description and cres): in_lsb_description = False printError( pkg, 'malformed-line-in-lsb-comment-block', line) else: lsb_tags["Description"][-1] += \ " " + cres.group(1) else: tag = res.group(1) if not tag.startswith('X-') and \ tag not in LSB_KEYWORDS: printError(pkg, 'unknown-lsb-keyword', line) else: in_lsb_description = (tag == 'Description') if tag not in lsb_tags: lsb_tags[tag] = [] lsb_tags[tag].append(res.group(2)) lastline = line if not status_found and status_regex.search(line): status_found = True if not reload_found and reload_regex.search(line): reload_found = True res = chkconfig_content_regex.search(line) if res: chkconfig_content_found = True if use_deflevels: if res.group(1) == '-': printWarning(pkg, 'no-default-runlevel', fname) elif res.group(1) != '-': printWarning(pkg, 'service-default-enabled', fname) res = subsys_regex.search(line) if res: subsys_regex_found = True name = res.group(1) if use_subsys and name != basename: error = True if name[0] == '$': value = Pkg.substitute_shell_vars(name, content_str) if value == basename: error = False else: i = name.find('}') if i != -1: name = name[0:i] error = name != basename if error and len(name): if name[0] == '$': printWarning(pkg, 'incoherent-subsys', fname, name) else: printError(pkg, 'incoherent-subsys', fname, name) if "Default-Start" in lsb_tags: if "".join(lsb_tags["Default-Start"]): printWarning(pkg, 'service-default-enabled', fname) if not status_found: printError(pkg, 'no-status-entry', fname) if not reload_found: printWarning(pkg, 'no-reload-entry', fname) if not chkconfig_content_found: printError(pkg, 'no-chkconfig-line', fname) if not subsys_regex_found and use_subsys: printError(pkg, 'subsys-not-used', fname) elif subsys_regex_found and not use_subsys: printError(pkg, 'subsys-unsupported', fname) if len(initscript_list) == 1: pkgname = re.sub("-sysvinit$", "", pkg.name.lower()) goodnames = (pkgname, pkgname + 'd') if initscript_list[0] not in goodnames: printWarning(pkg, 'incoherent-init-script-name', initscript_list[0], str(goodnames))
def check_binary(self, pkg): files = pkg.files() menus = [] for fname, pkgfile in files.items(): # Check menu files res = menu_file_regex.search(fname) mode = pkgfile.mode if res: basename = res.group(1) if not stat.S_ISREG(mode): printError(pkg, 'non-file-in-menu-dir', fname) else: if basename != pkg.name: printWarning(pkg, 'non-coherent-menu-filename', fname) if mode & 0o444 != 0o444: printError(pkg, 'non-readable-menu-file', fname) if mode & 0o111: printError(pkg, 'executable-menu-file', fname) menus.append(fname) else: # Check old menus from KDE and GNOME res = old_menu_file_regex.search(fname) if res: if stat.S_ISREG(mode): printError(pkg, 'old-menu-entry', fname) else: # Check non transparent xpm files res = xpm_ext_regex.search(fname) if res: if stat.S_ISREG(mode) and not pkg.grep('None",', fname): printWarning(pkg, 'non-transparent-xpm', fname) if fname.startswith('/usr/lib64/menu'): printError(pkg, 'menu-in-wrong-dir', fname) if menus: postin = pkg[rpm.RPMTAG_POSTIN] or \ pkg.scriptprog(rpm.RPMTAG_POSTINPROG) if not postin: printError(pkg, 'menu-without-postin') elif not update_menus_regex.search(postin): printError(pkg, 'postin-without-update-menus') postun = pkg[rpm.RPMTAG_POSTUN] or \ pkg.scriptprog(rpm.RPMTAG_POSTUNPROG) if not postun: printError(pkg, 'menu-without-postun') elif not update_menus_regex.search(postun): printError(pkg, 'postun-without-update-menus') directory = pkg.dirName() for f in menus: # remove comments and handle cpp continuation lines cmd = Pkg.getstatusoutput(('/lib/cpp', directory + f), True)[1] for line in cmd.splitlines(): if not line.startswith('?'): continue res = package_regex.search(line) if res: package = res.group(1) if package != pkg.name: printWarning(pkg, 'incoherent-package-value-in-menu', package, f) else: printInfo(pkg, 'unable-to-parse-menu-entry', line) command = True res = command_regex.search(line) if res: command_line = (res.group(1) or res.group(2)).split() command = command_line[0] for launcher in launchers: if not launcher[0].search(command): continue found = False if launcher[1]: found = '/bin/' + command_line[0] in files or \ '/usr/bin/' + command_line[0] in files or \ '/usr/X11R6/bin/' + command_line[0] \ in files if not found: for l in launcher[1]: if l in pkg.req_names(): found = True break if not found: printError(pkg, 'use-of-launcher-in-menu-but-no-requires-on', launcher[1][0]) command = command_line[1] break if command[0] == '/': if command not in files: printWarning( pkg, 'menu-command-not-in-package', command) elif not ('/bin/' + command in files or '/usr/bin/' + command in files or '/usr/X11R6/bin/' + command in files): printWarning(pkg, 'menu-command-not-in-package', command) else: printWarning(pkg, 'missing-menu-command') command = False res = longtitle_regex.search(line) if res: grp = res.groups() title = grp[1] or grp[2] if title[0] != title[0].upper(): printWarning(pkg, 'menu-longtitle-not-capitalized', title) res = version_regex.search(title) if res: printWarning(pkg, 'version-in-menu-longtitle', title) else: printError(pkg, 'no-longtitle-in-menu', f) title = None res = title_regex.search(line) if res: grp = res.groups() title = grp[1] or grp[2] if title[0] != title[0].upper(): printWarning(pkg, 'menu-title-not-capitalized', title) res = version_regex.search(title) if res: printWarning(pkg, 'version-in-menu-title', title) if '/' in title: printError(pkg, 'invalid-title', title) else: printError(pkg, 'no-title-in-menu', f) title = None res = needs_regex.search(line) if res: grp = res.groups() needs = (grp[1] or grp[2]).lower() if needs in ('x11', 'text', 'wm'): res = section_regex.search(line) if res: grp = res.groups() section = grp[1] or grp[2] # don't warn entries for sections if command and section not in valid_sections: printError(pkg, 'invalid-menu-section', section, f) else: printInfo(pkg, 'unable-to-parse-menu-section', line) elif needs not in standard_needs: printInfo(pkg, 'strange-needs', needs, f) else: printInfo(pkg, 'unable-to-parse-menu-needs', line) res = icon_regex.search(line) if res: icon = res.group(1) if not icon_ext_regex.search(icon): printWarning(pkg, 'invalid-menu-icon-type', icon) if icon[0] == '/' and needs == 'x11': printWarning(pkg, 'hardcoded-path-in-menu-icon', icon) else: for path in icon_paths: if (path[0] + icon) not in files: printError( pkg, path[1] + '-icon-not-in-package', icon, f) else: printWarning(pkg, 'no-icon-in-menu', title) res = xdg_migrated_regex.search(line) if res: if not res.group(1).lower() == "true": printError(pkg, 'non-xdg-migrated-menu') else: printError(pkg, 'non-xdg-migrated-menu')
def check_file(self, pkg, filename): lines = pkg.grep(pam_stack_re, filename) if lines: printError(pkg, 'use-old-pam-stack', filename, '(line %s)' % ", ".join(lines))