]> ruderich.org/simon Gitweb - blhc/blhc.git/blob - bin/blhc
Properly detect if given a directory name as argument.
[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     -f $file or die "No such file: $file";
605
606     open my $fh, '<', $file or die $!;
607
608     # Architecture of this file.
609     my $arch = $option_arch;
610
611     # Hardening options. Not all architectures support all hardening options.
612     my $harden_format  = 1;
613     my $harden_fortify = 1;
614     my $harden_stack   = 1;
615     my $harden_relro   = 1;
616     my $harden_bindnow = $option_bindnow; # defaults to 0
617     my $harden_pie     = $option_pie;     # defaults to 0
618
619     while (my $line = <$fh>) {
620         # Detect architecture automatically unless overridden. For buildd logs
621         # only, doesn't use the dpkg-buildpackage header. Necessary to ignore
622         # build logs which aren't built (wrong architecture, build error,
623         # etc.).
624         if (not $arch
625                 and $line =~ /^Architecture: (.+)$/) {
626             $arch = $1;
627         }
628
629         # dpkg-buildflags only provides hardening flags since 1.16.1, don't
630         # check for hardening flags in buildd mode if an older dpkg-dev is
631         # used. Default flags (-g -O2) are still checked.
632         #
633         # Packages which were built before 1.16.1 but used their own hardening
634         # flags are not checked.
635         if ($option_buildd
636                 and index($line, 'Toolchain package versions: ') == 0) {
637             require Dpkg::Version;
638             if (not $line =~ /\bdpkg-dev_(\S+)/
639                     or Dpkg::Version::version_compare($1, '1.16.1') < 0) {
640                 $harden_format  = 0;
641                 $harden_fortify = 0;
642                 $harden_stack   = 0;
643                 $harden_relro   = 0;
644                 $harden_bindnow = 0;
645                 $harden_pie     = 0;
646             }
647         }
648
649         # The following two versions of CMake in Debian obeyed CPPFLAGS, but
650         # this was later dropped because upstream rejected the patch. Thus
651         # build logs with these versions will have fortify hardening flags
652         # enabled, even though they may be not correctly set and are missing
653         # when build with later CMake versions. Thanks to Aron Xu for letting
654         # me know.
655         if (index($line, 'Package versions: ') == 0
656                 and $line =~ /\bcmake_(\S+)/
657                 and ($1 eq '2.8.7-1' or $1 eq '2.8.7-2')) {
658             if (not $option_buildd) {
659                 error_invalid_cmake($1);
660                 $exit |= $exit_code{invalid_cmake};
661             } else {
662                 print "$buildd_tag{invalid_cmake}|$1|\n";
663             }
664         }
665
666         # If hardening wrapper is used (wraps calls to gcc and adds hardening
667         # flags automatically) we can't perform any checks, abort.
668         if (index($line, 'Build-Depends: ') == 0
669                 and $line =~ /\bhardening-wrapper\b/) {
670             if (not $option_buildd) {
671                 error_hardening_wrapper();
672                 $exit |= $exit_code{hardening_wrapper};
673             } else {
674                 print "$buildd_tag{hardening_wrapper}||\n";
675             }
676             next FILE;
677         }
678
679         # We skip over unimportant lines at the beginning of the log to
680         # prevent false positives.
681         last if index($line, 'dpkg-buildpackage: ') == 0;
682     }
683
684     # Input lines, contain only the lines with compiler commands.
685     my @input = ();
686
687     my $continuation = 0;
688     my $complete_line = undef;
689     while (my $line = <$fh>) {
690         # And stop at the end of the build log. Package details (reported by
691         # the buildd logs) are not important for us. This also prevents false
692         # positives.
693         last if $line =~ /^Build finished at \d{8}-\d{4}$/;
694
695         # Detect architecture automatically unless overridden.
696         if (not $arch
697                 and $line =~ /^dpkg-buildpackage: host architecture (.+)$/) {
698             $arch = $1;
699         }
700
701         # Ignore compiler warnings for now.
702         next if $line =~ /$warning_regex/o;
703
704         if (not $option_buildd and index($line, "\033") != -1) { # esc
705             # Remove all ANSI color sequences which are sometimes used in
706             # non-verbose builds.
707             $line = Term::ANSIColor::colorstrip($line);
708             # Also strip '\0xf' (delete previous character), used by Elinks'
709             # build system.
710             $line =~ s/\x0f//g;
711             # And "ESC(B" which seems to be used on armhf and hurd (not sure
712             # what it does).
713             $line =~ s/\033\(B//g;
714         }
715
716         # Check if this line indicates a non verbose build.
717         my $non_verbose = is_non_verbose_build($line);
718
719         # One line may contain multiple commands (";"). Treat each one as
720         # single line. parse_line() is slow, only use it when necessary.
721         my @line = (index($line, ';') == -1)
722                  ? ($line)
723                  : map {
724                        # Ensure newline at the line end - necessary for
725                        # correct parsing later.
726                        $_ =~ s/\s+$//;
727                        $_ .= "\n";
728                    } Text::ParseWords::parse_line(';', 1, $line);
729         foreach my $line (@line) {
730             if ($continuation) {
731                 $continuation = 0;
732
733                 # Join lines, but leave the "\" in place so it's clear where
734                 # the original line break was.
735                 chomp $complete_line;
736                 $complete_line .= ' ' . $line;
737             }
738             # Line continuation, line ends with "\".
739             if ($line =~ /\\$/) {
740                 $continuation = 1;
741                 # Start line continuation.
742                 if (not defined $complete_line) {
743                     $complete_line = $line;
744                 }
745                 next;
746             }
747
748             # Use the complete line if a line continuation occurred.
749             if (defined $complete_line) {
750                 $line = $complete_line;
751                 $complete_line = undef;
752             }
753
754             # Ignore lines with no compiler commands.
755             next if not $non_verbose
756                     and not $line =~ /\b$cc_regex(?:\s|\\)/o;
757             # Ignore lines with no filenames with extensions. May miss some
758             # non-verbose builds (e.g. "gcc -o test" [sic!]), but shouldn't be
759             # a problem as the log will most likely contain other non-verbose
760             # commands which are detected.
761             next if not $non_verbose
762                     and not $line =~ /$file_extension_regex/o;
763
764             # Ignore false positives.
765             #
766             # `./configure` output.
767             next if not $non_verbose
768                     and $line =~ /^(?:checking|[Cc]onfigure:) /;
769             next if $line =~ /^\s*(?:Host\s+)?(?:C(?:\+\+)?\s+)?
770                                 [Cc]ompiler[\s.]*:?\s+
771                                 /xo;
772             next if $line =~ /^\s*(?:- )?(?:HOST_)?(?:CC|CXX)\s*=\s*$cc_regex_full\s*$/o;
773             # `moc-qt4`, contains '-I/usr/share/qt4/mkspecs/linux-g++' (or
774             # similar for other architectures) which gets recognized as a
775             # compiler line. Ignore it.
776             next if $line =~ m{^/usr/bin/moc-qt4
777                                \s.+\s
778                                -I/usr/share/qt4/mkspecs/[a-z]+-g\++(?:-64)?
779                                \s}x;
780
781             # Check if additional hardening options were used. Used to ensure
782             # they are used for the complete build.
783             $harden_pie     = 1 if any_flags_used($line, @def_cflags_pie,
784                                                          @def_ldflags_pie);
785             $harden_bindnow = 1 if any_flags_used($line, @def_ldflags_bindnow);
786
787             push @input, $line;
788         }
789     }
790
791     close $fh or die $!;
792
793     # Ignore arch if requested.
794     if (scalar @option_ignore_arch > 0 and $arch) {
795         foreach my $ignore (@option_ignore_arch) {
796             if ($arch eq $ignore) {
797                 print "ignoring architecture '$arch'\n";
798                 next FILE;
799             }
800         }
801     }
802
803     if (scalar @input == 0) {
804         if (not $option_buildd) {
805             print "No compiler commands!\n";
806             $exit |= $exit_code{no_compiler_commands};
807         } else {
808             print "$buildd_tag{no_compiler_commands}||\n";
809         }
810         next FILE;
811     }
812
813     if ($option_buildd) {
814         $statistics{commands} += scalar @input;
815     }
816
817     # Option or auto detected.
818     if ($arch) {
819         # The following was partially copied from dpkg-dev 1.16.1.2
820         # (/usr/share/perl5/Dpkg/Vendor/Debian.pm, add_hardening_flags()),
821         # copyright Raphaël Hertzog <hertzog@debian.org>, Kees Cook
822         # <kees@debian.org>, Canonical, Ltd. licensed under GPL version 2 or
823         # later. Keep it in sync.
824
825         require Dpkg::Arch;
826         my ($abi, $os, $cpu) = Dpkg::Arch::debarch_to_debtriplet($arch);
827
828         # Disable unsupported hardening options.
829         if ($cpu =~ /^(?:ia64|alpha|mips|mipsel|hppa)$/ or $arch eq 'arm') {
830             $harden_stack = 0;
831         }
832         if ($cpu =~ /^(?:ia64|hppa|avr32)$/) {
833             $harden_relro   = 0;
834             $harden_bindnow = 0;
835         }
836     }
837
838     # Default values.
839     my @cflags   = @def_cflags;
840     my @cxxflags = @def_cxxflags;
841     my @cppflags = @def_cppflags;
842     my @ldflags  = @def_ldflags;
843     # Check the specified hardening options, same order as dpkg-buildflags.
844     if ($harden_pie) {
845         @cflags   = (@cflags,   @def_cflags_pie);
846         @cxxflags = (@cxxflags, @def_cflags_pie);
847         @ldflags  = (@ldflags,  @def_ldflags_pie);
848     }
849     if ($harden_stack) {
850         @cflags   = (@cflags,   @def_cflags_stack);
851         @cxxflags = (@cxxflags, @def_cflags_stack);
852     }
853     if ($harden_fortify) {
854         @cflags   = (@cflags,   @def_cflags_fortify);
855         @cxxflags = (@cxxflags, @def_cflags_fortify);
856         @cppflags = (@cppflags, @def_cppflags_fortify);
857     }
858     if ($harden_format) {
859         @cflags   = (@cflags,   @def_cflags_format);
860         @cxxflags = (@cxxflags, @def_cflags_format);
861     }
862     if ($harden_relro) {
863         @ldflags = (@ldflags, @def_ldflags_relro);
864     }
865     if ($harden_bindnow) {
866         @ldflags = (@ldflags, @def_ldflags_bindnow);
867     }
868
869     # Hack to fix cppflags_fortify_broken() if --ignore-flag
870     # -D_FORTIFY_SOURCE=2 is used to ignore missing fortification. Only works
871     # as long as @def_cppflags_fortify contains only one variable.
872     if (scalar @def_cppflags_fortify == 0) {
873         $harden_fortify = 0;
874     }
875
876     # Ignore flags for this arch if requested.
877     if ($arch and exists $option_ignore_arch_flag{$arch}) {
878         my @flag_refs = (\@cflags, \@cxxflags, \@cppflags, \@ldflags);
879
880         remove_flags(\@flag_refs,
881                      \%flag_renames,
882                      @{$option_ignore_arch_flag{$arch}});
883     }
884
885     my @ignore_line = @option_ignore_line;
886     # Ignore lines for this arch if requested.
887     if ($arch and exists $option_ignore_arch_line{$arch}) {
888         @ignore_line = (@ignore_line, @{$option_ignore_arch_line{$arch}});
889     }
890
891 LINE:
892     for (my $i = 0; $i < scalar @input; $i++) {
893         my $line = $input[$i];
894
895         # Ignore line if requested.
896         foreach my $ignore (@ignore_line) {
897             next LINE if $line =~ /$ignore/;
898         }
899
900         my $skip = 0;
901         if (is_non_verbose_build($line, $input[$i + 1], \$skip)) {
902             if (not $option_buildd) {
903                 error_non_verbose_build($line);
904                 $exit |= $exit_code{non_verbose_build};
905             } else {
906                 $statistics{commands_nonverbose}++;
907             }
908             next;
909         }
910         # Even if it's a verbose build, we might have to skip this line.
911         next if $skip;
912
913         # Remove everything until and including the compiler command. Makes
914         # checks easier and faster.
915         $line =~ s/^.*?$cc_regex//o;
916         # "([...] test.c)" is not detected as 'test.c' - fix this by removing
917         # the brace and similar characters.
918         $line =~ s/['")]+$//;
919
920         # Skip unnecessary tests when only preprocessing.
921         my $flag_preprocess = 0;
922
923         my $dependency = 0;
924         my $preprocess = 0;
925         my $compile    = 0;
926         my $link       = 0;
927
928         # Preprocess, compile, assemble.
929         if ($line =~ /\s(-E|-S|-c)\b/) {
930             $preprocess      = 1;
931             $flag_preprocess = 1 if $1 eq '-E';
932             $compile         = 1 if $1 eq '-S' or $1 eq '-c';
933         # Dependency generation for Makefiles. The other flags (-MF -MG -MP
934         # -MT -MQ) are always used with -M/-MM.
935         } elsif ($line =~ /\s(?:-M|-MM)\b/) {
936             $dependency = 1;
937         # Otherwise assume we are linking.
938         } else {
939             $link = 1;
940         }
941
942         # -MD/-MMD also cause dependency generation, but they don't imply -E!
943         if ($line =~ /\s(?:-MD|-MMD)\b/) {
944             $dependency      = 0;
945             $flag_preprocess = 0;
946         }
947
948         # Dependency generation for Makefiles, no preprocessing or other flags
949         # needed.
950         next if $dependency;
951
952         # Get all file extensions on this line.
953         my @extensions = $line =~ /$file_extension_regex/go;
954         # Ignore all unknown extensions to speedup the search below.
955         @extensions = grep { exists $extension{$_} } @extensions;
956
957         # These file types don't require preprocessing.
958         if (extension_found(\%extensions_no_preprocess, @extensions)) {
959             $preprocess = 0;
960         }
961         # These file types require preprocessing.
962         if (extension_found(\%extensions_preprocess, @extensions)) {
963             $preprocess = 1;
964         }
965
966         # If there are source files then it's compiling/linking in one step
967         # and we must check both. We only check for source files here, because
968         # header files cause too many false positives.
969         if (not $flag_preprocess
970                 and extension_found(\%extensions_compile_link, @extensions)) {
971             # Assembly files don't need CFLAGS.
972             if (not extension_found(\%extensions_compile, @extensions)
973                     and extension_found(\%extensions_no_compile, @extensions)) {
974                 $compile = 0;
975             # But the rest does.
976             } else {
977                 $compile = 1;
978             }
979         }
980
981         # Assume CXXFLAGS are required when a C++ file is specified in the
982         # compiler line.
983         my $compile_cpp = 0;
984         if ($compile
985                 and extension_found(\%extensions_compile_cpp, @extensions)) {
986             $compile     = 0;
987             $compile_cpp = 1;
988         }
989
990         if ($option_buildd) {
991             $statistics{preprocess}++  if $preprocess;
992             $statistics{compile}++     if $compile;
993             $statistics{compile_cpp}++ if $compile_cpp;
994             $statistics{link}++        if $link;
995         }
996
997         # Check hardening flags.
998         my @missing;
999         if ($compile and not all_flags_used($line, \@missing, @cflags)
1000                 # Libraries linked with -fPIC don't have to (and can't) be
1001                 # linked with -fPIE as well. It's no error if only PIE flags
1002                 # are missing.
1003                 and not pic_pie_conflict($line, $harden_pie, \@missing, @def_cflags_pie)
1004                 # Assume dpkg-buildflags returns the correct flags.
1005                 and index($line, '`dpkg-buildflags --get CFLAGS`') == -1) {
1006             if (not $option_buildd) {
1007                 error_flags('CFLAGS missing', \@missing, \%flag_renames, $input[$i]);
1008                 $exit |= $exit_code{flags_missing};
1009             } else {
1010                 $statistics{compile_missing}++;
1011             }
1012         } elsif ($compile_cpp and not all_flags_used($line, \@missing, @cflags)
1013                 # Libraries linked with -fPIC don't have to (and can't) be
1014                 # linked with -fPIE as well. It's no error if only PIE flags
1015                 # are missing.
1016                 and not pic_pie_conflict($line, $harden_pie, \@missing, @def_cflags_pie)
1017                 # Assume dpkg-buildflags returns the correct flags.
1018                 and index($line, '`dpkg-buildflags --get CXXFLAGS`') == -1) {
1019             if (not $option_buildd) {
1020                 error_flags('CXXFLAGS missing', \@missing, \%flag_renames, $input[$i]);
1021                 $exit |= $exit_code{flags_missing};
1022             } else {
1023                 $statistics{compile_cpp_missing}++;
1024             }
1025         }
1026         if ($preprocess
1027                 and (not all_flags_used($line, \@missing, @cppflags)
1028                     # The fortify flag might be overwritten, detect that.
1029                      or ($harden_fortify
1030                          and cppflags_fortify_broken($line, \@missing)))
1031                 # Assume dpkg-buildflags returns the correct flags.
1032                 and index($line, '`dpkg-buildflags --get CPPFLAGS`') == -1) {
1033             if (not $option_buildd) {
1034                 error_flags('CPPFLAGS missing', \@missing, \%flag_renames, $input[$i]);
1035                 $exit |= $exit_code{flags_missing};
1036             } else {
1037                 $statistics{preprocess_missing}++;
1038             }
1039         }
1040         if ($link and not all_flags_used($line, \@missing, @ldflags)
1041                 # Same here, -fPIC conflicts with -fPIE.
1042                 and not pic_pie_conflict($line, $harden_pie, \@missing, @def_ldflags_pie)
1043                 # Assume dpkg-buildflags returns the correct flags.
1044                 and index($line, '`dpkg-buildflags --get LDFLAGS`') == -1) {
1045             if (not $option_buildd) {
1046                 error_flags('LDFLAGS missing', \@missing, \%flag_renames, $input[$i]);
1047                 $exit |= $exit_code{flags_missing};
1048             } else {
1049                 $statistics{link_missing}++;
1050             }
1051         }
1052     }
1053 }
1054
1055 # Print statistics for buildd mode, only output in this mode.
1056 if ($option_buildd) {
1057     my @warning;
1058
1059     if ($statistics{preprocess_missing}) {
1060         push @warning, sprintf 'CPPFLAGS %d (of %d)',
1061                                $statistics{preprocess_missing},
1062                                $statistics{preprocess};
1063     }
1064     if ($statistics{compile_missing}) {
1065         push @warning, sprintf 'CFLAGS %d (of %d)',
1066                                $statistics{compile_missing},
1067                                $statistics{compile};
1068     }
1069     if ($statistics{compile_cpp_missing}) {
1070         push @warning, sprintf 'CXXFLAGS %d (of %d)',
1071                                $statistics{compile_cpp_missing},
1072                                $statistics{compile_cpp};
1073     }
1074     if ($statistics{link_missing}) {
1075         push @warning, sprintf 'LDFLAGS %d (of %d)',
1076                                $statistics{link_missing},
1077                                $statistics{link};
1078     }
1079     if (scalar @warning) {
1080         local $" = ', '; # array join string
1081         print "$buildd_tag{flags_missing}|@warning missing|\n";
1082     }
1083
1084     if ($statistics{commands_nonverbose}) {
1085         printf "$buildd_tag{non_verbose_build}|%d (of %d) hidden|\n",
1086                $statistics{commands_nonverbose},
1087                $statistics{commands},
1088     }
1089 }
1090
1091
1092 exit $exit;
1093
1094
1095 __END__
1096
1097 =head1 NAME
1098
1099 blhc - build log hardening check, checks build logs for missing hardening flags
1100
1101 =head1 SYNOPSIS
1102
1103 B<blhc> [I<options>] I<< <dpkg-buildpackage build log file>.. >>
1104
1105 =head1 DESCRIPTION
1106
1107 blhc is a small tool which checks build logs for missing hardening flags. It's
1108 licensed under the GPL 3 or later.
1109
1110 It's designed to check build logs generated by Debian's dpkg-buildpackage (or
1111 tools using dpkg-buildpackage like pbuilder or the official buildd build logs)
1112 to help maintainers detect missing hardening flags in their packages.
1113
1114 If there's no output, no flags are missing and the build log is fine.
1115
1116 =head1 OPTIONS
1117
1118 =over 8
1119
1120 =item B<--all>
1121
1122 Force check for all +all (+pie, +bindnow) hardening flags. By default it's
1123 auto detected.
1124
1125 =item B<--arch> I<architecture>
1126
1127 Set the specific architecture (e.g. amd64, armel, etc.), automatically
1128 disables hardening flags not available on this architecture. Is detected
1129 automatically if dpkg-buildpackage is used.
1130
1131 =item B<--bindnow>
1132
1133 Force check for all +bindnow hardening flags. By default it's auto detected.
1134
1135 =item B<--buildd>
1136
1137 Special mode for buildds when automatically parsing log files. The following
1138 changes are in effect:
1139
1140 =over 2
1141
1142 =item *
1143
1144 Print tags instead of normal warnings, see L</"BUILDD TAGS"> for a list of
1145 possible tags.
1146
1147 =item *
1148
1149 Don't check hardening flags in old log files (if dpkg-dev << 1.16.1 is
1150 detected).
1151
1152 =item *
1153
1154 Don't require Term::ANSIColor.
1155
1156 =item *
1157
1158 Return exit code 0, unless there was a error.
1159
1160 =back
1161
1162 =item B<--color>
1163
1164 Use colored (ANSI) output for warning messages.
1165
1166 =item B<--ignore-arch> I<arch>
1167
1168 Ignore build logs from architectures matching I<arch>. I<arch> is a string.
1169
1170 Used to prevent false positives. This option can be specified multiple times.
1171
1172 =item B<--ignore-arch-flag> I<arch>:I<flag>
1173
1174 Like B<--ignore-flag>, but only ignore flag on I<arch>.
1175
1176 =item B<--ignore-arch-line> I<arch>:I<line>
1177
1178 Like B<--ignore-line>, but only ignore line on I<arch>.
1179
1180 =item B<--ignore-flag> I<flag>
1181
1182 Don't print an error when the specific flag is missing in a compiler line.
1183 I<flag> is a string.
1184
1185 Used to prevent false positives. This option can be specified multiple times.
1186
1187 =item B<--ignore-line> I<regex>
1188
1189 Ignore lines matching the given Perl regex. I<regex> is automatically anchored
1190 at the beginning and end of the line to prevent false negatives.
1191
1192 B<NOTE>: Not the input lines are checked, but the lines which are displayed in
1193 warnings (which have line continuation resolved).
1194
1195 Used to prevent false positives. This option can be specified multiple times.
1196
1197 =item B<--pie>
1198
1199 Force check for all +pie hardening flags. By default it's auto detected.
1200
1201 =item B<-h -? --help>
1202
1203 Print available options.
1204
1205 =item B<--version>
1206
1207 Print version number and license.
1208
1209 =back
1210
1211 Auto detection for B<--pie> and B<--bindnow> only works if at least one
1212 command uses the required hardening flag (e.g. -fPIE). Then it's required for
1213 all other commands as well.
1214
1215 =head1 EXAMPLES
1216
1217 Normal usage, parse a single log file.
1218
1219     blhc path/to/log/file
1220
1221 If there's no output, no flags are missing and the build log is fine.
1222
1223 Parse multiple log files. The exit code is ORed over all files.
1224
1225     blhc path/to/directory/with/log/files/*
1226
1227 Don't treat missing C<-g> as error:
1228
1229     blhc --ignore-flag -g path/to/log/file
1230
1231 Don't treat missing C<-pie> on kfreebsd-amd64 as error:
1232
1233     blhc --ignore-arch-flag kfreebsd-amd64:-pie path/to/log/file
1234
1235 Ignore lines consisting exactly of C<./script gcc file> which would cause a
1236 false positive.
1237
1238     blhc --ignore-line '\./script gcc file' path/to/log/file
1239
1240 Ignore lines matching C<./script gcc file> somewhere in the line.
1241
1242     blhc --ignore-line '.*\./script gcc file.*' path/to/log/file
1243
1244 Use blhc with pbuilder.
1245
1246     pbuilder path/to/package.dsc | tee path/log/file
1247     blhc path/to/file || echo flags missing
1248
1249 =head1 BUILDD TAGS
1250
1251 The following tags are used in I<--buildd> mode. In braces the additional data
1252 which is displayed.
1253
1254 =over 2
1255
1256 =item B<I-hardening-wrapper-used>
1257
1258 The package uses hardening-wrapper which intercepts calls to gcc and adds
1259 hardening flags. The build log doesn't contain any hardening flags and thus
1260 can't be checked by blhc.
1261
1262 =item B<W-compiler-flags-hidden> (summary of hidden lines)
1263
1264 Build log contains lines which hide the real compiler flags. For example:
1265
1266     CC test-a.c
1267     CC test-b.c
1268     CC test-c.c
1269     LD test
1270
1271 Most of the time either C<export V=1> or C<export verbose=1> in
1272 F<debian/rules> fixes builds with hidden compiler flags. Sometimes C<.SILENT>
1273 in a F<Makefile> must be removed. And as last resort the F<Makefile> must be
1274 patched to remove the C<@>s hiding the real compiler commands.
1275
1276 =item B<W-dpkg-buildflags-missing> (summary of missing flags)
1277
1278 CPPFLAGS, CFLAGS, CXXFLAGS, LDFLAGS missing.
1279
1280 =item B<I-invalid-cmake-used> (version)
1281
1282 By default CMake ignores CPPFLAGS thus missing those hardening flags. Debian
1283 patched CMake in versions 2.8.7-1 and 2.8.7-2 to respect CPPFLAGS, but this
1284 patch was rejected by upstream and later reverted in Debian. Thus those two
1285 versions show correct usage of CPPFLAGS even if the package doesn't correctly
1286 handle them (for example by passing them to CFLAGS). To prevent false
1287 negatives just blacklist those two versions.
1288
1289 =item B<I-no-compiler-commands>
1290
1291 No compiler commands were detected. Either the log contains none or they were
1292 not correctly detected by blhc (please report the bug in this case).
1293
1294 =back
1295
1296 =head1 EXIT STATUS
1297
1298 The exit status is a "bit mask", each listed status is ORed when the error
1299 condition occurs to get the result.
1300
1301 =over 4
1302
1303 =item B<0>
1304
1305 Success.
1306
1307 =item B<1>
1308
1309 No compiler commands were found.
1310
1311 =item B<2>
1312
1313 Invalid arguments/options given to blhc.
1314
1315 =item B<4>
1316
1317 Non verbose build.
1318
1319 =item B<8>
1320
1321 Missing hardening flags.
1322
1323 =item B<16>
1324
1325 Hardening wrapper detected, no tests performed.
1326
1327 =item B<32>
1328
1329 Invalid CMake version used. See B<I-invalid-cmake-used> under L</"BUILDD
1330 TAGS"> for a detailed explanation.
1331
1332 =back
1333
1334 =head1 AUTHOR
1335
1336 Simon Ruderich, E<lt>simon@ruderich.orgE<gt>
1337
1338 Thanks to to Bernhard R. Link E<lt>brlink@debian.orgE<gt> and Jaria Alto
1339 E<lt>jari.aalto@cante.netE<gt> for their valuable input and suggestions.
1340
1341 =head1 COPYRIGHT AND LICENSE
1342
1343 Copyright (C) 2012 by Simon Ruderich
1344
1345 This program is free software: you can redistribute it and/or modify
1346 it under the terms of the GNU General Public License as published by
1347 the Free Software Foundation, either version 3 of the License, or
1348 (at your option) any later version.
1349
1350 This program is distributed in the hope that it will be useful,
1351 but WITHOUT ANY WARRANTY; without even the implied warranty of
1352 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
1353 GNU General Public License for more details.
1354
1355 You should have received a copy of the GNU General Public License
1356 along with this program.  If not, see <http://www.gnu.org/licenses/>.
1357
1358 =head1 SEE ALSO
1359
1360 L<hardening-check(1)>, L<dpkg-buildflags(1)>
1361
1362 =cut