X-Git-Url: https://ruderich.org/simon/gitweb/?a=blobdiff_plain;f=bin%2Fblhc;h=e183027e22886699b7e4c66639ad77c12555034e;hb=d9bd7570d07a93026a3147971c8e34be6c1c6c86;hp=c82e9bc37475987d2bb8610e6c160ec2a9f42fb6;hpb=b85a3fb508fc2f9d98d44583cf31cbf5ac63a382;p=blhc%2Fblhc.git diff --git a/bin/blhc b/bin/blhc index c82e9bc..e183027 100755 --- a/bin/blhc +++ b/bin/blhc @@ -23,6 +23,7 @@ use warnings; use Getopt::Long (); use Term::ANSIColor (); +use Text::ParseWords (); our $VERSION = '0.01'; @@ -36,6 +37,81 @@ my $cc_regex = qr/(?:[a-z0-9_]+-(?:linux|kfreebsd)-gnu(?:eabi|eabihf)?-)? # Regex to catch (GCC) compiler warnings. my $warning_regex = qr/^(.+?):([0-9]+):[0-9]+: warning: (.+?) \[(.+?)\]$/; +# Regex for source files which require preprocessing. +my $source_preprocess_compile_regex = qr/ + # C + c + # Objective-C + | m + # C++ + | cc | cp | cxx | cpp | CPP | c\+\+ | C + # Objective-C++ + | mm | M + # Fortran + | F | FOR | fpp | FPP | FTN | F90 | F95 | F03 | F08 + /x; +my $source_preprocess_no_compile_regex = qr/ + # Assembly + s + /x; +my $source_preprocess_regex = qr/ + $source_preprocess_compile_regex + | $source_preprocess_no_compile_regex + /x; +# Regex for source files which don't require preprocessing. +my $source_no_preprocess_compile_regex = qr/ + # C + i + # C++ + | ii + # Objective-C + | mi + # Objective-C++ + | mii + # Fortran + | f | for | ftn | f90 | f95 | f03 | f08 + /x; +my $source_no_preprocess_no_compile_regex = qr/ + # Assembly + S | sx + /x; +my $source_no_preprocess_regex = qr/ + $source_no_preprocess_compile_regex + | $source_no_preprocess_no_compile_regex + /x; +# Regex for header files which require preprocessing. +my $header_preprocess_regex = qr/ + # C, C++, Objective-C, Objective-C++ + h + # C++ + | hh | H | hp | hxx | hpp | HPP | h\+\+ | tcc + /x; +# Regexps to match files with the given characteristics. +my $file_no_preprocess_regex = qr/ + $cc_regex.+? + \.(?: $source_no_preprocess_regex)\b + /x; +my $file_preprocess_regex = qr/ + $cc_regex.+? + \.(?: $header_preprocess_regex + | $source_preprocess_regex)\b + /x; +my $file_compile_link_regex = qr/ + $cc_regex.+? + \.(?: $source_preprocess_regex + | $source_no_preprocess_regex)\b + /x; +my $file_compile_regex = qr/ + $cc_regex.+? + \.(?: $source_preprocess_compile_regex + | $source_no_preprocess_compile_regex)\b + /x; +my $file_no_compile_regex = qr/ + $cc_regex.+ + \.(?: $source_preprocess_no_compile_regex + | $source_no_preprocess_no_compile_regex)\b + /x; + # Expected (hardening) flags. All flags are used as regexps. my @cflags = ( '-g', @@ -119,7 +195,7 @@ sub any_flags_used { my ($line, @flags) = @_; foreach my $flag (@flags) { - return 1 if $line =~ /\s$flag(?:\s|\\|$)/; + return 1 if $line =~ /\s$flag(?:\s|\\)/; } return 0; @@ -129,14 +205,12 @@ sub all_flags_used { my @missing_flags = (); foreach my $flag (@flags) { - if ($line !~ /\s$flag(?:\s|\\|$)/) { + if ($line !~ /\s$flag(?:\s|\\)/) { push @missing_flags, $flag; } } - if (scalar @missing_flags == 0) { - return 1; - } + return 1 if scalar @missing_flags == 0; @{$missing_flags_ref} = @missing_flags; return 0; @@ -168,7 +242,7 @@ sub is_non_verbose_build { my ($line, $next_line, $skip_ref) = @_; if (not ($line =~ /^checking if you want to see long compiling messages\.\.\. no/ - or $line =~ /^\s*\[?(?:CC|CCLD|CXX|CXXLD|LD)\]?\s+(.+?)$/ + or $line =~ /^\s*\[?(?:CC|CCLD|CXX|CXXLD|LD|LINK)\]?\s+(.+?)$/ or $line =~ /^\s*(?:C|c)ompiling\s+(.+?)(?:\.\.\.)?$/ or $line =~ /^\s*(?:B|b)uilding (?:program|shared library)\s+(.+?)$/ or $line =~ /^\s*\[[\d ]+%\] Building (?:C|CXX) object (.+?)$/)) { @@ -219,6 +293,7 @@ my $option_help = 0; my $option_version = 0; my $option_all = 0; my $option_arch = undef; +my $option_buildd = 0; if (not Getopt::Long::GetOptions( 'help|h|?' => \$option_help, 'version' => \$option_version, @@ -228,6 +303,7 @@ if (not Getopt::Long::GetOptions( 'all' => \$option_all, # Misc. 'arch' => \$option_arch, + 'buildd' => \$option_buildd, )) { require Pod::Usage; Pod::Usage::pod2usage(2); @@ -270,8 +346,28 @@ my $start = 0; my $continuation = 0; my $complete_line = undef; while (my $line = <>) { - # We skip over unimportant lines at the beginning to prevent false - # positives. + # dpkg-buildflags only provides hardening flags since 1.16.1, don't check + # for hardening flags in buildd mode if an older dpkg-dev is used. Default + # flags (-g -O2) are still checked. + # + # Packages which were built before 1.16.1 but used their own hardening + # flags are not checked. + if ($option_buildd and not $start + and $line =~ /^Toolchain package versions: /) { + require Dpkg::Version; + if ($line !~ /dpkg-dev_(\S+)/ + or Dpkg::Version::version_compare($1, '1.16.1') < 0) { + $harden_format = 0; + $harden_fortify = 0; + $harden_stack = 0; + $harden_relro = 0; + $harden_bindnow = 0; + $harden_pie = 0; + } + } + + # We skip over unimportant lines at the beginning of the log to prevent + # false positives. $start = 1 if $line =~ /^dpkg-buildpackage:/; next if not $start; @@ -287,7 +383,7 @@ while (my $line = <>) { # Remove all ANSI color sequences which are sometimes used in non-verbose # builds. $line = Term::ANSIColor::colorstrip($line); - # Also strip '\0xf' (delete previous character), used by Elink's build + # Also strip '\0xf' (delete previous character), used by Elinks' build # system. $line =~ s/\x0f//g; # And "ESC(B" which seems to be used on armhf and hurd (not sure what it @@ -298,8 +394,10 @@ while (my $line = <>) { my $non_verbose = is_non_verbose_build($line); # One line may contain multiple commands (";"). Treat each one as single - # line. - my @line = split /(?) { # # `./configure` output. next if not $non_verbose and $line =~ /^checking /; - next if $line =~ /^\s*(?:C\s+)? - (?:C|c)ompiler[\s.]*:\s+ + next if $line =~ /^\s*(?:Host\s+)?(?:C\s+)? + (?:C|c)ompiler[\s.]*:?\s+ $cc_regex (?:\s-std=[a-z0-9:+]+)?\s*$ /x - or $line =~ /^\s*(?:- )?(?:CC|CXX)\s*=\s*$cc_regex\s*$/ + or $line =~ /^\s*(?:- )?(?:HOST_)?(?:CC|CXX)\s*=\s*$cc_regex\s*$/ or $line =~ /^\s*-- Check for working (?:C|CXX) compiler: / or $line =~ /^\s*(?:echo )?Using [A-Z_]+\s*=\s*/; # Debian buildd output. @@ -423,47 +521,60 @@ for (my $i = 0; $i < scalar @input; $i++) { # Even if it's a verbose build, we might have to skip this line. next if $skip; + # Skip unnecessary tests when only preprocessing. + my $flag_preprocess = 0; + + my $preprocess = 0; + my $compile = 0; + my $link = 0; + + # Preprocess, compile, assemble. + if ($line =~ /$cc_regex.*?\s(-E|-S|-c)\b/) { + $preprocess = 1; + $flag_preprocess = 1 if $1 eq '-E'; + $compile = 1 if $1 eq '-S' or $1 eq '-c'; + # Otherwise assume we are linking. + } else { + $link = 1; + } - # Is this a compiler or linker command? - my $compiler = 1; - my $linker = 0; - - # Linker commands. - if ($line =~ m{\s-o # -o - [\s\\]*\s+ # possible line continuation - (?:[/.A-Za-z0-9~_-]+/)? # path to file - [A-Za-z0-9~_-]+ # binary name (no dots!) - (?:[0-9.]*\.so[0-9.]*[a-z]? # library (including version) - |\.la - |\.cgi)? # CGI binary - (?:\s|\\|$) # end of file name - }x - or $line =~ /^libtool: link: / - or $line =~ m{\s*/bin/bash .+?libtool\s+(.+?\s+)?--mode=(re)?link}) { - $compiler = 0; - $linker = 1; + # These file types don't require preprocessing. + if ($line =~ /$file_no_preprocess_regex/) { + $preprocess = 0; + } + # These file types require preprocessing. + if ($line =~ /$file_preprocess_regex/) { + $preprocess = 1; } # If there are source files then it's compiling/linking in one step and we - # must check both. - if ($line =~ /\.(?:c|cc|cpp)\b/) { - $compiler = 1; + # must check both. We only check for source files here, because header + # files cause too many false positives. + if (not $flag_preprocess and $line =~ /$file_compile_link_regex/) { + # Assembly files don't need CFLAGS. + if (not $line =~ /$file_compile_regex/ + and $line =~ /$file_no_compile_regex/) { + $compile = 0; + # But the rest does. + } else { + $compile = 1; + } } # Check hardening flags. my @missing; - if ($compiler and not all_flags_used($line, \@missing, @cflags) + if ($compile and not all_flags_used($line, \@missing, @cflags) # Libraries linked with -fPIC don't have to (and can't) be linked # with -fPIE as well. It's no error if only PIE flags are missing. and not pic_pie_conflict($line, $harden_pie, \@missing, @cflags_pie)) { error_flags('CFLAGS missing', \@missing, \%flag_renames, $line); $exit |= 1 << 3; } - if ($compiler and not all_flags_used($line, \@missing, @cppflags)) { + if ($preprocess and not all_flags_used($line, \@missing, @cppflags)) { error_flags('CPPFLAGS missing', \@missing, \%flag_renames, $line); $exit |= 1 << 3; } - if ($linker and not all_flags_used($line, \@missing, @ldflags) + if ($link and not all_flags_used($line, \@missing, @ldflags) # Same here, -fPIC conflicts with -fPIE. and not pic_pie_conflict($line, $harden_pie, \@missing, @ldflags_pie)) { error_flags('LDFLAGS missing', \@missing, \%flag_renames, $line); @@ -492,6 +603,7 @@ B [--pie] [--bindnow] [--all] --bindnow force +bindbow check --all force +all (+pie, +bindnow) check --arch set architecture (autodetected) + --buildd parser mode for buildds =head1 DESCRIPTION @@ -529,6 +641,20 @@ Set the specific architecture (e.g. amd64, armel, etc.), automatically disables hardening flags not available on this architecture. Is detected automatically if dpkg-buildpackage is used. +=item B<--buildd> + +Special mode for buildds when automatically parsing log files. The following +changes are in effect: + +=over 2 + +=item + +Don't check hardening flags in old log files (if dpkg-dev << 1.16.1 is +detected). + +=back + =back Auto detection for B<--pie> and B<--bindnow> only works if at least one