]> ruderich.org/simon Gitweb - blhc/blhc.git/blobdiff - bin/blhc
Use I<> for arguments in POD.
[blhc/blhc.git] / bin / blhc
index f1580217aaf64f94c482c63d9339cb84d10afed6..04b7125e87185f5c1706d4345685ffe75a2462b6 100755 (executable)
--- a/bin/blhc
+++ b/bin/blhc
@@ -31,9 +31,16 @@ our $VERSION = '0.01';
 # CONSTANTS/VARIABLES
 
 # Regex to catch compiler commands.
 # CONSTANTS/VARIABLES
 
 # Regex to catch compiler commands.
-my $cc_regex = qr/(?:[a-z0-9_]+-(?:linux-|kfreebsd-)?gnu(?:eabi|eabihf)?-)?
-                  (?<!\.)(?:cc|gcc|g\+\+|c\+\+)
-                  (?:-[\d.]+)?/x;
+my $cc_regex = qr/
+    (?<!\.)(?:cc|gcc|g\+\+|c\+\+)
+    (?:-[\d.]+)?
+    /x;
+# Full regex which matches the complete compiler name. Used in a few places to
+# prevent false negatives.
+my $cc_regex_full = qr/
+    (?:[a-z0-9_]+-(?:linux-|kfreebsd-)?gnu(?:eabi|eabihf)?-)?
+    $cc_regex
+    /x;
 # Regex to catch (GCC) compiler warnings.
 my $warning_regex = qr/^(.+?):([0-9]+):[0-9]+: warning: (.+?) \[(.+?)\]$/;
 
 # Regex to catch (GCC) compiler warnings.
 my $warning_regex = qr/^(.+?):([0-9]+):[0-9]+: warning: (.+?) \[(.+?)\]$/;
 
@@ -165,8 +172,7 @@ my @def_cflags_pie = (
     '-fPIE',
 );
 my @def_cxxflags = (
     '-fPIE',
 );
 my @def_cxxflags = (
-    '-g',
-    '-O(?:2|3)',
+    @def_cflags,
 );
 # @def_cxxflags_* is the same as @def_cflags_*.
 my @def_cppflags = ();
 );
 # @def_cxxflags_* is the same as @def_cflags_*.
 my @def_cppflags = ();
@@ -184,13 +190,33 @@ my @def_ldflags_pie = (
     '-fPIE',
     '-pie',
 );
     '-fPIE',
     '-pie',
 );
-# Renaming rules for the output so the regex parts are not visible.
+my @def_ldflags_pic = (
+    '-fPIC',
+    '-fpic',
+);
+# Renaming rules for the output so the regex parts are not visible. Also
+# stores string values of flag regexps above, see compile_flag_regexp().
 my %flag_renames = (
     '-O(?:2|3)'       => '-O2',
     '-Wl,(-z,)?relro' => '-Wl,-z,relro',
     '-Wl,(-z,)?now'   => '-Wl,-z,now',
 );
 
 my %flag_renames = (
     '-O(?:2|3)'       => '-O2',
     '-Wl,(-z,)?relro' => '-Wl,-z,relro',
     '-Wl,(-z,)?now'   => '-Wl,-z,now',
 );
 
+# Statistics of missing flags and non-verbose build commands. Used for
+# $option_buildd.
+my %statistics = (
+    preprocess          => 0,
+    preprocess_missing  => 0,
+    compile             => 0,
+    compile_missing     => 0,
+    compile_cpp         => 0,
+    compile_cpp_missing => 0,
+    link                => 0,
+    link_missing        => 0,
+    commands            => 0,
+    commands_nonverbose => 0,
+);
+
 # Use colored (ANSI) output?
 my $option_color;
 
 # Use colored (ANSI) output?
 my $option_color;
 
