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