invalid-filerequires.diff
[platform/upstream/rpmlint.git] / BinariesCheck.py
1 # -*- coding: utf-8 -*-
2 #############################################################################
3 # File          : BinariesCheck.py
4 # Package       : rpmlint
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 #############################################################################
10
11 import re
12 import stat
13
14 import rpm
15
16 from Filter import addDetails, printError, printWarning
17 import AbstractCheck
18 import Config
19 import Pkg
20
21
22 DEFAULT_SYSTEM_LIB_PATHS = (
23     '/lib', '/usr/lib', '/usr/X11R6/lib',
24     '/lib64', '/usr/lib64', '/usr/X11R6/lib64')
25
26 class BinaryInfo:
27
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|$)')
40
41     def __init__(self, pkg, path, file, is_ar, is_shlib):
42         self.readelf_error = False
43         self.needed = []
44         self.rpath = []
45         self.undef = []
46         self.unused = []
47         self.comment = False
48         self.soname = False
49         self.non_pic = True
50         self.stack = False
51         self.exec_stack = False
52         self.exit_calls = []
53         fork_called = False
54         self.tail = ''
55
56         is_debug = path.endswith('.debug')
57
58         cmd = ['env', 'LC_ALL=C', 'readelf', '-W', '-S', '-l', '-d', '-s']
59         cmd.append(path)
60         res = Pkg.getstatusoutput(cmd)
61         if not res[0]:
62             for l in res[1].splitlines():
63
64                 r = BinaryInfo.needed_regex.search(l)
65                 if r:
66                     self.needed.append(r.group(1))
67                     continue
68
69                 r = BinaryInfo.rpath_regex.search(l)
70                 if r:
71                     for p in r.group(1).split(':'):
72                         self.rpath.append(p)
73                     continue
74
75                 if BinaryInfo.comment_regex.search(l):
76                     self.comment = True
77                     continue
78
79                 if BinaryInfo.pic_regex.search(l):
80                     self.non_pic = False
81                     continue
82
83                 r = BinaryInfo.soname_regex.search(l)
84                 if r:
85                     self.soname = r.group(1)
86                     continue
87
88                 r = BinaryInfo.stack_regex.search(l)
89                 if r:
90                     self.stack = True
91                     flags = r.group(1)
92                     if flags and BinaryInfo.stack_exec_regex.search(flags):
93                         self.exec_stack = True
94                     continue
95
96                 if is_shlib:
97                     r = BinaryInfo.exit_call_regex.search(l)
98                     if r:
99                         self.exit_calls.append(r.group(1))
100                         continue
101                     r = BinaryInfo.fork_call_regex.search(l)
102                     if r:
103                         fork_called = True
104                         continue
105
106             if self.non_pic:
107                 self.non_pic = 'TEXTREL' in res[1]
108
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.
112             if fork_called:
113                 self.exit_calls = []
114
115         else:
116             self.readelf_error = True
117             printWarning(pkg, 'binaryinfo-readelf-failed',
118                          file, re.sub('\n.*', '', res[1]))
119
120         fobj = None
121         try:
122             fobj = open(path)
123             fobj.seek(-12, 2) # 2 == os.SEEK_END, for python 2.4 compat (#172)
124             self.tail = fobj.read()
125         except Exception, e:
126             printWarning(pkg, 'binaryinfo-tail-failed %s: %s' % (file, e))
127         if fobj:
128             fobj.close()
129
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))
137             if not res[0]:
138                 for l in res[1].splitlines():
139                     undef = BinaryInfo.undef_regex.search(l)
140                     if undef:
141                         self.undef.append(undef.group(1))
142                 if self.undef:
143                     cmd = self.undef[:]
144                     cmd.insert(0, 'c++filt')
145                     try:
146                         res = Pkg.getstatusoutput(cmd)
147                         if not res[0]:
148                             self.undef = res[1].splitlines()
149                     except:
150                         pass
151             else:
152                 printWarning(pkg, 'ldd-failed', file)
153             res = Pkg.getstatusoutput(
154                 ('env', 'LC_ALL=C', 'ldd', '-r', '-u', path))
155             if res[0]:
156                 # Either ldd doesn't grok -u (added in glibc 2.3.4) or we have
157                 # unused direct dependencies
158                 in_unused = False
159                 for l in res[1].splitlines():
160                     if not l.rstrip():
161                         pass
162                     elif l.startswith('Unused direct dependencies'):
163                         in_unused = True
164                     elif in_unused:
165                         unused = BinaryInfo.unused_regex.search(l)
166                         if unused:
167                             self.unused.append(unused.group(1))
168                         else:
169                             in_unused = False
170
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')
180 if pie_exec_re:
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$')
190
191 def dir_base(path):
192     res = path_regex.search(path)
193     if res:
194         return res.group(1), res.group(2)
195     else:
196         return '', path
197
198 class BinariesCheck(AbstractCheck.AbstractCheck):
199
200     def __init__(self):
201         AbstractCheck.AbstractCheck.__init__(self, 'BinariesCheck')
202
203     def check(self, pkg):
204         # Check only binary package
205         if pkg.isSource():
206             return
207
208         files = pkg.files()
209         exec_files = []
210         has_lib = False
211         version = None
212         binary = False
213         binary_in_usr_lib = False
214         has_usr_lib_file = False
215
216         multi_pkg = False
217         res = srcname_regex.search(pkg[rpm.RPMTAG_SOURCERPM] or '')
218         if res:
219             multi_pkg = (pkg.name != res.group(1))
220
221         for fname, pkgfile in files.items():
222
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
230
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
235
236             if not is_binary:
237                 if reference_regex.search(fname):
238                     lines = pkg.grep(invalid_dir_ref_regex, fname)
239                     if lines:
240                         printError(pkg, 'invalid-directory-reference', fname,
241                                    '(line %s)' % ", ".join(lines))
242                 continue
243
244             # binary files only from here on
245
246             binary = True
247
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
251
252             if pkg.arch == 'noarch':
253                 printError(
254                     pkg,
255                     'arch-independent-package-contains-binary-or-object',
256                     fname)
257                 continue
258
259             # arch dependent packages only from here on
260
261             # in /usr/share ?
262             if fname.startswith('/usr/share/'):
263                 printError(pkg, 'arch-dependent-file-in-usr-share', fname)
264
265             # in /etc ?
266             if fname.startswith('/etc/'):
267                 printError(pkg, 'binary-in-etc', fname)
268
269             if pkg.arch == 'sparc' and sparc_regex.search(pkgfile.magic):
270                 printError(pkg, 'non-sparc32-binary', fname)
271
272             if is_ocaml_native or fname.endswith('.o') or \
273                     fname.endswith('.static'):
274                 continue
275
276             # stripped ?
277             if 'not stripped' in pkgfile.magic:
278                 printWarning(pkg, 'unstripped-binary-or-object', fname)
279
280             # inspect binary file
281             is_shlib = so_regex.search(fname)
282             bin_info = BinaryInfo(pkg, pkgfile.path, fname, is_ar, is_shlib)
283
284             if is_shlib:
285                 has_lib = True
286
287             # shared libs
288             if is_shlib and not bin_info.readelf_error:
289
290                 # so name in library
291                 if not bin_info.soname:
292                     printWarning(pkg, 'no-soname', fname)
293                 else:
294                     if not validso_regex.search(bin_info.soname):
295                         printError(pkg, 'invalid-soname', fname,
296                                    bin_info.soname)
297                     else:
298                         (directory, base) = dir_base(fname)
299                         try:
300                             symlink = directory + bin_info.soname
301                             link = files[symlink].linkto
302                             if link not in (fname, base, ''):
303                                 printError(pkg, 'invalid-ldconfig-symlink',
304                                            fname, link)
305                         except KeyError:
306                             if base.startswith("lib") or base.startswith("ld-"):
307                                 printError(pkg, 'no-ldconfig-symlink', fname)
308
309                     res = soversion_regex.search(bin_info.soname)
310                     if res:
311                         soversion = res.group(1) or res.group(2)
312                         if version is None:
313                             version = soversion
314                         elif version != soversion:
315                             version = -1
316
317                 if bin_info.non_pic:
318                     printError(pkg, 'shlib-with-non-pic-code', fname)
319
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',
327                                  fname, s)
328
329                 # calls exit() or _exit()?
330                 for ec in bin_info.exit_calls:
331                     printWarning(pkg, 'shared-lib-calls-exit', fname, ec)
332
333             # rpath ?
334             if bin_info.rpath:
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)
339                         break
340
341             is_exec = 'executable' in pkgfile.magic
342             is_shobj = 'shared object' in pkgfile.magic
343
344             if not is_exec and not is_shobj:
345                 continue
346
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
350                 is_exec = True
351
352             if is_exec:
353
354                 if bin_regex.search(fname):
355                     exec_files.append(fname)
356
357                 if ocaml_mixed_regex.search(bin_info.tail):
358                     printWarning(pkg, 'ocaml-mixed-executable', fname)
359
360                 if not is_shobj and pie_exec_re and pie_exec_re.search(fname):
361                     printError(pkg, 'non-position-independent-executable',
362                                fname)
363
364             if bin_info.readelf_error:
365                 continue
366
367             if not bin_info.needed and not (
368                 bin_info.soname and ldso_soname_regex.search(bin_info.soname)):
369                 if is_shobj:
370                     printError(pkg,
371                                'shared-lib-without-dependency-information',
372                                fname)
373                 else:
374                     printError(pkg, 'statically-linked-binary', fname)
375
376             else:
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))):
382
383                     found_libc = False
384                     for lib in bin_info.needed:
385                         if "libc." in lib:
386                             found_libc = True
387                             break
388
389                     if not found_libc:
390                         if is_shobj:
391                             printError(pkg, 'library-not-linked-against-libc',
392                                        fname)
393                         else:
394                             printError(pkg, 'program-not-linked-against-libc',
395                                        fname)
396
397             if bin_info.stack:
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)
404
405         if has_lib:
406             for f in exec_files:
407                 printError(pkg, 'executable-in-library-package', f)
408             for f in files:
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)
416
417         if not binary and not multi_pkg and pkg.arch != 'noarch':
418             printError(pkg, 'no-binary')
419
420         if has_usr_lib_file and not binary_in_usr_lib:
421             printWarning(pkg, 'only-non-binary-in-usr-lib')
422
423 # Create an object to enable the auto registration of the test
424 check = BinariesCheck()
425
426 # Add information about checks
427 addDetails(
428 'arch-independent-package-contains-binary-or-object',
429 '''The package contains a binary or object file but is tagged
430 noarch.''',
431
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.''',
435
436 'binary-in-etc',
437 '''This package installs an ELF binary in /etc.  Both the
438 FHS and the FSSTND forbid this.''',
439
440 # 'non-sparc32-binary',
441 # '',
442
443 'invalid-soname',
444 '''The soname of the library is neither of the form lib<libname>.so.<major> or
445 lib<libname>-<major>.so.''',
446
447 'invalid-ldconfig-symlink',
448 '''The symbolic link references the wrong file. It should reference
449 the shared library.''',
450
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.)''',
457
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.
462
463 Another common mistake that causes this problem is linking with
464 ``gcc -Wl,-shared'' instead of ``gcc -shared''.''',
465
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.''',
472
473 'statically-linked-binary',
474 '''The package installs a statically linked binary or object file.
475
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).''',
480
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.''',
484
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
491 themselves.''',
492
493 'incoherent-version-in-name',
494 '''The package name should contain the major version of the library.''',
495
496 'invalid-directory-reference',
497 'This file contains a reference to /tmp or /home.',
498
499 'no-binary',
500 '''The package should be of the noarch architecture because it doesn't contain
501 any binaries.''',
502
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.''',
507
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.''',
513
514 'only-non-binary-in-usr-lib',
515 '''There are only non binary files in /usr/lib so they should be in
516 /usr/share.''',
517
518 'binaryinfo-readelf-failed',
519 '''Executing readelf on this file failed, all checks could not be run.''',
520
521 'binaryinfo-tail-failed',
522 '''Reading trailing bytes of this file failed, all checks could not be run.''',
523
524 'ldd-failed',
525 '''Executing ldd on this file failed, all checks could not be run.''',
526
527 'executable-stack',
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.''',
533
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.''',
538
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
546 situation.''',
547
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''',
556
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.''',
560 )
561
562 # BinariesCheck.py ends here
563
564 # Local variables:
565 # indent-tabs-mode: nil
566 # py-indent-offset: 4
567 # End:
568 # ex: ts=4 sw=4 et