# binaries -- lintian check script -*- perl -*- # Copyright (C) 1998 Christian Schwarz and Richard Braakman # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, you can find it on the World Wide # Web at http://www.gnu.org/copyleft/gpl.html, or write to the Free # Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, # MA 02110-1301, USA. package Maemian::binaries; use strict; use Tags; use Util; use Spelling; use File::Spec; # Table based on checks/emdebian's %archdetecttable, as found in # emdebian-tools. our %ARCH_REGEX = ( '32' => qr'ELF 32-bit', '64' => qr'ELF 64-bit', 'alpha' => qr'ELF 64-bit LSB .* Alpha', 'amd64' => qr'ELF 64-bit LSB .* x86-64, .* (?:GNU/Linux|(?!GNU))', 'arm' => qr'ELF 32-bit LSB .* ARM, version \d,', 'armeb' => qr'ELF 32-bit MSB .* ARM', 'armel' => qr'ELF 32-bit LSB .* \(SYSV\)', 'hppa' => qr'ELF 32-bit MSB .* PA-RISC', 'hurd-i386' => qr'ELF 32-bit LSB .* Intel 80386, .* (?:GNU/Hurd|(?!GNU))', 'i386' => qr'ELF 32-bit LSB .* 80386, .* (?:GNU/Linux|(?!GNU))', 'ia64' => qr'ELF 64-bit LSB .* IA-64', 'kfreebsd-amd64' => qr'ELF 64-bit LSB .* x86-64, .* (?:GNU/kFreeBSD|(?!GNU))', 'kfreebsd-i386' => qr'ELF 32-bit LSB .* 80386, .* (?:GNU/kFreeBSD|(?!GNU))', 'lpia' => qr'ELF 32-bit LSB .* 80386, .* (?:GNU/Linux|(?!GNU))', 'm32r' => qr'ELF 32-bit MSB .* M32R', 'm68k' => qr'ELF 32-bit MSB .* 680[02]0', 'mips' => qr'ELF 32-bit MSB .* MIPS', 'mipsel' => qr'ELF 32-bit LSB .* MIPS', 'mips64' => qr'ELF 64-bit MSB .* MIPS', 'mipsel64' => qr'ELF 64-bit LSB .* MIPS', 'powerpc' => qr'ELF 32-bit MSB .* PowerPC', 'ppc64' => qr'ELF 64-bit MSB .* PowerPC', 's390' => qr'ELF 32-bit MSB .* S.390', 's390x' => qr'ELF 64-bit MSB .* S.390', 'sparc' => qr'ELF 32-bit MSB .* SPARC', 'sparc64' => qr'ELF 64-bit MSB .* SPARC'); our $multiarch; sub run { my $pkg = shift; my $type = shift; my $info = shift; my $arch; my $dynsyms = 0; my $needs_libc = ''; my $needs_libc_file; my $needs_libc_count = 0; my $needs_depends_line = 0; my $has_perl_lib = 0; my %SONAME; $arch = $info->field('architecture'); foreach my $file (sort keys %{$info->objdump_info}) { my $objdump = $info->objdump_info->{$file}; $file = './' . $file; if (defined $objdump->{SONAME}) { foreach my $soname (@{$objdump->{SONAME}}) { $SONAME{$soname} ||= []; push @{$SONAME{$soname}}, $file; } } foreach my $symbol (@{$objdump->{SYMBOLS}}) { my ($foo, $sec, $sym) = @{$symbol}; if ($arch ne 'hppa') { if ($foo eq '.text' and $sec eq 'Base' and $sym eq '__gmon_start__') { tag "binary-compiled-with-profiling-enabled", "$file"; } } else { if ( ($sec =~ /^GLIBC_.*/) and ($sym eq '_mcount') ) { tag "binary-compiled-with-profiling-enabled", "$file"; } } } foreach (@{$objdump->{NOTES}}) { if (m/^File format not recognized$/) { tag "apparently-corrupted-elf-binary", "$file"; } elsif (m/^File truncated$/) { tag "apparently-truncated-elf-binary", "$file"; } elsif (m/^Packed with UPX$/) { tag "binary-file-compressed-with-upx", "$file"; } elsif (m/^Invalid operation$/) { tag "binary-with-bad-dynamic-table", "$file" unless $file =~ m%^\./usr/lib/debug/%; } } } # For the package naming check, filter out SONAMEs where all the files are at # paths other than /lib, /usr/lib, or /usr/X11R6/lib. This avoids false # positives with plugins like Apache modules, which may have their own SONAMEs # but which don't matter for the purposes of this check. Also filter out # nsswitch modules sub lib_soname_path { my (@paths) = @_; foreach my $path (@paths) { next if $path =~ m%^(?:\.?/)?lib/libnss_[^.]+\.so(?:\.[0-9]+)$%; return 1 if $path =~ m%^(?:\.?/)?lib/[^/]+$%; return 1 if $path =~ m%^(?:\.?/)?usr/lib/[^/]+$%; return 1 if $path =~ m%^(?:\.?/)?usr/X11R6/lib/[^/]+$%; } return 0; } my @sonames = sort grep { lib_soname_path (@{$SONAME{$_}}) } keys %SONAME; # try to identify transition strings my $base_pkg = $pkg; $base_pkg =~ s/c102\b//o; $base_pkg =~ s/c2a?\b//o; $base_pkg =~ s/\dg$//o; $base_pkg =~ s/gf$//o; $base_pkg =~ s/-udeb$//o; $base_pkg =~ s/^lib64/lib/o; my $match_found = 0; foreach my $expected_name (@sonames) { $expected_name =~ s/([0-9])\.so\./$1-/; $expected_name =~ s/\.so(?:\.|\z)//; $expected_name =~ s/_/-/g; if ((lc($expected_name) eq $pkg) || (lc($expected_name) eq $base_pkg)) { $match_found = 1; last; } } tag "package-name-doesnt-match-sonames", "@sonames" if @sonames && !$match_found; my %directories; foreach (sort keys %{$info->file_info}) { next unless length $_; my $data = $info->file_info->{$_}; next unless $data =~ /^directory$/ or $data =~ / link to /; $directories{"/$_"}++; } # If we have an unknown architecture, pretend that all binaries are fine. if ($arch ne 'all' and not exists($ARCH_REGEX{$arch})) { $ARCH_REGEX{$arch} = qr/./; } # process all files in package foreach my $file (sort keys %{$info->file_info}) { my $fileinfo = $info->file_info->{$file}; my $objdump = $info->objdump_info->{$file}; $file = './' . $file; # binary or object file? next unless ($fileinfo =~ m/^[^,]*\bELF\b/) or ($fileinfo =~ m/\bcurrent ar archive\b/); # Warn about Architecture: all packages that contain shared libraries, but # only if those libraries aren't installed in a multiarch directory. The # package may be a support package for cross-compiles. if ($arch eq 'all') { my ($arch) = ($file =~ m,^\./(?:usr/)?lib/([^/]+)/,); $multiarch = Maemian::Data->new('binaries/multiarch') unless defined($multiarch); unless ($arch and $multiarch->known($arch)) { tag "arch-independent-package-contains-binary-or-object", "$file"; } } # ELF? next unless $fileinfo =~ m/^[^,]*\bELF\b/o; if ($file =~ m,^\./etc/,) { tag "binary-in-etc", "$file"; } if ($file =~ m,^\./usr/share/,) { tag "arch-dependent-file-in-usr-share", "$file"; } if ($arch ne 'all' and $fileinfo !~ m/$ARCH_REGEX{$arch}/) { if ($file =~ m,/lib(\d{2})/, or $file =~ m,/emul/ia(\d{2}),) { tag "binary-from-other-architecture", $file unless ($fileinfo =~ m/$ARCH_REGEX{$1}/); } elsif ($arch eq 'amd64' and $fileinfo =~ m/$ARCH_REGEX{i386}/) { # Ignore i386 binaries in amd64 packages for right now. } else { $multiarch = Maemian::Data->new('binaries/multiarch') unless defined($multiarch); tag "binary-from-other-architecture", $file unless (grep { $file =~ m,/\Q$_\E/, } $multiarch->all); } } # stripped? if ($fileinfo =~ m,not stripped\s*$,o) { # Is it an object file (which generally can not be stripped), # a kernel module, debugging symbols, or perhaps a debugging package? # Ocaml executables are exempted, see #252695 unless ($file =~ m,\.k?o$, or $pkg =~ m/-dbg$/ or $pkg =~ m/debug/ or $file =~ m,/lib/debug/, or exists $objdump->{OCAML}) { tag "unstripped-binary-or-object", "$file"; } } else { # stripped but a debug or profiling library? if (($file =~ m,/lib/debug/,o) or ($file =~ m,/lib/profile/,o)) { tag "library-in-debug-or-profile-should-not-be-stripped", "$file"; } else { # appropriately stripped, but is it stripped enough? if (exists $objdump->{NOTE_SECTION}) { tag "binary-has-unneeded-section", "$file .note"; } if (exists $objdump->{COMMENT_SECTION}) { tag "binary-has-unneeded-section", "$file .comment"; } } } # rpath is disallowed, except in private directories if (exists $objdump->{RPATH}) { foreach my $rpath (map {File::Spec->canonpath($_)} keys %{$objdump->{RPATH}}) { next if $rpath =~ m,^/usr/lib/(?:games/)?\Q$pkg\E(?:/|\z),; next if $rpath =~ m,^\$ORIGIN$,; next if $directories{$rpath}; tag "binary-or-shlib-defines-rpath", "$file $rpath"; } } my $strings = slurp_entire_file("strings/$file"); spelling_check('spelling-error-in-binary', $strings, $file); if ($pkg !~ m/^zlib.+/ and $strings =~ /(?:in|de)flate (?:\d[ \w.\-]{1,20}[\w.\-])/m) { tag "embedded-zlib", $file; } # binary or shared object? next unless ($fileinfo =~ m/executable/) or ($fileinfo =~ m/shared object/); next if $type eq 'udeb'; # Perl library? if ($file =~ m,^\./usr/lib/perl5/.*\.so$,) { $has_perl_lib = 1; } # Something other than detached debugging symbols in /usr/lib/debug paths. if ($file =~ m,^\./usr/lib/debug/(?:lib\d*|s?bin|usr|opt|dev|emul)/,) { if (exists($objdump->{NEEDED})) { tag "debug-file-should-use-detached-symbols", $file; } } # statically linked? if (!exists($objdump->{NEEDED}) || !defined($objdump->{NEEDED})) { if ($fileinfo =~ m/shared object/o) { # Some exceptions: detached debugging information and the dynamic # loader (which itself has no dependencies). next if ($file =~ m%^\./usr/lib/debug/%); next if ($file =~ m%^\./lib/(?:[\w/]+/)?ld-[\d.]+\.so$%); tag "shared-lib-without-dependency-information", "$file"; } else { # Some exceptions: files in /boot, /usr/lib/debug/*, named *-static or # *.static, or *-static as package-name. next if ($file =~ m%^./boot/%); # klibc binaries appear to be static. next if ($objdump->{KLIBC}); # Location of debugging symbols: next if ($file =~ m%^./usr/lib/debug/%); next if ($file =~ /(?:\.|-)static$/); next if ($pkg =~ /-static$/); tag "statically-linked-binary", "$file"; } } else { my $lib; my $no_libc = 1; $needs_depends_line = 1; for $lib (@{$objdump->{NEEDED}}) { if ($lib =~ /^libc\.so\.(\d+.*)/) { $needs_libc = "libc$1"; $needs_libc_file = $file unless $needs_libc_file; $needs_libc_count++; $no_libc = 0; } } if ($no_libc and not $file =~ m,/libc\b,) { if ($fileinfo =~ m/shared object/) { tag "library-not-linked-against-libc", "$file"; } else { tag "program-not-linked-against-libc", "$file"; } } } } # Find the package dependencies, which is used by various checks. my $depends = ''; if (defined $info->field('pre-depends')) { $depends = $info->field('pre-depends'); } if (defined $info->field('depends')) { $depends .= ', ' if $depends; $depends .= $info->field('depends'); } $depends =~ s/\n/ /g; # Check for a libc dependency. if ($needs_depends_line) { if ($depends && $needs_libc && $pkg !~ /^libc[\d.]+(?:-|\z)/) { # Match libcXX or libcXX-*, but not libc3p0. my $re = qr/(?:^|,)\s*\Q$needs_libc\E\b/; if ($depends !~ /$re/) { my $others = ''; $needs_libc_count--; if ($needs_libc_count > 0) { $others = " and $needs_libc_count others"; } tag "missing-dependency-on-libc", "needed by $needs_libc_file$others"; } } elsif (!$depends) { tag "missing-depends-line"; } } # Check for a Perl dependency. if ($has_perl_lib) { my $re = qr/(?:^|,)\s*perlapi-[\d.]+(?:\s*\[[^\]]+\])?\s*(?:,|\z)/; unless ($depends =~ /$re/) { tag 'missing-dependency-on-perlapi'; } } } 1; # Local Variables: # indent-tabs-mode: t # cperl-indent-level: 4 # End: # vim: syntax=perl ts=8 sw=4