]> ruderich.org/simon Gitweb - blhc/blhc.git/blob - bin/blhc
Fix indentation.
[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 and
967 other important warnings. It's licensed under the GPL 3 or later.
968
969 =head1 OPTIONS
970
971 =over 8
972
973 =item B<--all>
974
975 Force check for all +all (+pie, +bindnow) hardening flags. By default it's
976 auto detected.
977
978 =item B<--arch> I<architecture>
979
980 Set the specific architecture (e.g. amd64, armel, etc.), automatically
981 disables hardening flags not available on this architecture. Is detected
982 automatically if dpkg-buildpackage is used.
983
984 =item B<--bindnow>
985
986 Force check for all +bindnow hardening flags. By default it's auto detected.
987
988 =item B<--buildd>
989
990 Special mode for buildds when automatically parsing log files. The following
991 changes are in effect:
992
993 =over 2
994
995 =item
996
997 Print tags instead of normal warnings, see README file for a list of possible
998 tags.
999
1000 =item
1001
1002 Don't check hardening flags in old log files (if dpkg-dev << 1.16.1 is
1003 detected).
1004
1005 =item
1006
1007 Don't require Term::ANSIColor.
1008
1009 =back
1010
1011 =item B<--color>
1012
1013 Use colored (ANSI) output for warning messages.
1014
1015 =item B<--ignore-flag> I<flag>
1016
1017 Don't print an error when the specific flag is missing in a compiler line.
1018 I<flag> is a string.
1019
1020 Used to prevent false positives. This option can be specified multiple times.
1021
1022 =item B<--ignore-line> I<regex>
1023
1024 Ignore lines matching the given Perl regex. I<regex> is automatically anchored
1025 at the beginning and end of the line to prevent false negatives.
1026
1027 B<NOTE>: Not the input lines are checked, but the lines which are displayed in
1028 warnings (which have line continuation resolved).
1029
1030 Used to prevent false positives. This option can be specified multiple times.
1031
1032 =item B<--pie>
1033
1034 Force check for all +pie hardening flags. By default it's auto detected.
1035
1036 =item B<-h -? --help>
1037
1038 Print available options.
1039
1040 =item B<--version>
1041
1042 Print version number and license.
1043
1044 =back
1045
1046 Auto detection for B<--pie> and B<--bindnow> only works if at least one
1047 command uses the required hardening flag (e.g. -fPIE). Then it's required for
1048 all other commands as well.
1049
1050 =head1 EXIT STATUS
1051
1052 The exit status is a "bit mask", each listed status is ORed when the error
1053 condition occurs to get the result.
1054
1055 =over 4
1056
1057 =item B<0>
1058
1059 Success.
1060
1061 =item B<1>
1062
1063 No compiler commands were found.
1064
1065 =item B<2>
1066
1067 Invalid arguments/options given to blhc.
1068
1069 =item B<4>
1070
1071 Non verbose build.
1072
1073 =item B<8>
1074
1075 Missing hardening flags.
1076
1077 =item B<16>
1078
1079 Hardening wrapper detected, no tests performed.
1080
1081 =back
1082
1083 =head1 AUTHOR
1084
1085 Simon Ruderich, E<lt>simon@ruderich.orgE<gt>
1086
1087 =head1 COPYRIGHT AND LICENSE
1088
1089 Copyright (C) 2012 by Simon Ruderich
1090
1091 This program is free software: you can redistribute it and/or modify
1092 it under the terms of the GNU General Public License as published by
1093 the Free Software Foundation, either version 3 of the License, or
1094 (at your option) any later version.
1095
1096 This program is distributed in the hope that it will be useful,
1097 but WITHOUT ANY WARRANTY; without even the implied warranty of
1098 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
1099 GNU General Public License for more details.
1100
1101 You should have received a copy of the GNU General Public License
1102 along with this program.  If not, see <http://www.gnu.org/licenses/>.
1103
1104 =cut