def check_file(self, pkg, filename): try: f = open(filename) except: return try: first_line = f.read(256).split("\n")[0] if self.RE_BIN_SH.match(first_line): status, output = Pkg.getstatusoutput(["dash", "-n", filename]) if status == 2: printWarning(pkg, "bin-sh-syntax-error", filename) try: status, output = Pkg.getstatusoutput( ["checkbashisms", filename]) if status == 1: printInfo(pkg, "potential-bashisms", filename) except Exception as x: printError( pkg, 'rpmlint-exception', "%(file)s raised an exception: %(x)s" % { 'file': filename, 'x': x }) except UnicodeDecodeError: pass finally: f.close()
def check(self, pkg): if pkg.isSource(): return files = pkg.files() for fname, pkgfile in files.items(): if pkgfile.is_ghost: continue if fname.startswith('/usr/lib/debug') or \ not stat.S_ISREG(pkgfile.mode) or \ not pkgfile.magic.startswith('ELF '): continue ret, output = Pkg.getstatusoutput( ['ldd', '-r', '-u', pkgfile.path]) for l in output.split('\n'): l = l.lstrip() if not l.startswith('/'): continue lib = l.rsplit('/')[-1] if lib in ('libdl.so.2', 'libm.so.6', 'libpthread.so.0'): continue printError(pkg, 'elf-binary-unused-dependency', fname, lib)
def check(self, pkg): if pkg.isSource(): return; files = pkg.files() for fname, pkgfile in files.items(): if pkgfile.is_ghost: continue if fname.startswith('/usr/lib/debug') or \ not stat.S_ISREG(pkgfile.mode) or \ not pkgfile.magic.startswith('ELF '): continue ret, output = Pkg.getstatusoutput(['ldd', '-r', '-u', pkgfile.path]) for l in output.split('\n'): l = l.lstrip() if not l.startswith('/'): continue lib = l.rsplit('/')[-1] if lib in ('libdl.so.2', 'libm.so.6', 'libpthread.so.0'): continue printError(pkg, 'elf-binary-unused-dependency', fname, lib)
def check_file(self, pkg, filename): pkgfile = pkg.files()[filename] if not (stat.S_ISREG(pkgfile.mode) and pkgfile.magic.startswith('POSIX shell script')): return try: status, output = Pkg.getstatusoutput(["dash", "-n", filename]) if status == 2: printWarning(pkg, "bin-sh-syntax-error", filename) status, output = Pkg.getstatusoutput( ["checkbashisms", filename]) if status == 1: printInfo(pkg, "potential-bashisms", filename) except (FileNotFoundError, UnicodeDecodeError): pass
def check_file(self, pkg, filename): try: f = open(filename) except: return try: first_line = f.read(256).split("\n")[0] if self.RE_BIN_SH.match(first_line): status, output = Pkg.getstatusoutput(["dash", "-n", filename]) if status == 2: printWarning(pkg, "bin-sh-syntax-error", filename) try: status, output = Pkg.getstatusoutput(["checkbashisms", filename]) if status == 1: printInfo(pkg, "potential-bashisms", filename) except Exception as x: printError(pkg, 'rpmlint-exception', "%(file)s raised an exception: %(x)s" % {'file':filename, 'x':x}) finally: f.close()
def check_syntax_script(prog, commandline, script): if not script: return False # TODO: test that "prog" is available/executable tmpfile, tmpname = Pkg.mktemp() try: tmpfile.write(script) tmpfile.close() ret = Pkg.getstatusoutput((prog, commandline, tmpname)) finally: tmpfile.close() os.remove(tmpname) return ret[0]
def check_syntax_script(prog, commandline, script): if not script: return False # TODO: test that "prog" is available/executable tmpfd, tmpname = tempfile.mkstemp(prefix='rpmlint.') tmpfile = os.fdopen(tmpfd, 'wb') try: tmpfile.write(script) tmpfile.close() ret = Pkg.getstatusoutput((prog, commandline, tmpname)) finally: tmpfile.close() os.remove(tmpname) return ret[0]
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_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_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 __init__(self, pkg, path, file, is_ar, is_shlib): self.readelf_error = False self.needed = [] self.rpath = [] self.undef = [] self.unused = [] self.comment = False self.soname = False self.non_pic = True self.stack = False self.exec_stack = False self.exit_calls = [] self.forbidden_calls = [] fork_called = False self.tail = '' self.setgid = False self.setuid = False self.setgroups = False self.chroot = False self.chdir = False self.chroot_near_chdir = False self.mktemp = False is_debug = path.endswith('.debug') cmd = ['env', 'LC_ALL=C', 'readelf', '-W', '-S', '-l', '-d', '-s'] cmd.append(path) res = Pkg.getstatusoutput(cmd) if not res[0]: lines = res[1].splitlines() for l in lines: r = BinaryInfo.needed_regex.search(l) if r: self.needed.append(r.group(1)) continue r = BinaryInfo.rpath_regex.search(l) if r: for p in r.group(1).split(':'): self.rpath.append(p) continue if BinaryInfo.comment_regex.search(l): self.comment = True continue if BinaryInfo.pic_regex.search(l): self.non_pic = False continue r = BinaryInfo.soname_regex.search(l) if r: self.soname = r.group(1) continue r = BinaryInfo.stack_regex.search(l) if r: self.stack = True flags = r.group(1) if flags and BinaryInfo.stack_exec_regex.search(flags): self.exec_stack = True continue if l.startswith("Symbol table"): break for l in lines: r = BinaryInfo.call_regex.search(l) if not r: continue l = r.group(1) if BinaryInfo.mktemp_call_regex.search(l): self.mktemp = True if BinaryInfo.setgid_call_regex.search(l): self.setgid = True if BinaryInfo.setuid_call_regex.search(l): self.setuid = True if BinaryInfo.setgroups_call_regex.search(l): self.setgroups = True if BinaryInfo.chdir_call_regex.search(l): self.chdir = True if BinaryInfo.chroot_call_regex.search(l): self.chroot = True if BinaryInfo.forbidden_functions: for r_name, func in BinaryInfo.forbidden_functions.items(): ret = func['f_regex'].search(l) if ret: self.forbidden_calls.append(r_name) if is_shlib: r = BinaryInfo.exit_call_regex.search(l) if r: self.exit_calls.append(r.group(1)) continue r = BinaryInfo.fork_call_regex.search(l) if r: fork_called = True continue # check if we don't have a string that will automatically # waive the presence of a forbidden call if self.forbidden_calls: cmd = ['env', 'LC_ALL=C', 'strings'] cmd.append(path) res = Pkg.getstatusoutput(cmd) if not res[0]: for l in res[1].splitlines(): # as we need to remove elements, iterate backwards for i in range(len(self.forbidden_calls) - 1, -1, -1): func = self.forbidden_calls[i] f = BinaryInfo.forbidden_functions[func] if 'waiver_regex' not in f: continue r = f['waiver_regex'].search(l) if r: del self.forbidden_calls[i] if self.non_pic: self.non_pic = 'TEXTREL' in res[1] # Ignore all exit() calls if fork() is being called. # Does not have any context at all but without this kludge, the # number of false positives would probably be intolerable. if fork_called: self.exit_calls = [] # check if chroot is near chdir (since otherwise, chroot is called # without chdir) # Currently this implementation works only on x86_64 due to reliance # on x86_64 specific assembly. Skip it on other architectures if pkg.arch == 'x86_64' and self.chroot and self.chdir: p = subprocess.Popen( ['env', 'LC_ALL=C', 'objdump', '-d', path], stdout=subprocess.PIPE, bufsize=-1) with p.stdout: index = 0 chroot_index = -99 chdir_index = -99 for line in p.stdout: res = BinaryInfo.objdump_call_regex.search(line) if not res: continue if b'@plt' not in res.group(1): pass elif b'chroot@plt' in res.group(1): chroot_index = index if abs(chroot_index - chdir_index) <= 2: self.chroot_near_chdir = True break elif b'chdir@plt' in res.group(1): chdir_index = index if abs(chroot_index - chdir_index) <= 2: self.chroot_near_chdir = True break index += 1 if p.wait() and not self.chroot_near_chdir: printWarning(pkg, 'binaryinfo-objdump-failed', file) self.chroot_near_chdir = True # avoid false positive else: self.readelf_error = True printWarning(pkg, 'binaryinfo-readelf-failed', file, re.sub('\n.*', '', res[1])) try: with open(path, 'rb') as fobj: fobj.seek(-12, os.SEEK_END) self.tail = Pkg.b2s(fobj.read()) except Exception as e: printWarning(pkg, 'binaryinfo-tail-failed %s: %s' % (file, e)) # Undefined symbol and unused direct dependency checks make sense only # for installed packages. # skip debuginfo: https://bugzilla.redhat.com/190599 if not is_ar and not is_debug and isinstance(pkg, Pkg.InstalledPkg): # We could do this with objdump, but it's _much_ simpler with ldd. res = Pkg.getstatusoutput( ('env', 'LC_ALL=C', 'ldd', '-d', '-r', path)) if not res[0]: for l in res[1].splitlines(): undef = BinaryInfo.undef_regex.search(l) if undef: self.undef.append(undef.group(1)) if self.undef: cmd = self.undef[:] cmd.insert(0, 'c++filt') try: res = Pkg.getstatusoutput(cmd) if not res[0]: self.undef = res[1].splitlines() except: pass else: printWarning(pkg, 'ldd-failed', file) res = Pkg.getstatusoutput( ('env', 'LC_ALL=C', 'ldd', '-r', '-u', path)) if res[0]: # Either ldd doesn't grok -u (added in glibc 2.3.4) or we have # unused direct dependencies in_unused = False for l in res[1].splitlines(): if not l.rstrip(): pass elif l.startswith('Unused direct dependencies'): in_unused = True elif in_unused: unused = BinaryInfo.unused_regex.search(l) if unused: self.unused.append(unused.group(1)) else: in_unused = False
def __init__(self, pkg, path, file, is_ar, is_shlib): self.readelf_error = False self.needed = [] self.rpath = [] self.undef = [] self.unused = [] self.comment = False self.soname = False self.non_pic = True self.stack = False self.exec_stack = False self.exit_calls = [] fork_called = False self.tail = '' self.setgid = False self.setuid = False self.setgroups = False self.chroot = False self.chdir = False self.chroot_near_chdir = False self.mktemp = False is_debug = path.endswith('.debug') cmd = ['env', 'LC_ALL=C', 'readelf', '-W', '-S', '-l', '-d', '-s'] cmd.append(path) res = Pkg.getstatusoutput(cmd) if not res[0]: for l in res[1].splitlines(): if BinaryInfo.mktemp_call_regex.search(l): self.mktemp = True if BinaryInfo.setgid_call_regex.search(l): self.setgid = True if BinaryInfo.setuid_call_regex.search(l): self.setuid = True if BinaryInfo.setgroups_call_regex.search(l): self.setgroups = True if BinaryInfo.chdir_call_regex.search(l): self.chdir = True if BinaryInfo.chroot_call_regex.search(l): self.chroot = True r = BinaryInfo.needed_regex.search(l) if r: self.needed.append(r.group(1)) continue r = BinaryInfo.rpath_regex.search(l) if r: for p in r.group(1).split(':'): self.rpath.append(p) continue if BinaryInfo.comment_regex.search(l): self.comment = True continue if BinaryInfo.pic_regex.search(l): self.non_pic = False continue r = BinaryInfo.soname_regex.search(l) if r: self.soname = r.group(1) continue r = BinaryInfo.stack_regex.search(l) if r: self.stack = True flags = r.group(1) if flags and BinaryInfo.stack_exec_regex.search(flags): self.exec_stack = True continue if is_shlib: r = BinaryInfo.exit_call_regex.search(l) if r: self.exit_calls.append(r.group(1)) continue r = BinaryInfo.fork_call_regex.search(l) if r: fork_called = True continue if self.non_pic: self.non_pic = 'TEXTREL' in res[1] # Ignore all exit() calls if fork() is being called. # Does not have any context at all but without this kludge, the # number of false positives would probably be intolerable. if fork_called: self.exit_calls = [] # check if chroot is near chdir (since otherwise, chroot is called # without chdir) if self.chroot and self.chdir: # FIXME this check is too slow, because forking for objdump is # quite slow according to a quick test and that's quite visible # on a server like postfix res = Pkg.getstatusoutput( ('env', 'LC_ALL=C', 'objdump', '-d', path)) if not res[0]: call = [] # we want that : # 401eb8: e8 c3 f0 ff ff callq 400f80 <free@plt> for l in res[1].splitlines(): # call is for x86 32 bits, callq for x86_64 if l.find('callq ') >= 0 or l.find('call ') >= 0: call.append(l.rpartition(' ')[2]) for index, c in enumerate(call): if c.find('chroot@plt') >= 0: for i in call[index - 2:index + 2]: if i.find('chdir@plt'): self.chroot_near_chdir = True else: self.readelf_error = True printWarning(pkg, 'binaryinfo-readelf-failed', file, re.sub('\n.*', '', res[1])) fobj = None try: fobj = open(path, 'rb') fobj.seek(-12, os.SEEK_END) self.tail = Pkg.b2s(fobj.read()) except Exception: e = sys.exc_info()[1] printWarning(pkg, 'binaryinfo-tail-failed %s: %s' % (file, e)) if fobj: fobj.close() # Undefined symbol and unused direct dependency checks make sense only # for installed packages. # skip debuginfo: https://bugzilla.redhat.com/190599 if not is_ar and not is_debug and isinstance(pkg, Pkg.InstalledPkg): # We could do this with objdump, but it's _much_ simpler with ldd. res = Pkg.getstatusoutput( ('env', 'LC_ALL=C', 'ldd', '-d', '-r', path)) if not res[0]: for l in res[1].splitlines(): undef = BinaryInfo.undef_regex.search(l) if undef: self.undef.append(undef.group(1)) if self.undef: cmd = self.undef[:] cmd.insert(0, 'c++filt') try: res = Pkg.getstatusoutput(cmd) if not res[0]: self.undef = res[1].splitlines() except: pass else: printWarning(pkg, 'ldd-failed', file) res = Pkg.getstatusoutput( ('env', 'LC_ALL=C', 'ldd', '-r', '-u', path)) if res[0]: # Either ldd doesn't grok -u (added in glibc 2.3.4) or we have # unused direct dependencies in_unused = False for l in res[1].splitlines(): if not l.rstrip(): pass elif l.startswith('Unused direct dependencies'): in_unused = True elif in_unused: unused = BinaryInfo.unused_regex.search(l) if unused: self.unused.append(unused.group(1)) else: in_unused = False
def __init__(self, pkg, path, file, is_ar, is_shlib): self.readelf_error = False self.needed = [] self.rpath = [] self.undef = [] self.unused = [] self.comment = False self.soname = False self.non_pic = True self.stack = False self.exec_stack = False self.exit_calls = [] fork_called = False self.tail = '' self.setgid = False self.setuid = False self.setgroups = False self.chroot = False self.chdir = False self.chroot_near_chdir = False self.mktemp = False is_debug = path.endswith('.debug') cmd = ['env', 'LC_ALL=C', 'readelf', '-W', '-S', '-l', '-d', '-s'] cmd.append(path) res = Pkg.getstatusoutput(cmd) if not res[0]: for l in res[1].splitlines(): if BinaryInfo.mktemp_call_regex.search(l): self.mktemp = True if BinaryInfo.setgid_call_regex.search(l): self.setgid = True if BinaryInfo.setuid_call_regex.search(l): self.setuid = True if BinaryInfo.setgroups_call_regex.search(l): self.setgroups = True if BinaryInfo.chdir_call_regex.search(l): self.chdir = True if BinaryInfo.chroot_call_regex.search(l): self.chroot = True r = BinaryInfo.needed_regex.search(l) if r: self.needed.append(r.group(1)) continue r = BinaryInfo.rpath_regex.search(l) if r: for p in r.group(1).split(':'): self.rpath.append(p) continue if BinaryInfo.comment_regex.search(l): self.comment = True continue if BinaryInfo.pic_regex.search(l): self.non_pic = False continue r = BinaryInfo.soname_regex.search(l) if r: self.soname = r.group(1) continue r = BinaryInfo.stack_regex.search(l) if r: self.stack = True flags = r.group(1) if flags and BinaryInfo.stack_exec_regex.search(flags): self.exec_stack = True continue if is_shlib: r = BinaryInfo.exit_call_regex.search(l) if r: self.exit_calls.append(r.group(1)) continue r = BinaryInfo.fork_call_regex.search(l) if r: fork_called = True continue if self.non_pic: self.non_pic = 'TEXTREL' in res[1] # Ignore all exit() calls if fork() is being called. # Does not have any context at all but without this kludge, the # number of false positives would probably be intolerable. if fork_called: self.exit_calls = [] # check if chroot is near chdir ( since otherwise, chroot is called without chdir ) if self.chroot and self.chdir: # FIXME this check is too slow, because forking for objdump is quite slow # according to a quick test and that's quite visible on a server like postfix res = Pkg.getstatusoutput(('env', 'LC_ALL=C', 'objdump', '-d', path)) if not res[0]: call = [] # we want that : # 401eb8: e8 c3 f0 ff ff callq 400f80 <free@plt> for l in res[1].splitlines(): if l.find('callq ') >= 0: call.append(l.rpartition(' ')[2]) for index,c in enumerate(call): if c.find('chroot@plt') >= 0: for i in call[index-2:index+2]: if i.find('chdir@plt'): self.chroot_near_chdir = True else: self.readelf_error = True printWarning(pkg, 'binaryinfo-readelf-failed', file, re.sub('\n.*', '', res[1])) fobj = None try: fobj = open(path) fobj.seek(-12, 2) # 2 == os.SEEK_END, for python 2.4 compat (#172) self.tail = fobj.read() except Exception, e: printWarning(pkg, 'binaryinfo-tail-failed %s: %s' % (file, e))
def __init__(self, pkg, path, file, is_ar, is_shlib): self.readelf_error = False self.needed = [] self.rpath = [] self.undef = [] self.unused = [] self.comment = False self.soname = False self.non_pic = True self.stack = False self.exec_stack = False self.exit_calls = [] self.forbidden_calls = [] fork_called = False self.tail = '' self.setgid = False self.setuid = False self.setgroups = False self.chroot = False self.chdir = False self.chroot_near_chdir = False self.mktemp = False is_debug = path.endswith('.debug') cmd = ['env', 'LC_ALL=C', 'readelf', '-W', '-S', '-l', '-d', '-s'] cmd.append(path) res = Pkg.getstatusoutput(cmd) if not res[0]: lines = res[1].splitlines() for l in lines: r = BinaryInfo.needed_regex.search(l) if r: self.needed.append(r.group(1)) continue r = BinaryInfo.rpath_regex.search(l) if r: for p in r.group(1).split(':'): self.rpath.append(p) continue if BinaryInfo.comment_regex.search(l): self.comment = True continue if BinaryInfo.pic_regex.search(l): self.non_pic = False continue r = BinaryInfo.soname_regex.search(l) if r: self.soname = r.group(1) continue r = BinaryInfo.stack_regex.search(l) if r: self.stack = True flags = r.group(1) if flags and BinaryInfo.stack_exec_regex.search(flags): self.exec_stack = True continue if l.startswith("Symbol table"): break for l in lines: r = BinaryInfo.call_regex.search(l) if not r: continue l = r.group(1) if BinaryInfo.mktemp_call_regex.search(l): self.mktemp = True if BinaryInfo.setgid_call_regex.search(l): self.setgid = True if BinaryInfo.setuid_call_regex.search(l): self.setuid = True if BinaryInfo.setgroups_call_regex.search(l): self.setgroups = True if BinaryInfo.chdir_call_regex.search(l): self.chdir = True if BinaryInfo.chroot_call_regex.search(l): self.chroot = True if BinaryInfo.forbidden_functions: for r_name, func in BinaryInfo.forbidden_functions.items(): ret = func['f_regex'].search(l) if ret: self.forbidden_calls.append(r_name) if is_shlib: r = BinaryInfo.exit_call_regex.search(l) if r: self.exit_calls.append(r.group(1)) continue r = BinaryInfo.fork_call_regex.search(l) if r: fork_called = True continue # check if we don't have a string that will automatically # waive the presence of a forbidden call if self.forbidden_calls: cmd = ['env', 'LC_ALL=C', 'strings'] cmd.append(path) res = Pkg.getstatusoutput(cmd) if not res[0]: for l in res[1].splitlines(): # as we need to remove elements, iterate backwards for i in range(len(self.forbidden_calls) - 1, -1, -1): func = self.forbidden_calls[i] f = BinaryInfo.forbidden_functions[func] if 'waiver_regex' not in f: continue r = f['waiver_regex'].search(l) if r: del self.forbidden_calls[i] if self.non_pic: self.non_pic = 'TEXTREL' in res[1] # Ignore all exit() calls if fork() is being called. # Does not have any context at all but without this kludge, the # number of false positives would probably be intolerable. if fork_called: self.exit_calls = [] # check if chroot is near chdir (since otherwise, chroot is called # without chdir) if self.chroot and self.chdir: # FIXME this check is too slow, because forking for objdump is # quite slow according to a quick test and that's quite visible # on a server like postfix res = Pkg.getstatusoutput( ('env', 'LC_ALL=C', 'objdump', '-d', path)) if not res[0]: call = [] # we want that : # 401eb8: e8 c3 f0 ff ff callq 400f80 <free@plt> for l in res[1].splitlines(): # call is for x86 32 bits, callq for x86_64 if l.find('callq ') >= 0 or l.find('call ') >= 0: call.append(l.rpartition(' ')[2]) for index, c in enumerate(call): if c.find('chroot@plt') >= 0: for i in call[index - 2:index + 2]: if i.find('chdir@plt'): self.chroot_near_chdir = True else: self.readelf_error = True printWarning(pkg, 'binaryinfo-readelf-failed', file, re.sub('\n.*', '', res[1])) try: with open(path, 'rb') as fobj: fobj.seek(-12, os.SEEK_END) self.tail = Pkg.b2s(fobj.read()) except Exception as e: printWarning(pkg, 'binaryinfo-tail-failed %s: %s' % (file, e)) # Undefined symbol and unused direct dependency checks make sense only # for installed packages. # skip debuginfo: https://bugzilla.redhat.com/190599 if not is_ar and not is_debug and isinstance(pkg, Pkg.InstalledPkg): # We could do this with objdump, but it's _much_ simpler with ldd. res = Pkg.getstatusoutput( ('env', 'LC_ALL=C', 'ldd', '-d', '-r', path)) if not res[0]: for l in res[1].splitlines(): undef = BinaryInfo.undef_regex.search(l) if undef: self.undef.append(undef.group(1)) if self.undef: cmd = self.undef[:] cmd.insert(0, 'c++filt') try: res = Pkg.getstatusoutput(cmd) if not res[0]: self.undef = res[1].splitlines() except: pass else: printWarning(pkg, 'ldd-failed', file) res = Pkg.getstatusoutput( ('env', 'LC_ALL=C', 'ldd', '-r', '-u', path)) if res[0]: # Either ldd doesn't grok -u (added in glibc 2.3.4) or we have # unused direct dependencies in_unused = False for l in res[1].splitlines(): if not l.rstrip(): pass elif l.startswith('Unused direct dependencies'): in_unused = True elif in_unused: unused = BinaryInfo.unused_regex.search(l) if unused: self.unused.append(unused.group(1)) else: in_unused = False
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 __init__(self, pkg, path, fname, is_ar, is_shlib): self.readelf_error = False self.needed = [] self.rpath = [] self.undef = [] self.unused = [] self.comment = False self.soname = False self.non_pic = True self.stack = False self.exec_stack = False self.exit_calls = [] self.forbidden_calls = [] fork_called = False self.tail = '' self.setgid = False self.setuid = False self.setgroups = False self.chroot = False self.chdir = False self.chroot_near_chdir = False self.mktemp = False is_debug = path.endswith('.debug') # Currently this implementation works only on specific # architectures due to reliance on arch specific assembly. if (pkg.arch.startswith('armv') or pkg.arch == 'aarch64'): # 10450: ebffffec bl 10408 <chroot@plt> BinaryInfo.objdump_call_regex = re.compile(br'\sbl\s+(.*)') elif (pkg.arch.endswith('86') or pkg.arch == 'x86_64'): # 401eb8: e8 c3 f0 ff ff callq 400f80 <chdir@plt> BinaryInfo.objdump_call_regex = re.compile(br'callq?\s(.*)') else: BinaryInfo.objdump_call_regex = None res = Pkg.getstatusoutput( ('readelf', '-W', '-S', '-l', '-d', '-s', path)) if not res[0]: lines = res[1].splitlines() for line in lines: r = BinaryInfo.needed_regex.search(line) if r: self.needed.append(r.group(1)) continue r = BinaryInfo.rpath_regex.search(line) if r: for p in r.group(1).split(':'): self.rpath.append(p) continue if BinaryInfo.comment_regex.search(line): self.comment = True continue if BinaryInfo.pic_regex.search(line): self.non_pic = False continue r = BinaryInfo.soname_regex.search(line) if r: self.soname = r.group(1) continue r = BinaryInfo.stack_regex.search(line) if r: self.stack = True flags = r.group(1) if flags and BinaryInfo.stack_exec_regex.search(flags): self.exec_stack = True continue if line.startswith("Symbol table"): break for line in lines: r = BinaryInfo.call_regex.search(line) if not r: continue line = r.group(1) if BinaryInfo.mktemp_call_regex.search(line): self.mktemp = True if BinaryInfo.setgid_call_regex.search(line): self.setgid = True if BinaryInfo.setuid_call_regex.search(line): self.setuid = True if BinaryInfo.setgroups_call_regex.search(line): self.setgroups = True if BinaryInfo.chdir_call_regex.search(line): self.chdir = True if BinaryInfo.chroot_call_regex.search(line): self.chroot = True if BinaryInfo.forbidden_functions: for r_name, func in BinaryInfo.forbidden_functions.items(): ret = func['f_regex'].search(line) if ret: self.forbidden_calls.append(r_name) if is_shlib: r = BinaryInfo.exit_call_regex.search(line) if r: self.exit_calls.append(r.group(1)) continue r = BinaryInfo.fork_call_regex.search(line) if r: fork_called = True continue # check if we don't have a string that will automatically # waive the presence of a forbidden call if self.forbidden_calls: res = Pkg.getstatusoutput(('strings', path)) if not res[0]: for line in res[1].splitlines(): # as we need to remove elements, iterate backwards for i in range(len(self.forbidden_calls) - 1, -1, -1): func = self.forbidden_calls[i] f = BinaryInfo.forbidden_functions[func] if 'waiver_regex' not in f: continue r = f['waiver_regex'].search(line) if r: del self.forbidden_calls[i] if self.non_pic: self.non_pic = 'TEXTREL' in res[1] # Ignore all exit() calls if fork() is being called. # Does not have any context at all but without this kludge, the # number of false positives would probably be intolerable. if fork_called: self.exit_calls = [] # check if chroot is near chdir (since otherwise, chroot is called # without chdir) if not BinaryInfo.objdump_call_regex and self.chroot and self.chdir: # On some architectures, e.g. PPC, it is to difficult to # find the actual invocations of chroot/chdir, if both # exist assume chroot is fine self.chroot_near_chdir = True elif self.chroot and self.chdir: p = subprocess.Popen(('objdump', '-d', path), stdout=subprocess.PIPE, bufsize=-1, env=dict(os.environ, LC_ALL="C")) with p.stdout: index = 0 chroot_index = -99 chdir_index = -99 for line in p.stdout: res = BinaryInfo.objdump_call_regex.search(line) if not res: continue if b'@plt' not in res.group(1): pass elif b'chroot@plt' in res.group(1): chroot_index = index if abs(chroot_index - chdir_index) <= 2: self.chroot_near_chdir = True break elif b'chdir@plt' in res.group(1): chdir_index = index if abs(chroot_index - chdir_index) <= 2: self.chroot_near_chdir = True break index += 1 if p.wait() and not self.chroot_near_chdir: printWarning(pkg, 'binaryinfo-objdump-failed', fname) self.chroot_near_chdir = True # avoid false positive elif chroot_index == -99 and chdir_index == -99: self.chroot_near_chdir = True # avoid false positive else: self.readelf_error = True # Go and others are producing ar archives that don't have ELF # headers, so don't complain about it if not is_ar: printWarning(pkg, 'binaryinfo-readelf-failed', fname, re.sub('\n.*', '', res[1])) try: with open(path, 'rb') as fobj: fobj.seek(-12, os.SEEK_END) self.tail = Pkg.b2s(fobj.read()) except Exception as e: printWarning(pkg, 'binaryinfo-tail-failed %s: %s' % (fname, e)) # Undefined symbol and unused direct dependency checks make sense only # for installed packages. # skip debuginfo: https://bugzilla.redhat.com/190599 if not is_ar and not is_debug and isinstance(pkg, Pkg.InstalledPkg): # We could do this with objdump, but it's _much_ simpler with ldd. res = Pkg.getstatusoutput(('ldd', '-d', '-r', path)) if not res[0]: for line in res[1].splitlines(): undef = BinaryInfo.undef_regex.search(line) if undef: self.undef.append(undef.group(1)) if self.undef: try: res = Pkg.getstatusoutput(['c++filt'] + self.undef) if not res[0]: self.undef = res[1].splitlines() except OSError: pass else: printWarning(pkg, 'ldd-failed', fname) res = Pkg.getstatusoutput(('ldd', '-r', '-u', path)) if res[0]: # Either ldd doesn't grok -u (added in glibc 2.3.4) or we have # unused direct dependencies in_unused = False for line in res[1].splitlines(): if not line.rstrip(): pass elif line.startswith('Unused direct dependencies'): in_unused = True elif in_unused: unused = BinaryInfo.unused_regex.search(line) if unused: self.unused.append(unused.group(1)) else: in_unused = False