]> ruderich.org/simon Gitweb - blhc/blhc.git/blobdiff - bin/blhc
update copyright years
[blhc/blhc.git] / bin / blhc
index 89cde257f5a7e5c39619759578a06b6ee88d0280..d2a8771515e5e01d8a3890db542cb7cf08244f21 100755 (executable)
--- a/bin/blhc
+++ b/bin/blhc
@@ -2,7 +2,7 @@
 
 # Build log hardening check, checks build logs for missing hardening flags.
 
-# Copyright (C) 2012  Simon Ruderich
+# Copyright (C) 2012-2015  Simon Ruderich
 #
 # 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
@@ -24,7 +24,7 @@ use warnings;
 use Getopt::Long ();
 use Text::ParseWords ();
 
-our $VERSION = '0.03';
+our $VERSION = '0.05';
 
 
 # CONSTANTS/VARIABLES
@@ -48,6 +48,9 @@ my $cc_regex_normal = qr/
     /x;
 # Regex to catch (GCC) compiler warnings.
 my $warning_regex = qr/^(.+?):(\d+):\d+: warning: (.+?) \[(.+?)\]$/;
+# Regex to catch libtool commands and not lines which show commands executed
+# by libtool (e.g. libtool: link: ...).
+my $libtool_regex = qr/\blibtool\s.*--mode=/;
 
 # List of source file extensions which require preprocessing.
 my @source_preprocess_compile_cpp = (
@@ -81,6 +84,12 @@ my @source_no_preprocess_compile_cpp = (
     # Objective-C++
     qw( mii ),
 );
+my @source_no_preprocess_compile_ada = (
+    # Ada body
+    qw( adb ),
+    # If you add another file, fix use of @source_no_preprocess_compile_ada
+    # below (search for $compile_ada).
+);
 my @source_no_preprocess_compile = (
     # C
     qw( i ),
@@ -90,10 +99,14 @@ my @source_no_preprocess_compile = (
     qw( mi ),
     # Fortran
     qw( f for ftn f90 f95 f03 f08 ),
+    # Ada
+    @source_no_preprocess_compile_ada,
 );
 my @source_no_preprocess_no_compile = (
     # Assembly
     qw( s ),
+    # Ada specification
+    qw( ads ),
 );
 my @source_no_preprocess = (
     @source_no_preprocess_compile,
@@ -166,13 +179,19 @@ my $file_extension_regex = qr/
                     # terminated with "\n".
     /x;
 
-# Expected (hardening) flags. All flags are used as regexps.
+# Expected (hardening) flags. All flags are used as regexps (and compiled to
+# real regexps below for better execution speed).
 my @def_cflags = (
     '-g',
-    '-O(?:2|3)',
+    '-O(?:2|3)', # keep at index 1, search for @def_cflags_debug to change it
+);
+my @def_cflags_debug = (
+    # These flags indicate a debug build which disables checks for -O2.
+    '-O0',
+    '-Og',
 );
 my @def_cflags_format = (
-    '-Wformat',
+    '-Wformat(?:=2)?', # -Wformat=2 implies -Wformat, accept it too
     '-Werror=format-security', # implies -Wformat-security
 );
 my @def_cflags_fortify = (
@@ -180,7 +199,10 @@ my @def_cflags_fortify = (
 );
 my @def_cflags_stack = (
     '-fstack-protector',
-    '--param=ssp-buffer-size=4',
+    '--param[= ]ssp-buffer-size=4',
+);
+my @def_cflags_stack_strong = (
+    '-fstack-protector-strong',
 );
 my @def_cflags_pie = (
     '-fPIE',
@@ -192,7 +214,8 @@ my @def_cxxflags = (
 my @def_cppflags = ();
 my @def_cppflags_fortify = (
     '-D_FORTIFY_SOURCE=2', # must be first, see cppflags_fortify_broken()
-    # If you add another flag fix hack below (search for "Hack to fix").
+    # If you add another flag fix hack below (search for "Hack to fix") and
+    # $def_cppflags_fortify[0].
 );
 my @def_cppflags_fortify_bad = (
     # These flags may overwrite -D_FORTIFY_SOURCE=2.
@@ -222,6 +245,7 @@ my @flag_refs = (
     \@def_cflags_format,
     \@def_cflags_fortify,
     \@def_cflags_stack,
+    \@def_cflags_stack_strong,
     \@def_cflags_pie,
     \@def_cxxflags,
     \@def_cppflags,
@@ -234,15 +258,18 @@ my @flag_refs = (
 # References to all used flags.
 my @flag_refs_all = (
     @flag_refs,
+    \@def_cflags_debug,
     \@def_cppflags_fortify_bad,
     \@def_ldflags_pic,
 );
 # 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',
+    '-O(?:2|3)'                    => '-O2',
+    '-Wformat(?:=2)?'              => '-Wformat',
+    '--param[= ]ssp-buffer-size=4' => '--param=ssp-buffer-size=4',
+    '-Wl,(?:-z,)?relro'            => '-Wl,-z,relro',
+    '-Wl,(?:-z,)?now'              => '-Wl,-z,now',
 );
 
 my %exit_code = (
@@ -283,6 +310,20 @@ my $option_color;
 
 # FUNCTIONS
 
+# Only works for single-level arrays with no undef values. Thanks to perlfaq4.
+sub array_equal {
+    my ($first_ref, $second_ref) = @_;
+
+    return 0 if scalar @{$first_ref} != scalar @{$second_ref};
+
+    my $length = scalar @{$first_ref};
+    for (my $i = 0; $i < $length; $i++) {
+        return 0 if $first_ref->[$i] ne $second_ref->[$i];
+    }
+
+    return 1;
+}
+
 sub error_flags {
     my ($message, $missing_flags_ref, $flag_renames_ref, $line) = @_;
 
@@ -295,6 +336,8 @@ sub error_flags {
     printf '%s (%s)%s %s',
            error_color($message, 'red'), $flags, error_color(':', 'yellow'),
            $line;
+
+    return;
 }
 sub error_non_verbose_build {
     my ($line) = @_;
@@ -303,6 +346,8 @@ sub error_non_verbose_build {
            error_color('NONVERBOSE BUILD', 'red'),
            error_color(':', 'yellow'),
            $line;
+
+    return;
 }
 sub error_invalid_cmake {
     my ($version) = @_;
@@ -311,12 +356,16 @@ sub error_invalid_cmake {
             error_color('INVALID CMAKE', 'red'),
             error_color(':', 'yellow'),
             $version;
+
+    return;
 }
 sub error_hardening_wrapper {
     printf "%s%s %s\n",
             error_color('HARDENING WRAPPER', 'red'),
             error_color(':', 'yellow'),
             'no checks possible, aborting';
+
+    return;
 }
 sub error_color {
     my ($message, $color) = @_;
@@ -394,6 +443,20 @@ sub pic_pie_conflict {
 sub is_non_verbose_build {
     my ($line, $next_line, $skip_ref) = @_;
 
+    if ($line =~ /$libtool_regex/o) {
+        # libtool's --silent hides the real compiler flags.
+        if ($line =~ /\s--silent/) {
+            return 1;
+        # If --silent is not present, skip this line as some compiler flags
+        # might be missing (e.g. -fPIE) which are handled correctly by libtool
+        # internally. libtool displays the real compiler command on the next
+        # line, so the flags are checked as usual.
+        } else {
+            ${$skip_ref} = 1;
+            return 0;
+        }
+    }
+
     if (not (index($line, 'checking if you want to see long compiling messages... no') == 0
                 or $line =~ /^\s*\[?(?:CC|CCLD|C\+\+|CXX|CXXLD|LD|LINK)\]?\s+(.+?)$/
                 or $line =~ /^\s*[Cc]ompiling\s+(.+?)(?:\.\.\.)?$/
@@ -406,6 +469,9 @@ sub is_non_verbose_build {
     #
     # C++ compiler setting.
     return 0 if $line =~ /^\s*C\+\+.+?:\s+(?:yes|no)\s*$/;
+    return 0 if $line =~ /^\s*C\+\+ Library: stdc\+\+$/;
+    # "Compiling" non binary files.
+    return 0 if $line =~ /^\s*Compiling \S+\.(?:py|el)['"]?\s*(?:\.\.\.)?$/;
     # "Compiling" with no file name.
     if ($line =~ /^\s*[Cc]ompiling\s+(.+?)(?:\.\.\.)?$/) {
         # $file_extension_regex may need spaces around the filename.
@@ -430,8 +496,8 @@ sub is_non_verbose_build {
         $file = $1;
 
         if (index($next_line, $file) != -1 and $next_line =~ /$cc_regex/o) {
-            # We still have to skip the current line as it doesn't contain any
-            # compiler commands.
+            # Not a non-verbose line, but we still have to skip the current line
+            # as it doesn't contain any compiler commands.
             ${$skip_ref} = 1;
             return 0;
         }
@@ -440,6 +506,7 @@ sub is_non_verbose_build {
     return 1;
 }
 
+# Remove @flags from $flag_refs_ref, uses $flag_renames_ref as reference.
 sub remove_flags {
     my ($flag_refs_ref, $flag_renames_ref, @flags) = @_;
 
@@ -453,37 +520,41 @@ sub remove_flags {
                         or not exists $removes{$flag_renames_ref->{$_}})
         } @{$flags};
     }
+
+    return;
 }
 
+# Modifies $flag_renames_ref hash.
 sub compile_flag_regexp {
     my ($flag_renames_ref, @flags) = @_;
 
     my @result = ();
     foreach my $flag (@flags) {
+        # Compile flag regexp for faster execution.
+        my $regex = qr/\s$flag(?:\s|\\)/;
+
         # Store flag name in replacement string for correct flags in messages
         # with qr//ed flag regexps.
-        $flag_renames_ref->{qr/\s$flag(?:\s|\\)/}
+        $flag_renames_ref->{$regex}
             = (exists $flag_renames_ref->{$flag})
                 ? $flag_renames_ref->{$flag}
                 : $flag;
 
-        # Compile flag regexp for faster execution.
-        push @result, qr/\s$flag(?:\s|\\)/;
+        push @result, $regex;
     }
     return @result;
 }
 
+# Does any extension in @extensions exist in %{$extensions_ref}?
 sub extension_found {
     my ($extensions_ref, @extensions) = @_;
 
-    my $found = 0;
     foreach my $extension (@extensions) {
         if (exists $extensions_ref->{$extension}) {
-            $found = 1;
-            last;
+            return 1;
         }
     }
-    return $found;
+    return 0;
 }
 
 
@@ -529,7 +600,8 @@ if ($option_help) {
     Pod::Usage::pod2usage(1);
 }
 if ($option_version) {
-    print "blhc $VERSION  Copyright (C) 2012  Simon Ruderich
+    print <<"EOF";
+blhc $VERSION  Copyright (C) 2012-2015  Simon Ruderich
 
 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
@@ -543,7 +615,7 @@ 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, see <http://www.gnu.org/licenses/>.
-";
+EOF
     exit 0;
 }
 
@@ -575,7 +647,7 @@ if (scalar @option_ignore_flag > 0) {
 # Same for arch specific ignore flags, but only prepare here.
 if (scalar @option_ignore_arch_flag > 0) {
     foreach my $ignore (@option_ignore_arch_flag) {
-        my ($ignore_arch, $ignore_flag) = split ':', $ignore, 2;
+        my ($ignore_arch, $ignore_flag) = split /:/, $ignore, 2;
 
         if (not $ignore_arch or not $ignore_flag) {
             printf STDERR 'Value "%s" invalid for option ignore-arch-flag '
@@ -601,7 +673,7 @@ foreach my $ignore (@option_ignore_line) {
 # Same for arch specific ignore lines.
 if (scalar @option_ignore_arch_line > 0) {
     foreach my $ignore (@option_ignore_arch_line) {
-        my ($ignore_arch, $ignore_line) = split ':', $ignore, 2;
+        my ($ignore_arch, $ignore_line) = split /:/, $ignore, 2;
 
         if (not $ignore_arch or not $ignore_line) {
             printf STDERR 'Value "%s" invalid for option ignore-arch-line '
@@ -632,18 +704,28 @@ foreach my $file (@ARGV) {
     my $harden_format  = 1;
     my $harden_fortify = 1;
     my $harden_stack   = 1;
+    my $harden_stack_strong = 1;
     my $harden_relro   = 1;
     my $harden_bindnow = $option_bindnow; # defaults to 0
     my $harden_pie     = $option_pie;     # defaults to 0
 
+    # Does this build log use ada? Ada also uses gcc as compiler but uses
+    # different CFLAGS. But only perform ada checks if an ada compiler used
+    # for performance reasons.
+    my $ada = 0;
+
     while (my $line = <$fh>) {
         # Detect architecture automatically unless overridden. For buildd logs
         # only, doesn't use the dpkg-buildpackage header. Necessary to ignore
         # build logs which aren't built (wrong architecture, build error,
         # etc.).
-        if (not $arch
-                and $line =~ /^Architecture: (.+)$/) {
-            $arch = $1;
+        if (not $arch) {
+            if (index($line, 'Build Architecture: ') == 0) {
+                $arch = substr $line, 20, -1; # -1 to ignore '\n' at the end
+            # For old logs (sbuild << 0.63.0-1).
+            } elsif (index($line, 'Architecture: ') == 0) {
+                $arch = substr $line, 14, -1; # -1 to ignore '\n' at the end
+            }
         }
 
         # dpkg-buildflags only provides hardening flags since 1.16.1, don't
@@ -652,11 +734,25 @@ foreach my $file (@ARGV) {
         #
         # Packages which were built before 1.16.1 but used their own hardening
         # flags are not checked.
+        #
+        # Strong stack protector is used since dpkg 1.17.11.
         if ($option_buildd
                 and index($line, 'Toolchain package versions: ') == 0) {
             require Dpkg::Version;
-            if (not $line =~ /\bdpkg-dev_(\S+)/
-                    or Dpkg::Version::version_compare($1, '1.16.1') < 0) {
+
+            my $disable = 1;
+            my $disable_strong = 1;
+
+            if ($line =~ /\bdpkg-dev_(\S+)/) {
+                if (Dpkg::Version::version_compare($1, '1.16.1') >= 0) {
+                    $disable = 0;
+                }
+                if (Dpkg::Version::version_compare($1, '1.17.11') >= 0) {
+                    $disable_strong = 0;
+                }
+            }
+
+            if ($disable) {
                 $harden_format  = 0;
                 $harden_fortify = 0;
                 $harden_stack   = 0;
@@ -664,6 +760,9 @@ foreach my $file (@ARGV) {
                 $harden_bindnow = 0;
                 $harden_pie     = 0;
             }
+            if ($disable_strong) {
+                $harden_stack_strong = 0;
+            }
         }
 
         # The following two versions of CMake in Debian obeyed CPPFLAGS, but
@@ -683,17 +782,29 @@ 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 (index($line, 'Build-Depends: ') == 0
-                and $line =~ /\bhardening-wrapper\b/) {
-            if (not $option_buildd) {
-                error_hardening_wrapper();
-                $exit |= $exit_code{hardening_wrapper};
-            } else {
-                print "$buildd_tag{hardening_wrapper}||\n";
+        # Debian's build daemons use "Filtered Build-Depends:" (or just
+        # "Build-Depends:" in older versions) for the build dependencies, but
+        # pbuilder uses "Depends:"; support both.
+        if (index($line, 'Filtered Build-Depends: ') == 0
+                or index($line, 'Build-Depends: ') == 0
+                or index($line, 'Depends: ') == 0) {
+            # If hardening wrapper is used (wraps calls to gcc and adds
+            # hardening flags automatically) we can't perform any checks,
+            # abort.
+            if ($line =~ /\bhardening-wrapper\b/) {
+                if (not $option_buildd) {
+                    error_hardening_wrapper();
+                    $exit |= $exit_code{hardening_wrapper};
+                } else {
+                    print "$buildd_tag{hardening_wrapper}||\n";
+                }
+                next FILE;
+            }
+
+            # Ada compiler.
+            if ($line =~ /\bgnat\b/) {
+                $ada = 1;
             }
-            next FILE;
         }
 
         # We skip over unimportant lines at the beginning of the log to
@@ -703,25 +814,42 @@ foreach my $file (@ARGV) {
 
     # Input lines, contain only the lines with compiler commands.
     my @input = ();
+    # Non-verbose lines in the input. Used to reduce calls to
+    # is_non_verbose_build() (which is quite slow) in the second loop when
+    # it's already clear if a line is non-verbose or not.
+    my @input_nonverbose = ();
 
     my $continuation = 0;
     my $complete_line = undef;
+    my $non_verbose;
     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.
-        last if $line =~ /^Build finished at \d{8}-\d{4}$/;
+        last if index($line, 'Build finished at ') == 0
+                and $line =~ /^Build finished at \d{8}-\d{4}$/;
+
+        if (not $continuation) {
+            $non_verbose = 0;
+        }
 
         # Detect architecture automatically unless overridden.
         if (not $arch
-                and $line =~ /^dpkg-buildpackage: host architecture (.+)$/) {
-            $arch = $1;
+                and index($line, 'dpkg-buildpackage: host architecture ') == 0) {
+            $arch = substr $line, 37, -1; # -1 to ignore '\n' at the end
+
+            # Old buildd logs use e.g. "host architecture is alpha", remove
+            # the "is", otherwise debarch_to_debtriplet() will not detect the
+            # architecture.
+            if (index($arch, 'is ') == 0) {
+                $arch = substr $arch, 3;
+            }
         }
 
         # Ignore compiler warnings for now.
         next if $line =~ /$warning_regex/o;
 
-        if (not $option_buildd and index($line, "\033") != -1) { # esc
+        if (not $option_buildd and index($line, "\033") != -1) { # \033 = esc
             # Remove all ANSI color sequences which are sometimes used in
             # non-verbose builds.
             $line = Term::ANSIColor::colorstrip($line);
@@ -734,7 +862,9 @@ foreach my $file (@ARGV) {
         }
 
         # Check if this line indicates a non verbose build.
-        my $non_verbose = is_non_verbose_build($line);
+        my $skip = 0;
+        $non_verbose |= is_non_verbose_build($line, undef, \$skip);
+        next if $skip;
 
         # One line may contain multiple commands (";"). Treat each one as
         # single line. parse_line() is slow, only use it when necessary.
@@ -788,14 +918,21 @@ foreach my $file (@ARGV) {
                     and $line =~ /^(?:checking|[Cc]onfigure:) /;
             next if $line =~ /^\s*(?:Host\s+)?(?:C(?:\+\+)?\s+)?
                                 [Cc]ompiler[\s.]*:?\s+
-                                /xo;
-            next if $line =~ /^\s*(?:- )?(?:HOST_)?(?:CC|CXX)\s*=\s*$cc_regex_full\s*$/o;
-            # `moc-qt4`, contains '-I/usr/share/qt4/mkspecs/linux-g++' (or
-            # similar for other architectures) which gets recognized as a
-            # compiler line. Ignore it.
-            next if $line =~ m{^/usr/bin/moc-qt4
+                                /x;
+            next if $line =~ m{^\s*(?:-\s)?(?:HOST_)?(?:CC|CXX)
+                                \s*=\s*$cc_regex_full
+                                # optional compiler options, don't allow
+                                # "everything" here to prevent false negatives
+                                \s*(?:\s-\S+)*\s*$}xo;
+            # `moc-qt4`/`moc-qt5` contain '-I.../linux-g++' in their command
+            # line (or similar for other architectures) which gets recognized
+            # as a compiler line, but `moc-qt*` is only a preprocessor for Qt
+            # C++ files. No hardening flags are relevant during this step,
+            # thus ignore `moc-qt*` lines. The resulting files will be
+            # compiled in a separate step (and therefore checked).
+            next if $line =~ m{^\S+/bin/moc(?:-qt[45])?
                                \s.+\s
-                               -I/usr/share/qt4/mkspecs/[a-z]+-g\++(?:-64)?
+                               -I\S+/mkspecs/[a-z]+-g\++(?:-64)?
                                \s}x;
             # Ignore false positives when the line contains only CC=gcc but no
             # other gcc command.
@@ -813,6 +950,7 @@ foreach my $file (@ARGV) {
             $harden_bindnow = 1 if any_flags_used($line, @def_ldflags_bindnow);
 
             push @input, $line;
+            push @input_nonverbose, $non_verbose;
         }
     }
 
@@ -844,7 +982,7 @@ foreach my $file (@ARGV) {
 
     # Option or auto detected.
     if ($arch) {
-        # The following was partially copied from dpkg-dev 1.16.1.2
+        # The following was partially copied from dpkg-dev 1.17.11
         # (/usr/share/perl5/Dpkg/Vendor/Debian.pm, add_hardening_flags()),
         # copyright RaphaĆ«l Hertzog <hertzog@debian.org>, Kees Cook
         # <kees@debian.org>, Canonical, Ltd. licensed under GPL version 2 or
@@ -854,8 +992,12 @@ foreach my $file (@ARGV) {
         my ($abi, $os, $cpu) = Dpkg::Arch::debarch_to_debtriplet($arch);
 
         # Disable unsupported hardening options.
-        if ($cpu =~ /^(?:ia64|alpha|mips|mipsel|hppa)$/ or $arch eq 'arm') {
+        if ($os !~ /^(?:linux|knetbsd|hurd)$/ or $cpu =~ /^(?:hppa|avr32)$/) {
+            $harden_pie = 0;
+        }
+        if ($cpu =~ /^(?:ia64|alpha|hppa)$/ or $arch eq 'arm') {
             $harden_stack = 0;
+            $harden_stack_strong = 0;
         }
         if ($cpu =~ /^(?:ia64|hppa|avr32)$/) {
             $harden_relro   = 0;
@@ -874,7 +1016,10 @@ foreach my $file (@ARGV) {
         @cxxflags = (@cxxflags, @def_cflags_pie);
         @ldflags  = (@ldflags,  @def_ldflags_pie);
     }
-    if ($harden_stack) {
+    if ($harden_stack_strong) {
+        @cflags   = (@cflags,   @def_cflags_stack_strong);
+        @cxxflags = (@cxxflags, @def_cflags_stack_strong);
+    } elsif ($harden_stack) {
         @cflags   = (@cflags,   @def_cflags_stack);
         @cxxflags = (@cxxflags, @def_cflags_stack);
     }
@@ -894,6 +1039,22 @@ foreach my $file (@ARGV) {
         @ldflags = (@ldflags, @def_ldflags_bindnow);
     }
 
+    # Stores normal CFLAGS when @cflags_ada are temporarily used.
+    my @cflags_backup;
+    # Ada CFLAGS, only set if ada is used.
+    my @cflags_ada;
+    # Ada doesn't support format hardening flags, see #680117 for more
+    # information. Filter them out if ada is used.
+    if ($ada and $harden_format) {
+        @cflags_ada = grep {
+            my $ok = 1;
+            foreach my $flag (@def_cflags_format) {
+                $ok = 0 if $_ eq $flag;
+            }
+            $ok;
+        } @cflags;
+    }
+
     # Hack to fix cppflags_fortify_broken() if --ignore-flag
     # -D_FORTIFY_SOURCE=2 is used to ignore missing fortification. Only works
     # as long as @def_cppflags_fortify contains only one variable.
@@ -903,9 +1064,9 @@ foreach my $file (@ARGV) {
 
     # Ignore flags for this arch if requested.
     if ($arch and exists $option_ignore_arch_flag{$arch}) {
-        my @flag_refs = (\@cflags, \@cxxflags, \@cppflags, \@ldflags);
+        my @local_flag_refs = (\@cflags, \@cxxflags, \@cppflags, \@ldflags);
 
-        remove_flags(\@flag_refs,
+        remove_flags(\@local_flag_refs,
                      \%flag_renames,
                      @{$option_ignore_arch_flag{$arch}});
     }
@@ -926,7 +1087,8 @@ LINE:
         }
 
         my $skip = 0;
-        if (is_non_verbose_build($line, $input[$i + 1], \$skip)) {
+        if ($input_nonverbose[$i]
+                and is_non_verbose_build($line, $input[$i + 1], \$skip)) {
             if (not $option_buildd) {
                 error_non_verbose_build($line);
                 $exit |= $exit_code{non_verbose_build};
@@ -935,14 +1097,15 @@ LINE:
             }
             next;
         }
-        # Even if it's a verbose build, we might have to skip this line.
+        # Even if it's a verbose build, we might have to skip this line (see
+        # is_non_verbose_build()).
         next if $skip;
 
         # 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.
+        # the brace and similar characters at the line end.
         $line =~ s/['")]+$//;
 
         # Skip unnecessary tests when only preprocessing.
@@ -1017,13 +1180,22 @@ LINE:
             }
         }
 
+        my $compile_cpp = 0;
+        my $compile_ada = 0;
         # Assume CXXFLAGS are required when a C++ file is specified in the
         # compiler line.
-        my $compile_cpp = 0;
         if ($compile
                 and extension_found(\%extensions_compile_cpp, @extensions)) {
             $compile     = 0;
             $compile_cpp = 1;
+        # Ada needs special CFLAGS, use them if only ada files are compiled.
+        } elsif ($ada
+                    and $compile
+                    and array_equal(\@extensions,
+                                    \@source_no_preprocess_compile_ada)) {
+            $compile_ada = 1;
+            @cflags_backup = @cflags;
+            @cflags        = @cflags_ada;
         }
 
         if ($option_buildd) {
@@ -1033,6 +1205,14 @@ LINE:
             $statistics{link}++        if $link;
         }
 
+        # Check if there are flags indicating a debug build. If that's true,
+        # skip the check for -O2. This prevents fortification, but that's fine
+        # for a debug build.
+        if (any_flags_used($line, @def_cflags_debug)) {
+            remove_flags([\@cflags], \%flag_renames, $def_cflags[1]);
+            remove_flags([\@cppflags], \%flag_renames, $def_cppflags_fortify[0]);
+        }
+
         # Check hardening flags.
         my @missing;
         if ($compile and not all_flags_used($line, \@missing, @cflags)
@@ -1088,6 +1268,11 @@ LINE:
                 $statistics{link_missing}++;
             }
         }
+
+        # Restore normal CFLAGS.
+        if ($compile_ada) {
+            @cflags = @cflags_backup;
+        }
     }
 }
 
@@ -1147,14 +1332,18 @@ blhc is a small tool which checks build logs for missing hardening flags. It's
 licensed under the GPL 3 or later.
 
 It's designed to check build logs generated by Debian's dpkg-buildpackage (or
-tools using dpkg-buildpackage like pbuilder or the official buildd build logs)
-to help maintainers detect missing hardening flags in their packages.
+tools using dpkg-buildpackage like pbuilder or sbuild (which is used for the
+official buildd build logs)) to help maintainers detect missing hardening
+flags in their packages.
 
 Only gcc is detected as compiler at the moment. If other compilers support
 hardening flags as well, please report them.
 
 If there's no output, no flags are missing and the build log is fine.
 
+See F<README> for details about performed checks, auto-detection and
+limitations.
+
 =head1 OPTIONS
 
 =over 8
@@ -1381,9 +1570,9 @@ Simon Ruderich, E<lt>simon@ruderich.orgE<gt>
 Thanks to to Bernhard R. Link E<lt>brlink@debian.orgE<gt> and Jaria Alto
 E<lt>jari.aalto@cante.netE<gt> for their valuable input and suggestions.
 
-=head1 COPYRIGHT AND LICENSE
+=head1 LICENSE AND COPYRIGHT
 
-Copyright (C) 2012 by Simon Ruderich
+Copyright (C) 2012-2015 by Simon Ruderich
 
 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