@@ -200,12 +226,10 @@ my $option_color;
 sub error_flags {
     my ($message, $missing_flags_ref, $flag_renames_ref, $line) = @_;
 
 sub error_flags {
     my ($message, $missing_flags_ref, $flag_renames_ref, $line) = @_;
 
-    # Rename flags if requested.
+    # Get string value of qr//-escaped regexps and if requested rename them.
     my @missing_flags = map {
     my @missing_flags = map {
-        (exists $flag_renames_ref->{$_})
-            ? $flag_renames_ref->{$_}
-            : $_
-    } @{$missing_flags_ref};
+            $flag_renames_ref->{$_}
+        } @{$missing_flags_ref};
 
     my $flags = join ' ', @missing_flags;
     printf "%s (%s)%s %s",
 
     my $flags = join ' ', @missing_flags;
     printf "%s (%s)%s %s",
@@ -240,7 +264,7 @@ sub any_flags_used {
     my ($line, @flags) = @_;
 
     foreach my $flag (@flags) {
     my ($line, @flags) = @_;
 
     foreach my $flag (@flags) {
-        return 1 if $line =~ /\s$flag(?:\s|\\)/;
+        return 1 if $line =~ /$flag/;
     }
 
     return 0;
     }
 
     return 0;
@@ -250,7 +274,7 @@ sub all_flags_used {
 
     my @missing_flags = ();
     foreach my $flag (@flags) {
 
     my @missing_flags = ();
     foreach my $flag (@flags) {
-        if ($line !~ /\s$flag(?:\s|\\)/) {
+        if (not $line =~ /$flag/) {
             push @missing_flags, $flag;
         }
     }
             push @missing_flags, $flag;
         }
     }
@@ -266,7 +290,7 @@ sub pic_pie_conflict {
     my ($line, $pie, $missing_flags_ref, @flags_pie) = @_;
 
     return 0 if not $pie;
     my ($line, $pie, $missing_flags_ref, @flags_pie) = @_;
 
     return 0 if not $pie;
-    return 0 if not any_flags_used($line, ('-fPIC', '-fpic'));
+    return 0 if not any_flags_used($line, @def_ldflags_pic);
 
     my %flags = map { $_ => 1 } @flags_pie;
 
 
     my %flags = map { $_ => 1 } @flags_pie;
 
@@ -308,7 +332,7 @@ sub is_non_verbose_build {
     if (defined $file) {
         # Get filename, we can't use the complete path as only parts of it are
         # used in the real compiler command.
     if (defined $file) {
         # Get filename, we can't use the complete path as only parts of it are
         # used in the real compiler command.
-        $file =~ m{/([a-zA-Z0-9._-]+)$};
+        $file =~ m{/([^/\s]+)$};
         $file = $1;
 
         if ($next_line =~ /\Q$file\E/ and $next_line =~ /$cc_regex/o) {
         $file = $1;
 
         if ($next_line =~ /\Q$file\E/ and $next_line =~ /$cc_regex/o) {
@@ -322,6 +346,24 @@ sub is_non_verbose_build {
     return 1;
 }
 
     return 1;
 }
 
+sub compile_flag_regexp {
+    my ($flag_renames_ref, @flags) = @_;
+
+    my @result = ();
+    foreach my $flag (@flags) {
+        # Store flag name in replacement string for correct flags in messages
+        # with qr//ed flag regexps.
+        $flag_renames_ref->{qr/\s$flag(?:\s|\\)/}
+            = (exists $flag_renames_ref->{$flag})
+                ? $flag_renames_ref->{$flag}
+                : $flag;
+
+        # Compile flag regexp for faster execution.
+        push @result, qr/\s$flag(?:\s|\\)/;
+    }
+    return @result;
+}
+
 sub extension_found {
     my ($extensions_ref, @extensions) = @_;
 
 sub extension_found {
     my ($extensions_ref, @extensions) = @_;
 
@@ -390,6 +432,22 @@ if ($option_all) {
     $option_bindnow = 1;
 }
 
     $option_bindnow = 1;
 }
 
+# Precompile all flag regexps. any_flags_used(), all_flags_used() get a lot
+# faster with this.
+@def_cflags           = compile_flag_regexp(\%flag_renames, @def_cflags);
+@def_cflags_format    = compile_flag_regexp(\%flag_renames, @def_cflags_format);
+@def_cflags_fortify   = compile_flag_regexp(\%flag_renames, @def_cflags_fortify);
+@def_cflags_stack     = compile_flag_regexp(\%flag_renames, @def_cflags_stack);
+@def_cflags_pie       = compile_flag_regexp(\%flag_renames, @def_cflags_pie);
+@def_cxxflags         = compile_flag_regexp(\%flag_renames, @def_cxxflags);
+@def_cppflags         = compile_flag_regexp(\%flag_renames, @def_cppflags);
+@def_cppflags_fortify = compile_flag_regexp(\%flag_renames, @def_cppflags_fortify);
+@def_ldflags          = compile_flag_regexp(\%flag_renames, @def_ldflags);
+@def_ldflags_relro    = compile_flag_regexp(\%flag_renames, @def_ldflags_relro);
+@def_ldflags_bindnow  = compile_flag_regexp(\%flag_renames, @def_ldflags_bindnow);
+@def_ldflags_pie      = compile_flag_regexp(\%flag_renames, @def_ldflags_pie);
+@def_ldflags_pic      = compile_flag_regexp(\%flag_renames, @def_ldflags_pic);
+
 # Final exit code.
 my $exit = 0;
 
 # Final exit code.
 my $exit = 0;
 
@@ -404,12 +462,6 @@ FILE: foreach my $file (@ARGV) {
     my $harden_bindnow = $option_bindnow; # defaults to 0
     my $harden_pie     = $option_pie;     # defaults to 0
 
     my $harden_bindnow = $option_bindnow; # defaults to 0
     my $harden_pie     = $option_pie;     # defaults to 0
 
-    # Input lines, contain only the lines with compiler commands.
-    my @input = ();
-
-    my $start = 0;
-    my $continuation = 0;
-    my $complete_line = undef;
     while (my $line = <$fh>) {
         # 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
     while (my $line = <$fh>) {
         # 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
@@ -417,8 +469,7 @@ FILE: foreach my $file (@ARGV) {
         #
         # Packages which were built before 1.16.1 but used their own hardening
         # flags are not 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: /) {
+        if ($option_buildd and $line =~ /^Toolchain package versions: /) {
             require Dpkg::Version;
             if ($line !~ /dpkg-dev_(\S+)/
                     or Dpkg::Version::version_compare($1, '1.16.1') < 0) {
             require Dpkg::Version;
             if ($line !~ /dpkg-dev_(\S+)/
                     or Dpkg::Version::version_compare($1, '1.16.1') < 0) {
@@ -433,16 +484,27 @@ FILE: foreach my $file (@ARGV) {
 
         # If hardening wrapper is used (wraps calls to gcc and adds hardening
         # flags automatically) we can't perform any checks, abort.
 
         # If hardening wrapper is used (wraps calls to gcc and adds hardening
         # flags automatically) we can't perform any checks, abort.
-        if (not $start and $line =~ /^Build-Depends: .*\bhardening-wrapper\b/) {
-            error_hardening_wrapper();
+        if ($line =~ /^Build-Depends: .*\bhardening-wrapper\b/) {
+            if (not $option_buildd) {
+                error_hardening_wrapper();
+            } else {
+                print "I-hardening-wrapper-used\n";
+            }
             $exit |= 1 << 4;
             next FILE;
         }
 
         # We skip over unimportant lines at the beginning of the log to
         # prevent false positives.
             $exit |= 1 << 4;
             next FILE;
         }
 
         # 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;
+        last if $line =~ /^dpkg-buildpackage:/;
+    }
+
+    # Input lines, contain only the lines with compiler commands.
+    my @input = ();
+
+    my $continuation = 0;
+    my $complete_line = undef;
+    while (my $line = <$fh>) {
         # And stop at the end of the build log. Package details (reported by
         # the buildd logs) are not important for us. This also prevents false
         # positives.
         # And stop at the end of the build log. Package details (reported by
         # the buildd logs) are not important for us. This also prevents false
         # positives.
@@ -518,10 +580,10 @@ FILE: foreach my $file (@ARGV) {
                         and $line =~ /^(?:checking|(?:C|c)onfigure:) /;
                 next if $line =~ /^\s*(?:Host\s+)?(?:C\s+)?
                                    (?:C|c)ompiler[\s.]*:?\s+
                         and $line =~ /^(?:checking|(?:C|c)onfigure:) /;
                 next if $line =~ /^\s*(?:Host\s+)?(?:C\s+)?
                                    (?:C|c)ompiler[\s.]*:?\s+
-                                   $cc_regex
+                                   $cc_regex_full
                                    (?:\s-std=[a-z0-9:+]+)?\s*$
                                  /xo
                                    (?:\s-std=[a-z0-9:+]+)?\s*$
                                  /xo
-                        or $line =~ /^\s*(?:- )?(?:HOST_)?(?:CC|CXX)\s*=\s*$cc_regex\s*$/o
+                        or $line =~ /^\s*(?:- )?(?:HOST_)?(?:CC|CXX)\s*=\s*$cc_regex_full\s*$/o
                         or $line =~ /^\s*-- Check for working (?:C|CXX) compiler: /
                         or $line =~ /^\s*(?:echo )?Using [A-Z_]+\s*=\s*/;
                 # `make` output.
                         or $line =~ /^\s*-- Check for working (?:C|CXX) compiler: /
                         or $line =~ /^\s*(?:echo )?Using [A-Z_]+\s*=\s*/;
                 # `make` output.
@@ -545,6 +607,10 @@ FILE: foreach my $file (@ARGV) {
         next FILE;
     }
 
         next FILE;
     }
 
+    if ($option_buildd) {
+        $statistics{commands} += scalar @input;
+    }
+
     # Option or auto detected.
     if ($option_arch) {
         # The following was partially copied from dpkg-dev 1.16.1.2
     # Option or auto detected.
     if ($option_arch) {
         # The following was partially copied from dpkg-dev 1.16.1.2
@@ -602,7 +668,11 @@ FILE: foreach my $file (@ARGV) {
 
         my $skip = 0;
         if (is_non_verbose_build($line, $input[$i + 1], \$skip)) {
 
         my $skip = 0;
         if (is_non_verbose_build($line, $input[$i + 1], \$skip)) {
-            error_non_verbose_build($line);
+            if (not $option_buildd) {
+                error_non_verbose_build($line);
+            } else {
+                $statistics{commands_nonverbose}++;
+            }
             $exit |= 1 << 2;
             next;
         }
             $exit |= 1 << 2;
             next;
         }
@@ -612,6 +682,9 @@ FILE: foreach my $file (@ARGV) {
         # Remove everything until and including the compiler command. Makes
         # checks easier and faster.
         $line =~ s/^.*?$cc_regex//o;
         # Remove everything until and including the compiler command. Makes
         # checks easier and faster.
         $line =~ s/^.*?$cc_regex//o;
+        # "([...] test.c)" is not detected as 'test.c' - fix this by removing
+        # the brace and similar characters.
+        $line =~ s/['")]+$//;
 
         # Skip unnecessary tests when only preprocessing.
         my $flag_preprocess = 0;
 
         # Skip unnecessary tests when only preprocessing.
         my $flag_preprocess = 0;
@@ -668,6 +741,13 @@ FILE: foreach my $file (@ARGV) {
             $compile_cpp = 1;
         }
 
             $compile_cpp = 1;
         }
 
+        if ($option_buildd) {
+            $statistics{preprocess}++  if $preprocess;
+            $statistics{compile}++     if $compile;
+            $statistics{compile_cpp}++ if $compile_cpp;
+            $statistics{link}++        if $link;
+        }
+
         # Check hardening flags.
         my @missing;
         if ($compile and not all_flags_used($line, \@missing, @cflags)
         # Check hardening flags.
         my @missing;
         if ($compile and not all_flags_used($line, \@missing, @cflags)
@@ -677,7 +757,11 @@ FILE: foreach my $file (@ARGV) {
                 and not pic_pie_conflict($line, $harden_pie, \@missing, @def_cflags_pie)
                 # Assume dpkg-buildflags returns the correct flags.
                 and not $line =~ /`dpkg-buildflags --get CFLAGS`/) {
                 and not pic_pie_conflict($line, $harden_pie, \@missing, @def_cflags_pie)
                 # Assume dpkg-buildflags returns the correct flags.
                 and not $line =~ /`dpkg-buildflags --get CFLAGS`/) {
-            error_flags('CFLAGS missing', \@missing, \%flag_renames, $input[$i]);
+            if (not $option_buildd) {
+                error_flags('CFLAGS missing', \@missing, \%flag_renames, $input[$i]);
+            } else {
+                $statistics{compile_missing}++;
+            }
             $exit |= 1 << 3;
         } elsif ($compile_cpp and not all_flags_used($line, \@missing, @cflags)
                 # Libraries linked with -fPIC don't have to (and can't) be
             $exit |= 1 << 3;
         } elsif ($compile_cpp and not all_flags_used($line, \@missing, @cflags)
                 # Libraries linked with -fPIC don't have to (and can't) be
@@ -686,13 +770,21 @@ FILE: foreach my $file (@ARGV) {
                 and not pic_pie_conflict($line, $harden_pie, \@missing, @def_cflags_pie)
                 # Assume dpkg-buildflags returns the correct flags.
                 and not $line =~ /`dpkg-buildflags --get CXXFLAGS`/) {
                 and not pic_pie_conflict($line, $harden_pie, \@missing, @def_cflags_pie)
                 # Assume dpkg-buildflags returns the correct flags.
                 and not $line =~ /`dpkg-buildflags --get CXXFLAGS`/) {
-            error_flags('CXXFLAGS missing', \@missing, \%flag_renames, $input[$i]);
+            if (not $option_buildd) {
+                error_flags('CXXFLAGS missing', \@missing, \%flag_renames, $input[$i]);
+            } else {
+                $statistics{compile_cpp_missing}++;
+            }
             $exit |= 1 << 3;
         }
         if ($preprocess and not all_flags_used($line, \@missing, @cppflags)
                 # Assume dpkg-buildflags returns the correct flags.
                 and not $line =~ /`dpkg-buildflags --get CPPFLAGS`/) {
             $exit |= 1 << 3;
         }
         if ($preprocess and not all_flags_used($line, \@missing, @cppflags)
                 # Assume dpkg-buildflags returns the correct flags.
                 and not $line =~ /`dpkg-buildflags --get CPPFLAGS`/) {
-            error_flags('CPPFLAGS missing', \@missing, \%flag_renames, $input[$i]);
+            if (not $option_buildd) {
+                error_flags('CPPFLAGS missing', \@missing, \%flag_renames, $input[$i]);
+            } else {
+                $statistics{preprocess_missing}++;
+            }
             $exit |= 1 << 3;
         }
         if ($link and not all_flags_used($line, \@missing, @ldflags)
             $exit |= 1 << 3;
         }
         if ($link and not all_flags_used($line, \@missing, @ldflags)
@@ -700,12 +792,53 @@ FILE: foreach my $file (@ARGV) {
                 and not pic_pie_conflict($line, $harden_pie, \@missing, @def_ldflags_pie)
                 # Assume dpkg-buildflags returns the correct flags.
                 and not $line =~ /`dpkg-buildflags --get LDFLAGS`/) {
                 and not pic_pie_conflict($line, $harden_pie, \@missing, @def_ldflags_pie)
                 # Assume dpkg-buildflags returns the correct flags.
                 and not $line =~ /`dpkg-buildflags --get LDFLAGS`/) {
-            error_flags('LDFLAGS missing', \@missing, \%flag_renames, $input[$i]);
+            if (not $option_buildd) {
+                error_flags('LDFLAGS missing', \@missing, \%flag_renames, $input[$i]);
+            } else {
+                $statistics{link_missing}++;
+            }
             $exit |= 1 << 3;
         }
     }
 }
 
             $exit |= 1 << 3;
         }
     }
 }
 
+# Print statistics for buildd mode, only output in this mode.
+if ($option_buildd) {
+    my @warning;
+
+    if ($statistics{preprocess_missing}) {
+        push @warning, sprintf "CPPFLAGS %d (of %d)",
+                               $statistics{preprocess_missing},
+                               $statistics{preprocess};
+    }
+    if ($statistics{compile_missing}) {
+        push @warning, sprintf "CFLAGS %d (of %d)",
+                               $statistics{compile_missing},
+                               $statistics{compile};
+    }
+    if ($statistics{compile_cpp_missing}) {
+        push @warning, sprintf "CXXFLAGS %d (of %d)",
+                               $statistics{compile_cpp_missing},
+                               $statistics{compile_cpp};
+    }
+    if ($statistics{link_missing}) {
+        push @warning, sprintf "LDFLAGS %d (of %d)",
+                               $statistics{link_missing},
+                               $statistics{link};
+    }
+    if (scalar @warning) {
+        local $" = ', '; # array join string
+        print "W-dpkg-buildflags-missing @warning missing\n";
+    }
+
+    if ($statistics{commands_nonverbose}) {
+        printf "W-compiler-flags-hidden %d (of %d) hidden\n",
+               $statistics{commands_nonverbose},
+               $statistics{commands},
+    }
+}
+
+
 exit $exit;
 
 
 exit $exit;
 
 
@@ -717,16 +850,7 @@ blhc - build log hardening check, checks build logs for missing hardening flags
 
 =head1 SYNOPSIS
 
 
 =head1 SYNOPSIS
 
-B<blhc> [options] <dpkg-buildpackage build log file>..
-
-    --all                   force +all (+pie, +bindnow) check
-    --arch                  set architecture (autodetected)
-    --bindnow               force +bindbow check
-    --buildd                parser mode for buildds
-    --color                 use colored output
-    --pie                   force +pie check
-    --help                  available options
-    --version               version number and license
+B<blhc> [I<options>] I<E<lt>dpkg-buildpackage build log fileE<gt>..>
 
 =head1 DESCRIPTION
 
 
 =head1 DESCRIPTION
 
@@ -742,7 +866,7 @@ other important warnings. It's licensed under the GPL 3 or later.
 Force check for all +all (+pie, +bindnow) hardening flags. By default it's
 auto detected.
 
 Force check for all +all (+pie, +bindnow) hardening flags. By default it's
 auto detected.
 
-=item B<--arch>
+=item B<--arch> I<architecture>
 
 Set the specific architecture (e.g. amd64, armel, etc.), automatically
 disables hardening flags not available on this architecture. Is detected
 
 Set the specific architecture (e.g. amd64, armel, etc.), automatically
 disables hardening flags not available on this architecture. Is detected