]> ruderich.org/simon Gitweb - blhc/blhc.git/blobdiff - bin/blhc
Whitespace only change.
[blhc/blhc.git] / bin / blhc
index 7b228ec13acc2eaa44ca83ad1ce98aede4744265..87037453ac493594c06b3339118d28621be94f12 100755 (executable)
--- a/bin/blhc
+++ b/bin/blhc
@@ -142,13 +142,13 @@ my %extension = map { $_ => 1 } (
 # Regexp to match file extensions.
 my $file_extension_regex = qr/
     \s
 # Regexp to match file extensions.
 my $file_extension_regex = qr/
     \s
-    \S+         # Filename without extension.
+    \S+             # Filename without extension.
     \.
     \.
-    ([^\\.\s]+) # File extension.
-    (?=\s|\\)   # At end of word. Can't use \b because some files have non
-                # word characters at the end and because \b matches double
-                # extensions (like .cpp.o). Works always as all lines are
-                # terminated with "\n".
+    ([^\\.,;:\s]+)  # File extension.
+    (?=\s|\\)       # At end of word. Can't use \b because some files have non
+                    # word characters at the end and because \b matches double
+                    # extensions (like .cpp.o). Works always as all lines are
+                    # terminated with "\n".
     /x;
 
 # Expected (hardening) flags. All flags are used as regexps.
     /x;
 
 # Expected (hardening) flags. All flags are used as regexps.
@@ -172,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 = ();
@@ -191,13 +190,34 @@ 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',
+    '-shared',
+);
+# 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;
 
@@ -207,12 +227,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",
@@ -247,7 +265,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;
@@ -257,7 +275,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;
         }
     }
@@ -273,7 +291,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;
 
@@ -315,7 +333,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) {
@@ -329,6 +347,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) = @_;
 
@@ -397,6 +433,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;
 
@@ -434,7 +486,11 @@ 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 ($line =~ /^Build-Depends: .*\bhardening-wrapper\b/) {
         # If hardening wrapper is used (wraps calls to gcc and adds hardening
         # flags automatically) we can't perform any checks, abort.
         if ($line =~ /^Build-Depends: .*\bhardening-wrapper\b/) {
-            error_hardening_wrapper();
+            if (not $option_buildd) {
+                error_hardening_wrapper();
+            } else {
+                print "I-hardening-wrapper-used\n";
+            }
             $exit |= 1 << 4;
             next FILE;
         }
             $exit |= 1 << 4;
             next FILE;
         }
@@ -516,23 +572,21 @@ FILE: foreach my $file (@ARGV) {
                 }
 
                 # Ignore lines with no compiler commands.
                 }
 
                 # Ignore lines with no compiler commands.
-                next if $line !~ /\b$cc_regex(?:\s|\\)/o and not $non_verbose;
+                next if not $non_verbose
+                        and not $line =~ /\b$cc_regex(?:\s|\\)/o;
+                # Ignore lines with no filenames with extensions. May miss
+                # some non-verbose builds (e.g. "gcc -o test" [sic!]), but
+                # shouldn't be a problem as the log will most likely contain
+                # other non-verbose commands which are detected.
+                next if not $non_verbose
+                        and not $line =~ /$file_extension_regex/o;
 
                 # Ignore false positives.
                 #
                 # `./configure` output.
                 next if not $non_verbose
                         and $line =~ /^(?:checking|(?:C|c)onfigure:) /;
 
                 # Ignore false positives.
                 #
                 # `./configure` output.
                 next if not $non_verbose
                         and $line =~ /^(?:checking|(?:C|c)onfigure:) /;
-                next if $line =~ /^\s*(?:Host\s+)?(?:C\s+)?
-                                   (?:C|c)ompiler[\s.]*:?\s+
-                                   $cc_regex_full
-                                   (?:\s-std=[a-z0-9:+]+)?\s*$
-                                 /xo
-                        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.
-                next if $line =~ /^Making [a-z]+ in \S+/; # e.g. "[...] in c++"
+                next if $line =~ /^\s*(?:- )?(?:HOST_)?(?:CC|CXX)\s*=\s*$cc_regex_full\s*$/o;
 
                 # Check if additional hardening options were used. Used to
                 # ensure they are used for the complete build.
 
                 # Check if additional hardening options were used. Used to
                 # ensure they are used for the complete build.
@@ -547,11 +601,19 @@ FILE: foreach my $file (@ARGV) {
     close $fh;
 
     if (scalar @input == 0) {
     close $fh;
 
     if (scalar @input == 0) {
-        print "No compiler commands!\n";
+        if (not $option_buildd) {
+            print "No compiler commands!\n";
+        } else {
+            print "W-no-compiler-commands\n";
+        }
         $exit |= 1;
         next FILE;
     }
 
         $exit |= 1;
         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
@@ -609,7 +671,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;
         }
@@ -619,6 +685,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;
@@ -675,6 +744,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)
@@ -684,7 +760,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
@@ -693,13 +773,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)
@@ -707,12 +795,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;
 
 
@@ -724,16 +853,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
 
@@ -749,7 +869,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