]> ruderich.org/simon Gitweb - blhc/blhc.git/blob - bin/blhc
Remove unnecessary capture groups.
[blhc/blhc.git] / bin / blhc
1 #!/usr/bin/perl
2
3 # Build log hardening check, checks build logs for missing hardening flags.
4
5 # Copyright (C) 2012  Simon Ruderich
6 #
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation, either version 3 of the License, or
10 # (at your option) any later version.
11 #
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 # GNU General Public License for more details.
16 #
17 # You should have received a copy of the GNU General Public License
18 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
19
20
21 use strict;
22 use warnings;
23
24 use Getopt::Long ();
25 use Text::ParseWords ();
26
27 our $VERSION = '0.01';
28
29
30 # CONSTANTS/VARIABLES
31
32 # Regex to catch compiler commands.
33 my $cc_regex = qr/
34     (?<!\.)(?:cc|gcc|g\+\+|c\+\+)
35     (?:-[\d.]+)?
36     /x;
37 # Full regex which matches the complete compiler name. Used in a few places to
38 # prevent false negatives.
39 my $cc_regex_full = qr/
40     (?:[a-z0-9_]+-(?:linux-|kfreebsd-)?gnu(?:eabi|eabihf)?-)?
41     $cc_regex
42     /x;
43 # Regex to catch (GCC) compiler warnings.
44 my $warning_regex = qr/^(.+?):([0-9]+):[0-9]+: warning: (.+?) \[(.+?)\]$/;
45
46 # List of source file extensions which require preprocessing.
47 my @source_preprocess_compile_cpp = (
48     # C++
49     qw( cc cp cxx cpp CPP c++ C ),
50     # Objective-C++
51     qw( mm M ),
52 );
53 my @source_preprocess_compile = (
54     # C
55     qw( c ),
56     # Objective-C
57     qw( m ),
58     # (Objective-)C++
59     @source_preprocess_compile_cpp,
60     # Fortran
61     qw( F FOR fpp FPP FTN F90 F95 F03 F08 ),
62 );
63 my @source_preprocess_no_compile = (
64     # Assembly
65     qw( S sx ),
66 );
67 my @source_preprocess = (
68     @source_preprocess_compile,
69     @source_preprocess_no_compile,
70 );
71 # List of source file extensions which don't require preprocessing.
72 my @source_no_preprocess_compile_cpp = (
73     # C++
74     qw( ii ),
75     # Objective-C++
76     qw( mii ),
77 );
78 my @source_no_preprocess_compile = (
79     # C
80     qw( i ),
81     # (Objective-)C++
82     @source_no_preprocess_compile_cpp,
83     # Objective-C
84     qw( mi ),
85     # Fortran
86     qw( f for ftn f90 f95 f03 f08 ),
87 );
88 my @source_no_preprocess_no_compile = (
89     # Assembly
90     qw( s ),
91 );
92 my @source_no_preprocess = (
93     @source_no_preprocess_compile,
94     @source_no_preprocess_no_compile,
95 );
96 # List of header file extensions which require preprocessing.
97 my @header_preprocess = (
98     # C, C++, Objective-C, Objective-C++
99     qw( h ),
100     # C++
101     qw( hh H hp hxx hpp HPP h++ tcc ),
102 );
103
104 # Hashes for fast extensions lookup to check if a file falls in one of these
105 # categories.
106 my %extensions_no_preprocess = map { $_ => 1 } (
107     @source_no_preprocess,
108 );
109 my %extensions_preprocess = map { $_ => 1 } (
110     @header_preprocess,
111     @source_preprocess,
112 );
113 my %extensions_compile_link = map { $_ => 1 } (
114     @source_preprocess,
115     @source_no_preprocess,
116 );
117 my %extensions_compile = map { $_ => 1 } (
118     @source_preprocess_compile,
119     @source_no_preprocess_compile,
120 );
121 my %extensions_no_compile = map { $_ => 1 } (
122     @source_preprocess_no_compile,
123     @source_no_preprocess_no_compile,
124 );
125 my %extensions_compile_cpp = map { $_ => 1 } (
126     @source_preprocess_compile_cpp,
127     @source_no_preprocess_compile_cpp,
128 );
129 my %extension = map { $_ => 1 } (
130     @source_no_preprocess,
131     @header_preprocess,
132     @source_preprocess,
133 );
134
135 # Regexp to match file extensions.
136 my $file_extension_regex = qr/
137     \s
138     \S+             # Filename without extension.
139     \.
140     ([^\/\\.,;:\s]+)# File extension.
141     (?=\s|\\)       # At end of word. Can't use \b because some files have non
142                     # word characters at the end and because \b matches double
143                     # extensions (like .cpp.o). Works always as all lines are
144                     # terminated with "\n".
145     /x;
146
147 # Expected (hardening) flags. All flags are used as regexps.
148 my @def_cflags = (
149     '-g',
150     '-O(?:2|3)',
151 );
152 my @def_cflags_format = (
153     '-Wformat',
154     '-Wformat-security',
155     '-Werror=format-security',
156 );
157 my @def_cflags_fortify = (
158     # fortify needs at least -O1, but -O2 is recommended anyway
159 );
160 my @def_cflags_stack = (
161     '-fstack-protector',
162     '--param=ssp-buffer-size=4',
163 );
164 my @def_cflags_pie = (
165     '-fPIE',
166 );
167 my @def_cxxflags = (
168     @def_cflags,
169 );
170 # @def_cxxflags_* is the same as @def_cflags_*.
171 my @def_cppflags = ();
172 my @def_cppflags_fortify = (
173     '-D_FORTIFY_SOURCE=2',
174 );
175 my @def_ldflags = ();
176 my @def_ldflags_relro = (
177     '-Wl,(?:-z,)?relro',
178 );
179 my @def_ldflags_bindnow = (
180     '-Wl,(?:-z,)?now',
181 );
182 my @def_ldflags_pie = (
183     '-fPIE',
184     '-pie',
185 );
186 my @def_ldflags_pic = (
187     '-fPIC',
188     '-fpic',
189     '-shared',
190 );
191 # References to all flags checked by the parser.
192 my @flag_refs = (
193     \@def_cflags,
194     \@def_cflags_format,
195     \@def_cflags_fortify,
196     \@def_cflags_stack,
197     \@def_cflags_pie,
198     \@def_cxxflags,
199     \@def_cppflags,
200     \@def_cppflags_fortify,
201     \@def_ldflags,
202     \@def_ldflags_relro,
203     \@def_ldflags_bindnow,
204 );
205 # References to all used flags.
206 my @flag_refs_all = (
207     @flag_refs,
208     \@def_ldflags_pie,
209     \@def_ldflags_pic,
210 );
211 # Renaming rules for the output so the regex parts are not visible. Also
212 # stores string values of flag regexps above, see compile_flag_regexp().
213 my %flag_renames = (
214     '-O(?:2|3)'         => '-O2',
215     '-Wl,(?:-z,)?relro' => '-Wl,-z,relro',
216     '-Wl,(?:-z,)?now'   => '-Wl,-z,now',
217 );
218
219 my %exit_code = (
220     no_compiler_commands => 1 << 0,
221     # used by POD::Usage => 1 << 1,
222     non_verbose_build    => 1 << 2,
223     flags_missing        => 1 << 3,
224     hardening_wrapper    => 1 << 4,
225     invalid_cmake        => 1 << 5,
226 );
227
228 # Statistics of missing flags and non-verbose build commands. Used for
229 # $option_buildd.
230 my %statistics = (
231     preprocess          => 0,
232     preprocess_missing  => 0,
233     compile             => 0,
234     compile_missing     => 0,
235     compile_cpp         => 0,
236     compile_cpp_missing => 0,
237     link                => 0,
238     link_missing        => 0,
239     commands            => 0,
240     commands_nonverbose => 0,
241 );
242
243 # Use colored (ANSI) output?
244 my $option_color;
245
246
247 # FUNCTIONS
248
249 sub error_flags {
250     my ($message, $missing_flags_ref, $flag_renames_ref, $line) = @_;
251
252     # Get string value of qr//-escaped regexps and if requested rename them.
253     my @missing_flags = map {
254             $flag_renames_ref->{$_}
255         } @{$missing_flags_ref};
256
257     my $flags = join ' ', @missing_flags;
258     printf "%s (%s)%s %s",
259            error_color($message, 'red'), $flags, error_color(':', 'yellow'),
260            $line;
261 }
262 sub error_non_verbose_build {
263     my ($line) = @_;
264
265     printf "%s%s %s",
266            error_color('NONVERBOSE BUILD', 'red'),
267            error_color(':', 'yellow'),
268            $line;
269 }
270 sub error_invalid_cmake {
271     my ($version) = @_;
272
273     printf "%s%s %s\n",
274             error_color('INVALID CMAKE', 'red'),
275             error_color(':', 'yellow'),
276             $version;
277 }
278 sub error_hardening_wrapper {
279     printf "%s%s %s\n",
280             error_color('HARDENING WRAPPER', 'red'),
281             error_color(':', 'yellow'),
282             'no checks possible, aborting';
283 }
284 sub error_color {
285     my ($message, $color) = @_;
286
287     if ($option_color) {
288         return Term::ANSIColor::colored($message, $color);
289     } else {
290         return $message;
291     }
292 }
293
294 sub any_flags_used {
295     my ($line, @flags) = @_;
296
297     foreach my $flag (@flags) {
298         return 1 if $line =~ /$flag/;
299     }
300
301     return 0;
302 }
303 sub all_flags_used {
304     my ($line, $missing_flags_ref, @flags) = @_;
305
306     my @missing_flags = ();
307     foreach my $flag (@flags) {
308         if (not $line =~ /$flag/) {
309             push @missing_flags, $flag;
310         }
311     }
312
313     return 1 if scalar @missing_flags == 0;
314
315     @{$missing_flags_ref} = @missing_flags;
316     return 0;
317 }
318
319 # Modifies $missing_flags_ref array.
320 sub pic_pie_conflict {
321     my ($line, $pie, $missing_flags_ref, @flags_pie) = @_;
322
323     return 0 if not $pie;
324     return 0 if not any_flags_used($line, @def_ldflags_pic);
325
326     my %flags = map { $_ => 1 } @flags_pie;
327
328     # Remove all PIE flags from @missing_flags as they are not required with
329     # -fPIC.
330     my @result = grep {
331         not exists $flags{$_}
332     } @{$missing_flags_ref};
333     @{$missing_flags_ref} = @result;
334
335     # We got a conflict when no flags are left, thus only PIE flags were
336     # missing. If other flags were missing abort because the conflict is not
337     # the problem.
338     return scalar @result == 0;
339 }
340
341 sub is_non_verbose_build {
342     my ($line, $next_line, $skip_ref) = @_;
343
344     if (not ($line =~ /^checking if you want to see long compiling messages\.\.\. no/
345                 or $line =~ /^\s*\[?(?:CC|CCLD|C\+\+|CXX|CXXLD|LD|LINK)\]?\s+(.+?)$/
346                 or $line =~ /^\s*(?:C|c)ompiling\s+(.+?)(?:\.\.\.)?$/
347                 or $line =~ /^\s*(?:B|b)uilding (?:program|shared library)\s+(.+?)$/
348                 or $line =~ /^\s*\[[\d ]+%\] Building (?:C|CXX) object (.+?)$/)) {
349         return 0;
350     }
351
352     # False positives.
353     #
354     # C++ compiler setting.
355     return 0 if $line =~ /^\s*C\+\+.+?:\s+(?:yes|no)\s*$/;
356     # "Compiling" with no file name.
357     if ($line =~ /^\s*(?:C|c)ompiling\s+(.+?)(?:\.\.\.)?$/) {
358         # $file_extension_regex may need spaces around the filename.
359         return 0 if not " $1 " =~ /$file_extension_regex/o;
360     }
361
362     my $file = $1;
363
364     # On the first pass we only check if this line is verbose or not.
365     return 1 if not defined $next_line;
366
367     # Second pass, we have access to the next line.
368     ${$skip_ref} = 0;
369
370     # CMake and other build systems print the non-verbose messages also when
371     # building verbose. If a compiler and the file name occurs in the next
372     # line, treat it as verbose build.
373     if (defined $file) {
374         # Get filename, we can't use the complete path as only parts of it are
375         # used in the real compiler command.
376         $file =~ m{/([^/\s]+)$};
377         $file = $1;
378
379         if ($next_line =~ /\Q$file\E/ and $next_line =~ /$cc_regex/o) {
380             # We still have to skip the current line as it doesn't contain any
381             # compiler commands.
382             ${$skip_ref} = 1;
383             return 0;
384         }
385     }
386
387     return 1;
388 }
389
390 sub compile_flag_regexp {
391     my ($flag_renames_ref, @flags) = @_;
392
393     my @result = ();
394     foreach my $flag (@flags) {
395         # Store flag name in replacement string for correct flags in messages
396         # with qr//ed flag regexps.
397         $flag_renames_ref->{qr/\s$flag(?:\s|\\)/}
398             = (exists $flag_renames_ref->{$flag})
399                 ? $flag_renames_ref->{$flag}
400                 : $flag;
401
402         # Compile flag regexp for faster execution.
403         push @result, qr/\s$flag(?:\s|\\)/;
404     }
405     return @result;
406 }
407
408 sub extension_found {
409     my ($extensions_ref, @extensions) = @_;
410
411     my $found = 0;
412     foreach my $extension (@extensions) {
413         if (exists $extensions_ref->{$extension}) {
414             $found = 1;
415             last;
416         }
417     }
418     return $found;
419 }
420
421
422 # MAIN
423
424 # Parse command line arguments.
425 my $option_help        = 0;
426 my $option_version     = 0;
427 my $option_pie         = 0;
428 my $option_bindnow     = 0;
429 my @option_ignore_flag = ();
430 my @option_ignore_line = ();
431 my $option_all         = 0;
432 my $option_arch        = undef;
433 my $option_buildd      = 0;
434    $option_color       = 0;
435 if (not Getopt::Long::GetOptions(
436             'help|h|?'      => \$option_help,
437             'version'       => \$option_version,
438             # Hardening options.
439             'pie'           => \$option_pie,
440             'bindnow'       => \$option_bindnow,
441             'all'           => \$option_all,
442             # Ignore.
443             'ignore-flag=s' => \@option_ignore_flag,
444             'ignore-line=s' => \@option_ignore_line,
445             # Misc.
446             'color'         => \$option_color,
447             'arch=s'        => \$option_arch,
448             'buildd'        => \$option_buildd,
449         )
450         or scalar @ARGV == 0) {
451     require Pod::Usage;
452     Pod::Usage::pod2usage(2);
453 }
454 if ($option_help) {
455     require Pod::Usage;
456     Pod::Usage::pod2usage(1);
457 }
458 if ($option_version) {
459     print "blhc $VERSION  Copyright (C) 2012  Simon Ruderich
460
461 This program is free software: you can redistribute it and/or modify
462 it under the terms of the GNU General Public License as published by
463 the Free Software Foundation, either version 3 of the License, or
464 (at your option) any later version.
465
466 This program is distributed in the hope that it will be useful,
467 but WITHOUT ANY WARRANTY; without even the implied warranty of
468 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
469 GNU General Public License for more details.
470
471 You should have received a copy of the GNU General Public License
472 along with this program.  If not, see <http://www.gnu.org/licenses/>.
473 ";
474     exit 0;
475 }
476
477 # Don't load Term::ANSIColor in buildd mode because Term::ANSIColor is not
478 # installed on Debian's buildds.
479 if (not $option_buildd) {
480     require Term::ANSIColor;
481 }
482
483 if ($option_all) {
484     $option_pie     = 1;
485     $option_bindnow = 1;
486 }
487
488 # Strip flags which should be ignored.
489 if (scalar @option_ignore_flag > 0) {
490     my %ignores = map { $_ => 1 } @option_ignore_flag;
491     foreach my $flags (@flag_refs) {
492         @{$flags} = grep {
493             # Flag found as string.
494             not exists $ignores{$_}
495             # Flag found as string representation of regexp.
496                 and (not defined $flag_renames{$_}
497                         or not exists $ignores{$flag_renames{$_}})
498             } @{$flags};
499     }
500 }
501
502 # Precompile all flag regexps. any_flags_used(), all_flags_used() get a lot
503 # faster with this.
504 foreach my $flags (@flag_refs_all) {
505     @{$flags} = compile_flag_regexp(\%flag_renames, @{$flags});
506 }
507
508 # Precompile ignore line regexps, also anchor at beginning and end of line.
509 foreach my $ignore (@option_ignore_line) {
510     $ignore = qr/^$ignore$/;
511 }
512
513 # Final exit code.
514 my $exit = 0;
515
516 FILE:
517 foreach my $file (@ARGV) {
518     print "checking '$file'...\n" if scalar @ARGV > 1;
519
520     open my $fh, '<', $file or die "$!: $file";
521
522     # Architecture of this file.
523     my $arch = $option_arch;
524
525     # Hardening options. Not all architectures support all hardening options.
526     my $harden_format  = 1;
527     my $harden_fortify = 1;
528     my $harden_stack   = 1;
529     my $harden_relro   = 1;
530     my $harden_bindnow = $option_bindnow; # defaults to 0
531     my $harden_pie     = $option_pie;     # defaults to 0
532
533     while (my $line = <$fh>) {
534         # dpkg-buildflags only provides hardening flags since 1.16.1, don't
535         # check for hardening flags in buildd mode if an older dpkg-dev is
536         # used. Default flags (-g -O2) are still checked.
537         #
538         # Packages which were built before 1.16.1 but used their own hardening
539         # flags are not checked.
540         if ($option_buildd and $line =~ /^Toolchain package versions: /) {
541             require Dpkg::Version;
542             if ($line !~ /\bdpkg-dev_(\S+)/
543                     or Dpkg::Version::version_compare($1, '1.16.1') < 0) {
544                 $harden_format  = 0;
545                 $harden_fortify = 0;
546                 $harden_stack   = 0;
547                 $harden_relro   = 0;
548                 $harden_bindnow = 0;
549                 $harden_pie     = 0;
550             }
551         }
552
553         # The following two versions of CMake in Debian obeyed CPPFLAGS, but
554         # this was later dropped because upstream rejected the patch. Thus
555         # build logs with these versions will have fortify hardening flags
556         # enabled, even though they may be not correctly set and are missing
557         # when build with later CMake versions. Thanks to Aron Xu for letting
558         # me know.
559         if ($line =~ /^Package versions: /
560                 and $line =~ /\bcmake_(\S+)/
561                 and ($1 eq '2.8.7-1' or $1 eq '2.8.7-2')) {
562             if (not $option_buildd) {
563                 error_invalid_cmake($1);
564             } else {
565                 print "W-invalid-cmake-used $1\n";
566             }
567             $exit |= $exit_code{invalid_cmake};
568         }
569
570         # If hardening wrapper is used (wraps calls to gcc and adds hardening
571         # flags automatically) we can't perform any checks, abort.
572         if ($line =~ /^Build-Depends: .*\bhardening-wrapper\b/) {
573             if (not $option_buildd) {
574                 error_hardening_wrapper();
575             } else {
576                 print "I-hardening-wrapper-used\n";
577             }
578             $exit |= $exit_code{hardening_wrapper};
579             next FILE;
580         }
581
582         # We skip over unimportant lines at the beginning of the log to
583         # prevent false positives.
584         last if $line =~ /^dpkg-buildpackage:/;
585     }
586
587     # Input lines, contain only the lines with compiler commands.
588     my @input = ();
589
590     my $continuation = 0;
591     my $complete_line = undef;
592     while (my $line = <$fh>) {
593         # And stop at the end of the build log. Package details (reported by
594         # the buildd logs) are not important for us. This also prevents false
595         # positives.
596         last if $line =~ /^Build finished at \d{8}-\d{4}$/;
597
598         # Detect architecture automatically unless overridden.
599         if (not $arch
600                 and $line =~ /^dpkg-buildpackage: host architecture (.+)$/) {
601             $arch = $1;
602         }
603
604         # Ignore compiler warnings for now.
605         next if $line =~ /$warning_regex/o;
606
607         if (not $option_buildd and $line =~ /\033/) { # esc
608             # Remove all ANSI color sequences which are sometimes used in
609             # non-verbose builds.
610             $line = Term::ANSIColor::colorstrip($line);
611             # Also strip '\0xf' (delete previous character), used by Elinks'
612             # build system.
613             $line =~ s/\x0f//g;
614             # And "ESC(B" which seems to be used on armhf and hurd (not sure
615             # what it does).
616             $line =~ s/\033\(B//g;
617         }
618
619         # Check if this line indicates a non verbose build.
620         my $non_verbose = is_non_verbose_build($line);
621
622         # One line may contain multiple commands (";"). Treat each one as
623         # single line. parse_line() is slow, only use it when necessary.
624         my @line = (not $line =~ /;/)
625                  ? ($line)
626                  : map {
627                        # Ensure newline at the line end - necessary for
628                        # correct parsing later.
629                        $_ =~ s/\s+$//;
630                        $_ .= "\n";
631                    } Text::ParseWords::parse_line(';', 1, $line);
632         foreach $line (@line) {
633             if ($continuation) {
634                 $continuation = 0;
635
636                 # Join lines, but leave the "\" in place so it's clear where
637                 # the original line break was.
638                 chomp $complete_line;
639                 $complete_line .= ' ' . $line;
640             }
641             # Line continuation, line ends with "\".
642             if ($line =~ /\\\s*$/) {
643                 $continuation = 1;
644                 # Start line continuation.
645                 if (not defined $complete_line) {
646                     $complete_line = $line;
647                 }
648                 next;
649             }
650
651             # Use the complete line if a line continuation occurred.
652             if (defined $complete_line) {
653                 $line = $complete_line;
654                 $complete_line = undef;
655             }
656
657             # Ignore lines with no compiler commands.
658             next if not $non_verbose
659                     and not $line =~ /\b$cc_regex(?:\s|\\)/o;
660             # Ignore lines with no filenames with extensions. May miss some
661             # non-verbose builds (e.g. "gcc -o test" [sic!]), but shouldn't be
662             # a problem as the log will most likely contain other non-verbose
663             # commands which are detected.
664             next if not $non_verbose
665                     and not $line =~ /$file_extension_regex/o;
666
667             # Ignore false positives.
668             #
669             # `./configure` output.
670             next if not $non_verbose
671                     and $line =~ /^(?:checking|(?:C|c)onfigure:) /;
672             next if $line =~ /^\s*(?:Host\s+)?(?:C(?:\+\+)?\s+)?
673                                 (?:C|c)ompiler[\s.]*:?\s+
674                                 /xo;
675             next if $line =~ /^\s*(?:- )?(?:HOST_)?(?:CC|CXX)\s*=\s*$cc_regex_full\s*$/o;
676
677             # Check if additional hardening options were used. Used to ensure
678             # they are used for the complete build.
679             $harden_pie     = 1 if any_flags_used($line, @def_cflags_pie, @def_ldflags_pie);
680             $harden_bindnow = 1 if any_flags_used($line, @def_ldflags_bindnow);
681
682             push @input, $line;
683         }
684     }
685
686     close $fh;
687
688     if (scalar @input == 0) {
689         if (not $option_buildd) {
690             print "No compiler commands!\n";
691         } else {
692             print "W-no-compiler-commands\n";
693         }
694         $exit |= $exit_code{no_compiler_commands};
695         next FILE;
696     }
697
698     if ($option_buildd) {
699         $statistics{commands} += scalar @input;
700     }
701
702     # Option or auto detected.
703     if ($arch) {
704         # The following was partially copied from dpkg-dev 1.16.1.2
705         # (/usr/share/perl5/Dpkg/Vendor/Debian.pm, add_hardening_flags()),
706         # copyright Raphaël Hertzog <hertzog@debian.org>, Kees Cook
707         # <kees@debian.org>, Canonical, Ltd. licensed under GPL version 2 or
708         # later. Keep it in sync.
709
710         require Dpkg::Arch;
711         my ($abi, $os, $cpu) = Dpkg::Arch::debarch_to_debtriplet($arch);
712
713         # Disable unsupported hardening options.
714         if ($cpu =~ /^(?:ia64|alpha|mips|mipsel|hppa)$/ or $arch eq 'arm') {
715             $harden_stack = 0;
716         }
717         if ($cpu =~ /^(?:ia64|hppa|avr32)$/) {
718             $harden_relro   = 0;
719             $harden_bindnow = 0;
720         }
721     }
722
723     # Default values.
724     my @cflags   = @def_cflags;
725     my @cxxflags = @def_cxxflags;
726     my @cppflags = @def_cppflags;
727     my @ldflags  = @def_ldflags;
728     # Check the specified hardening options, same order as dpkg-buildflags.
729     if ($harden_pie) {
730         @cflags   = (@cflags,   @def_cflags_pie);
731         @cxxflags = (@cxxflags, @def_cflags_pie);
732         @ldflags  = (@ldflags,  @def_ldflags_pie);
733     }
734     if ($harden_stack) {
735         @cflags   = (@cflags,   @def_cflags_stack);
736         @cxxflags = (@cxxflags, @def_cflags_stack);
737     }
738     if ($harden_fortify) {
739         @cflags   = (@cflags,   @def_cflags_fortify);
740         @cxxflags = (@cxxflags, @def_cflags_fortify);
741         @cppflags = (@cppflags, @def_cppflags_fortify);
742     }
743     if ($harden_format) {
744         @cflags   = (@cflags,   @def_cflags_format);
745         @cxxflags = (@cxxflags, @def_cflags_format);
746     }
747     if ($harden_relro) {
748         @ldflags = (@ldflags, @def_ldflags_relro);
749     }
750     if ($harden_bindnow) {
751         @ldflags = (@ldflags, @def_ldflags_bindnow);
752     }
753
754 LINE:
755     for (my $i = 0; $i < scalar @input; $i++) {
756         my $line = $input[$i];
757
758         # Ignore line if requested.
759         foreach my $ignore (@option_ignore_line) {
760             next LINE if $line =~ /$ignore/;
761         }
762
763         my $skip = 0;
764         if (is_non_verbose_build($line, $input[$i + 1], \$skip)) {
765             if (not $option_buildd) {
766                 error_non_verbose_build($line);
767             } else {
768                 $statistics{commands_nonverbose}++;
769             }
770             $exit |= $exit_code{non_verbose_build};
771             next;
772         }
773         # Even if it's a verbose build, we might have to skip this line.
774         next if $skip;
775
776         # Remove everything until and including the compiler command. Makes
777         # checks easier and faster.
778         $line =~ s/^.*?$cc_regex//o;
779         # "([...] test.c)" is not detected as 'test.c' - fix this by removing
780         # the brace and similar characters.
781         $line =~ s/['")]+$//;
782
783         # Skip unnecessary tests when only preprocessing.
784         my $flag_preprocess = 0;
785
786         my $dependency = 0;
787         my $preprocess = 0;
788         my $compile    = 0;
789         my $link       = 0;
790
791         # Preprocess, compile, assemble.
792         if ($line =~ /\s(-E|-S|-c)\b/) {
793             $preprocess      = 1;
794             $flag_preprocess = 1 if $1 eq '-E';
795             $compile         = 1 if $1 eq '-S' or $1 eq '-c';
796         # Dependency generation for Makefiles. The other flags (-MF -MG -MP
797         # -MT -MQ) are always used with -M/-MM.
798         } elsif ($line =~ /\s(?:-M|-MM)\b/) {
799             $dependency = 1;
800         # Otherwise assume we are linking.
801         } else {
802             $link = 1;
803         }
804
805         # -MD/-MMD also cause dependency generation, but they don't imply -E!
806         if ($line =~ /\s(?:-MD|-MMD)\b/) {
807             $dependency      = 0;
808             $flag_preprocess = 0;
809         }
810
811         # Dependency generation for Makefiles, no preprocessing or other flags
812         # needed.
813         next if $dependency;
814
815         # Get all file extensions on this line.
816         my @extensions = $line =~ /$file_extension_regex/go;
817         # Ignore all unknown extensions to speedup the search below.
818         @extensions = grep { exists $extension{$_} } @extensions;
819
820         # These file types don't require preprocessing.
821         if (extension_found(\%extensions_no_preprocess, @extensions)) {
822             $preprocess = 0;
823         }
824         # These file types require preprocessing.
825         if (extension_found(\%extensions_preprocess, @extensions)) {
826             $preprocess = 1;
827         }
828
829         # If there are source files then it's compiling/linking in one step
830         # and we must check both. We only check for source files here, because
831         # header files cause too many false positives.
832         if (not $flag_preprocess
833                 and extension_found(\%extensions_compile_link, @extensions)) {
834             # Assembly files don't need CFLAGS.
835             if (not extension_found(\%extensions_compile, @extensions)
836                     and extension_found(\%extensions_no_compile, @extensions)) {
837                 $compile = 0;
838             # But the rest does.
839             } else {
840                 $compile = 1;
841             }
842         }
843
844         # Assume CXXFLAGS are required when a C++ file is specified in the
845         # compiler line.
846         my $compile_cpp = 0;
847         if ($compile
848                 and extension_found(\%extensions_compile_cpp, @extensions)) {
849             $compile     = 0;
850             $compile_cpp = 1;
851         }
852
853         if ($option_buildd) {
854             $statistics{preprocess}++  if $preprocess;
855             $statistics{compile}++     if $compile;
856             $statistics{compile_cpp}++ if $compile_cpp;
857             $statistics{link}++        if $link;
858         }
859
860         # Check hardening flags.
861         my @missing;
862         if ($compile and not all_flags_used($line, \@missing, @cflags)
863                 # Libraries linked with -fPIC don't have to (and can't) be
864                 # linked with -fPIE as well. It's no error if only PIE flags
865                 # are missing.
866                 and not pic_pie_conflict($line, $harden_pie, \@missing, @def_cflags_pie)
867                 # Assume dpkg-buildflags returns the correct flags.
868                 and not $line =~ /`dpkg-buildflags --get CFLAGS`/) {
869             if (not $option_buildd) {
870                 error_flags('CFLAGS missing', \@missing, \%flag_renames, $input[$i]);
871             } else {
872                 $statistics{compile_missing}++;
873             }
874             $exit |= $exit_code{flags_missing};
875         } elsif ($compile_cpp and not all_flags_used($line, \@missing, @cflags)
876                 # Libraries linked with -fPIC don't have to (and can't) be
877                 # linked with -fPIE as well. It's no error if only PIE flags
878                 # are missing.
879                 and not pic_pie_conflict($line, $harden_pie, \@missing, @def_cflags_pie)
880                 # Assume dpkg-buildflags returns the correct flags.
881                 and not $line =~ /`dpkg-buildflags --get CXXFLAGS`/) {
882             if (not $option_buildd) {
883                 error_flags('CXXFLAGS missing', \@missing, \%flag_renames, $input[$i]);
884             } else {
885                 $statistics{compile_cpp_missing}++;
886             }
887             $exit |= $exit_code{flags_missing};
888         }
889         if ($preprocess and not all_flags_used($line, \@missing, @cppflags)
890                 # Assume dpkg-buildflags returns the correct flags.
891                 and not $line =~ /`dpkg-buildflags --get CPPFLAGS`/) {
892             if (not $option_buildd) {
893                 error_flags('CPPFLAGS missing', \@missing, \%flag_renames, $input[$i]);
894             } else {
895                 $statistics{preprocess_missing}++;
896             }
897             $exit |= $exit_code{flags_missing};
898         }
899         if ($link and not all_flags_used($line, \@missing, @ldflags)
900                 # Same here, -fPIC conflicts with -fPIE.
901                 and not pic_pie_conflict($line, $harden_pie, \@missing, @def_ldflags_pie)
902                 # Assume dpkg-buildflags returns the correct flags.
903                 and not $line =~ /`dpkg-buildflags --get LDFLAGS`/) {
904             if (not $option_buildd) {
905                 error_flags('LDFLAGS missing', \@missing, \%flag_renames, $input[$i]);
906             } else {
907                 $statistics{link_missing}++;
908             }
909             $exit |= $exit_code{flags_missing};
910         }
911     }
912 }
913
914 # Print statistics for buildd mode, only output in this mode.
915 if ($option_buildd) {
916     my @warning;
917
918     if ($statistics{preprocess_missing}) {
919         push @warning, sprintf "CPPFLAGS %d (of %d)",
920                                $statistics{preprocess_missing},
921                                $statistics{preprocess};
922     }
923     if ($statistics{compile_missing}) {
924         push @warning, sprintf "CFLAGS %d (of %d)",
925                                $statistics{compile_missing},
926                                $statistics{compile};
927     }
928     if ($statistics{compile_cpp_missing}) {
929         push @warning, sprintf "CXXFLAGS %d (of %d)",
930                                $statistics{compile_cpp_missing},
931                                $statistics{compile_cpp};
932     }
933     if ($statistics{link_missing}) {
934         push @warning, sprintf "LDFLAGS %d (of %d)",
935                                $statistics{link_missing},
936                                $statistics{link};
937     }
938     if (scalar @warning) {
939         local $" = ', '; # array join string
940         print "W-dpkg-buildflags-missing @warning missing\n";
941     }
942
943     if ($statistics{commands_nonverbose}) {
944         printf "W-compiler-flags-hidden %d (of %d) hidden\n",
945                $statistics{commands_nonverbose},
946                $statistics{commands},
947     }
948 }
949
950
951 exit $exit;
952
953
954 __END__
955
956 =head1 NAME
957
958 blhc - build log hardening check, checks build logs for missing hardening flags
959
960 =head1 SYNOPSIS
961
962 B<blhc> [I<options>] I<E<lt>dpkg-buildpackage build log fileE<gt>..>
963
964 =head1 DESCRIPTION
965
966 blhc is a small tool which checks build logs for missing hardening flags. It's
967 licensed under the GPL 3 or later.
968
969 It's designed to check build logs generated by Debian's dpkg-buildpackage (or
970 tools using dpkg-buildpackage like pbuilder or the official buildd build logs)
971 to help maintainers detect missing hardening flags in their packages.
972
973 =head1 OPTIONS
974
975 =over 8
976
977 =item B<--all>
978
979 Force check for all +all (+pie, +bindnow) hardening flags. By default it's
980 auto detected.
981
982 =item B<--arch> I<architecture>
983
984 Set the specific architecture (e.g. amd64, armel, etc.), automatically
985 disables hardening flags not available on this architecture. Is detected
986 automatically if dpkg-buildpackage is used.
987
988 =item B<--bindnow>
989
990 Force check for all +bindnow hardening flags. By default it's auto detected.
991
992 =item B<--buildd>
993
994 Special mode for buildds when automatically parsing log files. The following
995 changes are in effect:
996
997 =over 2
998
999 =item
1000
1001 Print tags instead of normal warnings, see L</"BUILDD TAGS"> for a list of
1002 possible tags.
1003
1004 =item
1005
1006 Don't check hardening flags in old log files (if dpkg-dev << 1.16.1 is
1007 detected).
1008
1009 =item
1010
1011 Don't require Term::ANSIColor.
1012
1013 =back
1014
1015 =item B<--color>
1016
1017 Use colored (ANSI) output for warning messages.
1018
1019 =item B<--ignore-flag> I<flag>
1020
1021 Don't print an error when the specific flag is missing in a compiler line.
1022 I<flag> is a string.
1023
1024 Used to prevent false positives. This option can be specified multiple times.
1025
1026 =item B<--ignore-line> I<regex>
1027
1028 Ignore lines matching the given Perl regex. I<regex> is automatically anchored
1029 at the beginning and end of the line to prevent false negatives.
1030
1031 B<NOTE>: Not the input lines are checked, but the lines which are displayed in
1032 warnings (which have line continuation resolved).
1033
1034 Used to prevent false positives. This option can be specified multiple times.
1035
1036 =item B<--pie>
1037
1038 Force check for all +pie hardening flags. By default it's auto detected.
1039
1040 =item B<-h -? --help>
1041
1042 Print available options.
1043
1044 =item B<--version>
1045
1046 Print version number and license.
1047
1048 =back
1049
1050 Auto detection for B<--pie> and B<--bindnow> only works if at least one
1051 command uses the required hardening flag (e.g. -fPIE). Then it's required for
1052 all other commands as well.
1053
1054 =head1 EXAMPLES
1055
1056 Normal usage, parse a single log file.
1057
1058     blhc path/to/log/file
1059
1060 Parse multiple log files. The exit code is ORed over all files.
1061
1062     blhc path/to/directory/with/log/files/*
1063
1064 Don't treat missing C<-g> as error:
1065
1066     blhc --ignore-flag -g path/to/log/file
1067
1068 Ignore lines consisting exactly of C<./script gcc file> which would cause a
1069 false positive.
1070
1071     blhc --ignore-line '\./script gcc file' path/to/log/file
1072
1073 Ignore lines matching C<./script gcc file> somewhere in the line.
1074
1075     blhc --ignore-line '.*\./script gcc file.*' path/to/log/file
1076
1077 Use blhc with pbuilder.
1078
1079     pbuilder path/to/package.dsc | tee path/log/file
1080     blhc path/to/file || echo flags missing
1081
1082 =head1 BUILDD TAGS
1083
1084 The following tags are used in I<--buildd> mode. In braces the additional data
1085 which is displayed.
1086
1087 =over 2
1088
1089 =item
1090
1091 B<I-hardening-wrapper-used>
1092
1093 The package uses hardening-wrapper which intercepts calls to gcc and adds
1094 hardening flags. The build log doesn't contain any hardening flags and thus
1095 can't be checked by blhc.
1096
1097 =item
1098
1099 B<W-compiler-flags-hidden> (summary of hidden lines)
1100
1101 Build log contains lines which hide the real compiler flags. For example:
1102
1103     CC test-a.c
1104     CC test-b.c
1105     CC test-c.c
1106     LD test
1107
1108 Most of the time either C<export V=1> or C<export verbose=1> in
1109 F<debian/rules> fixes builds with hidden compiler flags. Sometimes C<.SILENT>
1110 in a F<Makefile> must be removed. And as last resort the F<Makefile> must be
1111 patched to remove the C<@>s hiding the real compiler commands.
1112
1113 =item
1114
1115 B<W-dpkg-buildflags-missing> (summary of missing flags)
1116
1117 CPPFLAGS, CFLAGS, CXXFLAGS, LDFLAGS missing.
1118
1119 =item
1120
1121 B<W-invalid-cmake-used> (version)
1122
1123 =item
1124
1125 B<W-no-compiler-commands>
1126
1127 No compiler commands were detected. Either the log contains none or they were
1128 not correctly detected by blhc (please report the bug in this case).
1129
1130 =back
1131
1132 =head1 EXIT STATUS
1133
1134 The exit status is a "bit mask", each listed status is ORed when the error
1135 condition occurs to get the result.
1136
1137 =over 4
1138
1139 =item B<0>
1140
1141 Success.
1142
1143 =item B<1>
1144
1145 No compiler commands were found.
1146
1147 =item B<2>
1148
1149 Invalid arguments/options given to blhc.
1150
1151 =item B<4>
1152
1153 Non verbose build.
1154
1155 =item B<8>
1156
1157 Missing hardening flags.
1158
1159 =item B<16>
1160
1161 Hardening wrapper detected, no tests performed.
1162
1163 =back
1164
1165 =head1 AUTHOR
1166
1167 Simon Ruderich, E<lt>simon@ruderich.orgE<gt>
1168
1169 Thanks to to Bernhard R. Link E<lt>brlink@debian.orgE<gt> and Jaria Alto
1170 E<lt>jari.aalto@cante.netE<gt> for their valuable input and suggestions.
1171
1172 =head1 COPYRIGHT AND LICENSE
1173
1174 Copyright (C) 2012 by Simon Ruderich
1175
1176 This program is free software: you can redistribute it and/or modify
1177 it under the terms of the GNU General Public License as published by
1178 the Free Software Foundation, either version 3 of the License, or
1179 (at your option) any later version.
1180
1181 This program is distributed in the hope that it will be useful,
1182 but WITHOUT ANY WARRANTY; without even the implied warranty of
1183 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
1184 GNU General Public License for more details.
1185
1186 You should have received a copy of the GNU General Public License
1187 along with this program.  If not, see <http://www.gnu.org/licenses/>.
1188
1189 =head1 SEE ALSO
1190
1191 L<hardening-check(1)>, L<dpkg-buildflags(1)>
1192
1193 =cut