1 # -*- coding: utf-8 -*-
2 #############################################################################
3 # File : BinariesCheck.py
5 # Author : Frederic Lepied
6 # Created on : Tue Sep 28 07:01:42 1999
7 # Version : $Id: BinariesCheck.py 1893 2011-11-23 20:24:32Z scop $
8 # Purpose : check binary files in a binary rpm package.
9 #############################################################################
16 from Filter import addDetails, printError, printWarning
22 DEFAULT_SYSTEM_LIB_PATHS = (
23 '/lib', '/usr/lib', '/usr/X11R6/lib',
24 '/lib64', '/usr/lib64', '/usr/X11R6/lib64')
28 needed_regex = re.compile('\s+\(NEEDED\).*\[(\S+)\]')
29 rpath_regex = re.compile('\s+\(RPATH\).*\[(\S+)\]')
30 soname_regex = re.compile('\s+\(SONAME\).*\[(\S+)\]')
31 comment_regex = re.compile('^\s+\[\s*\d+\]\s+\.comment\s+')
32 pic_regex = re.compile('^\s+\[\s*\d+\]\s+\.rela?\.(data|text)')
33 # GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RWE 0x4
34 stack_regex = re.compile('^\s+GNU_STACK\s+(?:(?:\S+\s+){5}(\S+)\s+)?')
35 stack_exec_regex = re.compile('^..E$')
36 undef_regex = re.compile('^undefined symbol:\s+(\S+)')
37 unused_regex = re.compile('^\s+(\S+)')
38 exit_call_regex = re.compile('\s+FUNC\s+.*?\s+(_?exit(?:@\S+)?)(?:\s|$)')
39 fork_call_regex = re.compile('\s+FUNC\s+.*?\s+(fork(?:@\S+)?)(?:\s|$)')
41 def __init__(self, pkg, path, file, is_ar, is_shlib):
42 self.readelf_error = False
51 self.exec_stack = False
56 is_debug = path.endswith('.debug')
58 cmd = ['env', 'LC_ALL=C', 'readelf', '-W', '-S', '-l', '-d', '-s']
60 res = Pkg.getstatusoutput(cmd)
62 for l in res[1].splitlines():
64 r = BinaryInfo.needed_regex.search(l)
66 self.needed.append(r.group(1))
69 r = BinaryInfo.rpath_regex.search(l)
71 for p in r.group(1).split(':'):
75 if BinaryInfo.comment_regex.search(l):
79 if BinaryInfo.pic_regex.search(l):
83 r = BinaryInfo.soname_regex.search(l)
85 self.soname = r.group(1)
88 r = BinaryInfo.stack_regex.search(l)
92 if flags and BinaryInfo.stack_exec_regex.search(flags):
93 self.exec_stack = True
97 r = BinaryInfo.exit_call_regex.search(l)
99 self.exit_calls.append(r.group(1))
101 r = BinaryInfo.fork_call_regex.search(l)
107 self.non_pic = 'TEXTREL' in res[1]
109 # Ignore all exit() calls if fork() is being called.
110 # Does not have any context at all but without this kludge, the
111 # number of false positives would probably be intolerable.
116 self.readelf_error = True
117 printWarning(pkg, 'binaryinfo-readelf-failed',
118 file, re.sub('\n.*', '', res[1]))
123 fobj.seek(-12, 2) # 2 == os.SEEK_END, for python 2.4 compat (#172)
124 self.tail = fobj.read()
126 printWarning(pkg, 'binaryinfo-tail-failed %s: %s' % (file, e))
130 # Undefined symbol and unused direct dependency checks make sense only
131 # for installed packages.
132 # skip debuginfo: https://bugzilla.redhat.com/190599
133 if not is_ar and not is_debug and isinstance(pkg, Pkg.InstalledPkg):
134 # We could do this with objdump, but it's _much_ simpler with ldd.
135 res = Pkg.getstatusoutput(
136 ('env', 'LC_ALL=C', 'ldd', '-d', '-r', path))
138 for l in res[1].splitlines():
139 undef = BinaryInfo.undef_regex.search(l)
141 self.undef.append(undef.group(1))
144 cmd.insert(0, 'c++filt')
146 res = Pkg.getstatusoutput(cmd)
148 self.undef = res[1].splitlines()
152 printWarning(pkg, 'ldd-failed', file)
153 res = Pkg.getstatusoutput(
154 ('env', 'LC_ALL=C', 'ldd', '-r', '-u', path))
156 # Either ldd doesn't grok -u (added in glibc 2.3.4) or we have
157 # unused direct dependencies
159 for l in res[1].splitlines():
162 elif l.startswith('Unused direct dependencies'):
165 unused = BinaryInfo.unused_regex.search(l)
167 self.unused.append(unused.group(1))
171 path_regex = re.compile('(.*/)([^/]+)')
172 numeric_dir_regex = re.compile('/usr(?:/share)/man/man./(.*)\.[0-9](?:\.gz|\.bz2)')
173 versioned_dir_regex = re.compile('[^.][0-9]')
174 ldso_soname_regex = re.compile('^ld(-linux(-(ia|x86_)64))?\.so')
175 so_regex = re.compile('/lib(64)?/[^/]+\.so(\.[0-9]+)*$')
176 validso_regex = re.compile('(\.so\.\d+(\.\d+)*|\d\.so)$')
177 sparc_regex = re.compile('SPARC32PLUS|SPARC V9|UltraSPARC')
178 system_lib_paths = Config.getOption('SystemLibPaths', DEFAULT_SYSTEM_LIB_PATHS)
179 pie_exec_re = Config.getOption('PieExecutables')
181 pie_exec_re = re.compile(pie_exec_re)
182 usr_lib_regex = re.compile('^/usr/lib(64)?/')
183 bin_regex = re.compile('^(/usr(/X11R6)?)?/s?bin/')
184 soversion_regex = re.compile('.*?([0-9][.0-9]*)\\.so|.*\\.so\\.([0-9][.0-9]*).*')
185 reference_regex = re.compile('\.la$|^/usr/lib(64)?/pkgconfig/')
186 usr_lib_exception_regex = re.compile(Config.getOption('UsrLibBinaryException', '^/usr/lib(64)?/(perl|python|ruby|menu|pkgconfig|ocaml|lib[^/]+\.(so|l?a)$|bonobo/servers/)'))
187 srcname_regex = re.compile('(.*?)-[0-9]')
188 invalid_dir_ref_regex = re.compile('/(home|tmp)(\W|$)')
189 ocaml_mixed_regex = re.compile('^Caml1999X0\d\d$')
192 res = path_regex.search(path)
194 return res.group(1), res.group(2)
198 class BinariesCheck(AbstractCheck.AbstractCheck):
201 AbstractCheck.AbstractCheck.__init__(self, 'BinariesCheck')
203 def check(self, pkg):
204 # Check only binary package
213 binary_in_usr_lib = False
214 has_usr_lib_file = False
217 res = srcname_regex.search(pkg[rpm.RPMTAG_SOURCERPM] or '')
219 multi_pkg = (pkg.name != res.group(1))
221 for fname, pkgfile in files.items():
223 if not stat.S_ISDIR(pkgfile.mode) and usr_lib_regex.search(fname):
224 has_usr_lib_file = True
225 if not binary_in_usr_lib and \
226 usr_lib_exception_regex.search(fname):
227 # Fake that we have binaries there to avoid
228 # only-non-binary-in-usr-lib false positives
229 binary_in_usr_lib = True
231 is_elf = 'ELF' in pkgfile.magic
232 is_ar = 'current ar archive' in pkgfile.magic
233 is_ocaml_native = 'Objective caml native' in pkgfile.magic
234 is_binary = is_elf or is_ar or is_ocaml_native
237 if reference_regex.search(fname):
238 lines = pkg.grep(invalid_dir_ref_regex, fname)
240 printError(pkg, 'invalid-directory-reference', fname,
241 '(line %s)' % ", ".join(lines))
244 # binary files only from here on
248 if has_usr_lib_file and not binary_in_usr_lib and \
249 usr_lib_regex.search(fname):
250 binary_in_usr_lib = True
252 if pkg.arch == 'noarch':
255 'arch-independent-package-contains-binary-or-object',
259 # arch dependent packages only from here on
262 if fname.startswith('/usr/share/'):
263 printError(pkg, 'arch-dependent-file-in-usr-share', fname)
266 if fname.startswith('/etc/'):
267 printError(pkg, 'binary-in-etc', fname)
269 if pkg.arch == 'sparc' and sparc_regex.search(pkgfile.magic):
270 printError(pkg, 'non-sparc32-binary', fname)
272 if is_ocaml_native or fname.endswith('.o') or \
273 fname.endswith('.static'):
277 if 'not stripped' in pkgfile.magic:
278 printWarning(pkg, 'unstripped-binary-or-object', fname)
280 # inspect binary file
281 is_shlib = so_regex.search(fname)
282 bin_info = BinaryInfo(pkg, pkgfile.path, fname, is_ar, is_shlib)
288 if is_shlib and not bin_info.readelf_error:
291 if not bin_info.soname:
292 printWarning(pkg, 'no-soname', fname)
294 if not validso_regex.search(bin_info.soname):
295 printError(pkg, 'invalid-soname', fname,
298 (directory, base) = dir_base(fname)
300 symlink = directory + bin_info.soname
301 link = files[symlink].linkto
302 if link not in (fname, base, ''):
303 printError(pkg, 'invalid-ldconfig-symlink',
306 if base.startswith("lib") or base.startswith("ld-"):
307 printError(pkg, 'no-ldconfig-symlink', fname)
309 res = soversion_regex.search(bin_info.soname)
311 soversion = res.group(1) or res.group(2)
314 elif version != soversion:
318 printError(pkg, 'shlib-with-non-pic-code', fname)
320 # It could be useful to check these for others than shared
321 # libs only, but that has potential to generate lots of
322 # false positives and noise.
323 for s in bin_info.undef:
324 printWarning(pkg, 'undefined-non-weak-symbol', fname, s)
325 for s in bin_info.unused:
326 printWarning(pkg, 'unused-direct-shlib-dependency',
329 # calls exit() or _exit()?
330 for ec in bin_info.exit_calls:
331 printWarning(pkg, 'shared-lib-calls-exit', fname, ec)
335 for p in bin_info.rpath:
336 if p in system_lib_paths or not usr_lib_regex.search(p):
337 printError(pkg, 'binary-or-shlib-defines-rpath',
338 fname, bin_info.rpath)
341 is_exec = 'executable' in pkgfile.magic
342 is_shobj = 'shared object' in pkgfile.magic
344 if not is_exec and not is_shobj:
347 if is_shobj and not is_exec and '.so' not in fname and \
348 bin_regex.search(fname):
349 # pkgfile.magic does not contain "executable" for PIEs
354 if bin_regex.search(fname):
355 exec_files.append(fname)
357 if ocaml_mixed_regex.search(bin_info.tail):
358 printWarning(pkg, 'ocaml-mixed-executable', fname)
360 if not is_shobj and pie_exec_re and pie_exec_re.search(fname):
361 printError(pkg, 'non-position-independent-executable',
364 if bin_info.readelf_error:
367 if not bin_info.needed and not (
368 bin_info.soname and ldso_soname_regex.search(bin_info.soname)):
371 'shared-lib-without-dependency-information',
374 printError(pkg, 'statically-linked-binary', fname)
377 # linked against libc ?
378 if "libc." not in fname and \
379 (not bin_info.soname or \
380 ("libc." not in bin_info.soname and \
381 not ldso_soname_regex.search(bin_info.soname))):
384 for lib in bin_info.needed:
391 printError(pkg, 'library-not-linked-against-libc',
394 printError(pkg, 'program-not-linked-against-libc',
398 if bin_info.exec_stack:
399 printWarning(pkg, 'executable-stack', fname)
400 elif not bin_info.readelf_error and (
401 pkg.arch.endswith("86") or pkg.arch.startswith("pentium") or
402 pkg.arch in ("athlon", "x86_64")):
403 printError(pkg, 'missing-PT_GNU_STACK-section', fname)
407 printError(pkg, 'executable-in-library-package', f)
409 res = numeric_dir_regex.search(f)
410 fn = res and res.group(1) or f
411 if f not in exec_files and not so_regex.search(f) and \
412 not versioned_dir_regex.search(fn):
413 printError(pkg, 'non-versioned-file-in-library-package', f)
414 if version and version != -1 and version not in pkg.name:
415 printError(pkg, 'incoherent-version-in-name', version)
417 if not binary and not multi_pkg and pkg.arch != 'noarch':
418 printError(pkg, 'no-binary')
420 if has_usr_lib_file and not binary_in_usr_lib:
421 printWarning(pkg, 'only-non-binary-in-usr-lib')
423 # Create an object to enable the auto registration of the test
424 check = BinariesCheck()
426 # Add information about checks
428 'arch-independent-package-contains-binary-or-object',
429 '''The package contains a binary or object file but is tagged
432 'arch-dependent-file-in-usr-share',
433 '''This package installs an ELF binary in the /usr/share
434 hierarchy, which is reserved for architecture-independent files.''',
437 '''This package installs an ELF binary in /etc. Both the
438 FHS and the FSSTND forbid this.''',
440 # 'non-sparc32-binary',
444 '''The soname of the library is neither of the form lib<libname>.so.<major> or
445 lib<libname>-<major>.so.''',
447 'invalid-ldconfig-symlink',
448 '''The symbolic link references the wrong file. It should reference
449 the shared library.''',
451 'no-ldconfig-symlink',
452 '''The package should not only include the shared library itself, but
453 also the symbolic link which ldconfig would produce. (This is
454 necessary, so that the link gets removed by rpm automatically when
455 the package gets removed, even if for some reason ldconfig would not be
456 run at package postinstall phase.)''',
458 'shlib-with-non-pic-code',
459 '''The listed shared libraries contain object code that was compiled
460 without -fPIC. All object code in shared libraries should be
461 recompiled separately from the static libraries with the -fPIC option.
463 Another common mistake that causes this problem is linking with
464 ``gcc -Wl,-shared'' instead of ``gcc -shared''.''',
466 'binary-or-shlib-defines-rpath',
467 '''The binary or shared library defines `RPATH'. Usually this is a
468 bad thing because it hardcodes the path to search libraries and so
469 makes it difficult to move libraries around. Most likely you will find a
470 Makefile with a line like: gcc test.o -o test -Wl,--rpath. Also, sometimes
471 configure scripts provide a --disable-rpath flag to avoid this.''',
473 'statically-linked-binary',
474 '''The package installs a statically linked binary or object file.
476 Usually this is a packaging bug. If not, contact your rpmlint distributor
477 about this so that this error gets included in the exception file for rpmlint
478 and will not be flagged as a packaging bug in the future (or add it to your
479 local configuration if you installed rpmlint from the source tarball).''',
481 'executable-in-library-package',
482 '''The package mixes up libraries and executables. Mixing up these
483 both types of files makes upgrades quite impossible.''',
485 'non-versioned-file-in-library-package',
486 '''The package contains files in non versioned directories. This makes it
487 impossible to have multiple major versions of the libraries installed.
488 One solution can be to change the directories which contain the files
489 to subdirs of /usr/lib/<name>-<version> or /usr/share/<name>-<version>.
490 Another solution can be to include a version number in the file names
493 'incoherent-version-in-name',
494 '''The package name should contain the major version of the library.''',
496 'invalid-directory-reference',
497 'This file contains a reference to /tmp or /home.',
500 '''The package should be of the noarch architecture because it doesn't contain
503 # http://sources.redhat.com/ml/libc-alpha/2003-05/msg00034.html
504 'undefined-non-weak-symbol',
505 '''The binary contains undefined non-weak symbols. This may indicate improper
506 linkage; check that the binary has been linked as expected.''',
508 # http://www.redhat.com/archives/fedora-maintainers/2006-June/msg00176.html
509 'unused-direct-shlib-dependency',
510 '''The binary contains unused direct shared library dependencies. This may
511 indicate gratuitously bloated linkage; check that the binary has been linked
512 with the intended shared libraries only.''',
514 'only-non-binary-in-usr-lib',
515 '''There are only non binary files in /usr/lib so they should be in
518 'binaryinfo-readelf-failed',
519 '''Executing readelf on this file failed, all checks could not be run.''',
521 'binaryinfo-tail-failed',
522 '''Reading trailing bytes of this file failed, all checks could not be run.''',
525 '''Executing ldd on this file failed, all checks could not be run.''',
528 '''The binary declares the stack as executable. Executable stack is usually an
529 error as it is only needed if the code contains GCC trampolines or similar
530 constructs which uses code on the stack. One common source for needlessly
531 executable stack cases are object files built from assembler files which
532 don\'t define a proper .note.GNU-stack section.''',
534 'missing-PT_GNU_STACK-section',
535 '''The binary lacks a PT_GNU_STACK section. This forces the dynamic linker to
536 make the stack executable. Usual suspects include use of a non-GNU linker or
537 an old GNU linker version.''',
539 'shared-lib-calls-exit',
540 '''This library package calls exit() or _exit(), probably in a non-fork()
541 context. Doing so from a library is strongly discouraged - when a library
542 function calls exit(), it prevents the calling program from handling the
543 error, reporting it to the user, closing files properly, and cleaning up any
544 state that the program has. It is preferred for the library to return an
545 actual error code and let the calling program decide how to handle the
548 'ocaml-mixed-executable',
549 '''Executables built with ocamlc -custom are deprecated. Packagers should ask
550 upstream maintainers to build these executables without the -custom option. If
551 this cannot be changed and the executable needs to be packaged in its current
552 form, make sure that rpmbuild does not strip it during the build, and on setups
553 that use prelink, make sure that prelink does not strip it either, usually by
554 placing a blacklist file in /etc/prelink.conf.d. For more information, see
555 http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=256900#49''',
557 'non-position-independent-executable',
558 '''This executable must be position independent. Check that it is built with
559 -fPIE/-fpie in compiler flags and -pie in linker flags.''',
562 # BinariesCheck.py ends here
565 # indent-tabs-mode: nil
566 # py-indent-offset: 4