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