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