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