]> ruderich.org/simon Gitweb - blhc/blhc.git/blob - bin/blhc
dda96124fe76f12318a56683efd4c173dbb4e868
[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-2024  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.13';
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\+\+|gfortran|mpicc|mpicxx|mpifort)
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_prefix = qr/
42     [a-z0-9_]+-(?:linux-|kfreebsd-)?gnu(?:eabi|eabihf)?
43     /x;
44 my $cc_regex_full = qr/
45     (?:$cc_regex_full_prefix-)?
46     $cc_regex
47     /x;
48 # Regex to check if a line contains a compiler command.
49 my $cc_regex_normal = qr/
50     \b$cc_regex(?:\s|\\)
51     /x;
52 # Regex to catch (GCC) compiler warnings.
53 my $warning_regex = qr/^(.+?):(\d+):\d+: warning: (.+?) \[(.+?)\]$/;
54 # Regex to catch libtool commands and not lines which show commands executed
55 # by libtool (e.g. libtool: link: ...).
56 my $libtool_regex = qr/\blibtool["']?\s.*--mode=/;
57 my $libtool_link_regex = qr/\blibtool: link: /;
58
59 # List of source file extensions which require preprocessing.
60 my @source_preprocess_compile_cpp = (
61     # C++
62     qw( cc cp cxx cpp CPP c++ C ),
63     # Objective-C++
64     qw( mm M ),
65 );
66 my @source_preprocess_compile_fortran = (
67     # Fortran
68     qw( F FOR fpp FPP FTN F90 F95 F03 F08 ),
69 );
70 my @source_preprocess_compile = (
71     # C
72     qw( c ),
73     # Objective-C
74     qw( m ),
75     # (Objective-)C++
76     @source_preprocess_compile_cpp,
77     # Fortran
78     @source_preprocess_compile_fortran,
79 );
80 my @source_preprocess_no_compile = (
81     # Assembly
82     qw( S sx ),
83 );
84 my @source_preprocess = (
85     @source_preprocess_compile,
86     @source_preprocess_no_compile,
87 );
88 # List of source file extensions which don't require preprocessing.
89 my @source_no_preprocess_compile_cpp = (
90     # C++
91     qw( ii ),
92     # Objective-C++
93     qw( mii ),
94 );
95 my @source_no_preprocess_compile_ada = (
96     # Ada source
97     qw( ada ),
98     # Ada body
99     qw( adb ),
100 );
101 my @source_no_preprocess_compile_fortran = (
102     # Fortran
103     qw( f for ftn f90 f95 f03 f08 ),
104 );
105 my @source_no_preprocess_compile = (
106     # C
107     qw( i ),
108     # (Objective-)C++
109     @source_no_preprocess_compile_cpp,
110     # Objective-C
111     qw( mi ),
112     # Fortran
113     @source_no_preprocess_compile_fortran,
114     # Ada
115     @source_no_preprocess_compile_ada,
116 );
117 my @source_no_preprocess_no_compile_ada = (
118     # Ada specification
119     qw( ads ),
120 );
121 my @source_no_preprocess_no_compile = (
122     # Assembly
123     qw( s ),
124     # Ada
125     @source_no_preprocess_no_compile_ada,
126 );
127 my @source_no_preprocess = (
128     @source_no_preprocess_compile,
129     @source_no_preprocess_no_compile,
130 );
131 # List of header file extensions which require preprocessing.
132 my @header_preprocess = (
133     # C, C++, Objective-C, Objective-C++
134     qw( h ),
135     # C++
136     qw( hh H hp hxx hpp HPP h++ tcc ),
137 );
138 # Object files.
139 my @object = (
140     # Normal object files.
141     qw ( o ),
142     # Libtool object files.
143     qw ( lo la ),
144     # Dynamic libraries. bzip2 uses .sho.
145     qw ( so sho ),
146     # Static libraries.
147     qw ( a ),
148 );
149
150 # Hashes for fast extensions lookup to check if a file falls in one of these
151 # categories.
152 my %extensions_no_preprocess = map { $_ => 1 } (
153     # There's no @header_no_preprocess.
154     @source_no_preprocess,
155 );
156 my %extensions_preprocess = map { $_ => 1 } (
157     @header_preprocess,
158     @source_preprocess,
159 );
160 my %extensions_compile_link = map { $_ => 1 } (
161     @source_preprocess,
162     @source_no_preprocess,
163 );
164 my %extensions_compile = map { $_ => 1 } (
165     @source_preprocess_compile,
166     @source_no_preprocess_compile,
167 );
168 my %extensions_no_compile = map { $_ => 1 } (
169     @source_preprocess_no_compile,
170     @source_no_preprocess_no_compile,
171 );
172 my %extensions_compile_cpp = map { $_ => 1 } (
173     @source_preprocess_compile_cpp,
174     @source_no_preprocess_compile_cpp,
175 );
176 my %extensions_ada = map { $_ => 1 } (
177     @source_no_preprocess_compile_ada,
178     @source_no_preprocess_no_compile_ada,
179 );
180 my %extensions_fortran = map { $_ => 1 } (
181     @source_no_preprocess_compile_fortran,
182     @source_preprocess_compile_fortran,
183 );
184 my %extensions_object = map { $_ => 1 } (
185     @object,
186 );
187 my %extension = map { $_ => 1 } (
188     @source_no_preprocess,
189     @header_preprocess,
190     @source_preprocess,
191     @object,
192 );
193
194 # Regexp to match file extensions.
195 my $file_extension_regex = qr/
196     \s
197     \S+             # Filename without extension.
198     \.
199     ([^\/\\.,;:\s]+)# File extension.
200     (?=\s|\\)       # At end of word. Can't use \b because some files have non
201                     # word characters at the end and because \b matches double
202                     # extensions (like .cpp.o). Works always as all lines are
203                     # terminated with "\n".
204     /x;
205
206 # Expected (hardening) flags. All flags are used as regexps (and compiled to
207 # real regexps below for better execution speed).
208 my @def_cflags = (
209     '-g3?',
210     '-O(?:2|3)', # keep at index 1, search for @def_cflags_debug to change it
211 );
212 my @def_cflags_debug = (
213     # These flags indicate a debug build which disables checks for -O2.
214     '-O0',
215     '-Og',
216 );
217 my @def_cflags_format = (
218     '-Wformat(?:=2)?', # -Wformat=2 implies -Wformat, accept it too
219     '-Werror=format-security', # implies -Wformat-security
220 );
221 my @def_cflags_fortify = (
222     # fortify needs at least -O1, but -O2 is recommended anyway
223 );
224 my @def_cflags_stack = (
225     '-fstack-protector', # keep first, used by cflags_stack_broken()
226     '--param[= ]ssp-buffer-size=4',
227 );
228 my @def_cflags_stack_strong = (
229     '-fstack-protector-strong', # keep first, used by cflags_stack_broken()
230 );
231 my @def_cflags_stack_bad = (
232     # Blacklist all stack protector options for simplicity.
233     '-fno-stack-protector',
234     '-fno-stack-protector-all',
235     '-fno-stack-protector-strong',
236 );
237 my @def_cflags_pie = (
238     '-fPIE',
239 );
240 my @def_cxxflags = (
241     @def_cflags,
242 );
243 # @def_cxxflags_* is the same as @def_cflags_*.
244 my @def_cppflags = ();
245 my @def_cppflags_fortify = (
246     '-D_FORTIFY_SOURCE=[23]', # must be first, see cppflags_fortify_broken()
247     # If you add another flag fix hack below (search for "Hack to fix") and
248     # $def_cppflags_fortify[0].
249 );
250 my @def_cppflags_fortify_bad = (
251     # These flags may overwrite -D_FORTIFY_SOURCE=2.
252     '-U_FORTIFY_SOURCE',
253     '-D_FORTIFY_SOURCE=0',
254     '-D_FORTIFY_SOURCE=1',
255 );
256 my @def_ldflags = ();
257 my @def_ldflags_relro = (
258     '-Wl,(?:-z,)?relro',
259 );
260 my @def_ldflags_bindnow = (
261     '-Wl,(?:-z,)?now',
262 );
263 my @def_ldflags_pie = (
264     '-fPIE',
265     '-pie',
266 );
267 my @def_ldflags_pic = (
268     '-fPIC',
269     '-fpic',
270     '-shared',
271 );
272 # References to all flags checked by the flag checker.
273 my @flag_refs = (
274     \@def_cflags,
275     \@def_cflags_format,
276     \@def_cflags_fortify,
277     \@def_cflags_stack,
278     \@def_cflags_stack_strong,
279     \@def_cflags_stack_bad,
280     \@def_cflags_pie,
281     \@def_cxxflags,
282     \@def_cppflags,
283     \@def_cppflags_fortify,
284     \@def_ldflags,
285     \@def_ldflags_relro,
286     \@def_ldflags_bindnow,
287     \@def_ldflags_pie,
288 );
289 # References to all used flags.
290 my @flag_refs_all = (
291     @flag_refs,
292     \@def_cflags_debug,
293     \@def_cppflags_fortify_bad,
294     \@def_ldflags_pic,
295 );
296 # Renaming rules for the output so the regex parts are not visible. Also
297 # stores string values of flag regexps above, see compile_flag_regexp().
298 my %flag_renames = (
299     '-g3?'                         => '-g',
300     '-O(?:2|3)'                    => '-O2',
301     '-Wformat(?:=2)?'              => '-Wformat',
302     '--param[= ]ssp-buffer-size=4' => '--param=ssp-buffer-size=4',
303     '-D_FORTIFY_SOURCE=[23]'       => '-D_FORTIFY_SOURCE=2',
304     '-Wl,(?:-z,)?relro'            => '-Wl,-z,relro',
305     '-Wl,(?:-z,)?now'              => '-Wl,-z,now',
306 );
307
308 my %exit_code = (
309     no_compiler_commands => 1 << 0,
310     # used by POD::Usage => 1 << 1,
311     non_verbose_build    => 1 << 2,
312     flags_missing        => 1 << 3,
313     hardening_wrapper    => 1 << 4,
314     invalid_cmake        => 1 << 5,
315 );
316
317 my %buildd_tag = (
318     no_compiler_commands => 'I-no-compiler-commands',
319     non_verbose_build    => 'W-compiler-flags-hidden',
320     flags_missing        => 'W-dpkg-buildflags-missing',
321     hardening_wrapper    => 'I-hardening-wrapper-used',
322     invalid_cmake        => 'I-invalid-cmake-used',
323 );
324
325 # Statistics of missing flags and non-verbose build commands. Used for
326 # $option_buildd.
327 my %statistics = (
328     preprocess          => 0,
329     preprocess_missing  => 0,
330     compile             => 0,
331     compile_missing     => 0,
332     compile_cpp         => 0,
333     compile_cpp_missing => 0,
334     link                => 0,
335     link_missing        => 0,
336     commands            => 0,
337     commands_nonverbose => 0,
338 );
339
340 # Use colored (ANSI) output?
341 my $option_color;
342
343
344 # FUNCTIONS
345
346 sub split_line {
347     my ($line) = @_;
348
349     my @work = ($line);
350     foreach my $delim (';', '&&', '||') {
351         my @x;
352         foreach (@work) {
353             push @x, Text::ParseWords::parse_line(qr/\Q$delim\E/, 1, $_);
354         }
355         @work = @x;
356     }
357
358     return map {
359         # Ensure newline at the line end - necessary for
360         # correct parsing later.
361         $_ =~ s/\s+$//;
362         $_ .= "\n";
363     } @work;
364 }
365
366 sub error_flags {
367     my ($message, $missing_flags_ref, $flag_renames_ref, $line, $number) = @_;
368
369     # Get string value of qr//-escaped regexps and if requested rename them.
370     my @missing_flags = map {
371             $flag_renames_ref->{$_}
372         } @{$missing_flags_ref};
373
374     my $flags = join ' ', @missing_flags;
375     printf '%d:', $number if defined $number;
376     printf '%s (%s)%s %s',
377            error_color($message, 'red'), $flags, error_color(':', 'yellow'),
378            $line;
379
380     return;
381 }
382 sub error_non_verbose_build {
383     my ($line, $number) = @_;
384
385     printf '%d:', $number if defined $number;
386     printf '%s%s %s',
387            error_color('NONVERBOSE BUILD', 'red'),
388            error_color(':', 'yellow'),
389            $line;
390
391     return;
392 }
393 sub error_invalid_cmake {
394     my ($version) = @_;
395
396     printf "%s%s %s\n",
397             error_color('INVALID CMAKE', 'red'),
398             error_color(':', 'yellow'),
399             $version;
400
401     return;
402 }
403 sub error_hardening_wrapper {
404     printf "%s%s %s\n",
405             error_color('HARDENING WRAPPER', 'red'),
406             error_color(':', 'yellow'),
407             'no checks possible, aborting';
408
409     return;
410 }
411 sub error_color {
412     my ($message, $color) = @_;
413
414     if ($option_color) {
415         return Term::ANSIColor::colored($message, $color);
416     } else {
417         return $message;
418     }
419 }
420
421 sub any_flags_used {
422     my ($line, @flags) = @_;
423
424     foreach my $flag (@flags) {
425         return 1 if $line =~ /$flag/;
426     }
427
428     return 0;
429 }
430 sub all_flags_used {
431     my ($line, $missing_flags_ref, @flags) = @_;
432
433     my @missing_flags = ();
434     foreach my $flag (@flags) {
435         if (not $line =~ /$flag/) {
436             push @missing_flags, $flag;
437         }
438     }
439
440     return 1 if scalar @missing_flags == 0;
441
442     @{$missing_flags_ref} = @missing_flags;
443     return 0;
444 }
445 # Check if any of \@bad_flags occurs after $good_flag. Doesn't check if
446 # $good_flag is present.
447 sub flag_overwritten {
448     my ($line, $good_flag, $bad_flags) = @_;
449
450     if (not any_flags_used($line, @{$bad_flags})) {
451         return 0;
452     }
453
454     my $bad_pos = 0;
455     foreach my $flag (@{$bad_flags}) {
456         while ($line =~ /$flag/g) {
457             if ($bad_pos < $+[0]) {
458                 $bad_pos = $+[0];
459             }
460         }
461     }
462     my $good_pos = 0;
463     while ($line =~ /$good_flag/g) {
464         $good_pos = $+[0];
465     }
466     if ($good_pos > $bad_pos) {
467         return 0;
468     }
469     return 1;
470 }
471
472 sub cppflags_fortify_broken {
473     my ($line, $missing_flags) = @_;
474
475     # $def_cppflags_fortify[0] must be -D_FORTIFY_SOURCE=2!
476     my $fortify_source = $def_cppflags_fortify[0];
477
478     # Some build systems enable/disable fortify source multiple times, check
479     # the final result.
480     if (not flag_overwritten($line,
481                              $fortify_source,
482                              \@def_cppflags_fortify_bad)) {
483         return 0;
484     }
485     push @{$missing_flags}, $fortify_source;
486     return 1;
487 }
488
489 sub cflags_stack_broken {
490     my ($line, $missing_flags, $strong) = @_;
491
492     my $flag = $strong ? $def_cflags_stack_strong[0]
493                        : $def_cflags_stack[0];
494
495     if (not flag_overwritten($line, $flag, \@def_cflags_stack_bad)) {
496         return 0;
497     }
498     push @{$missing_flags}, $flag;
499     return 1;
500 }
501
502 # Modifies $missing_flags_ref array.
503 sub pic_pie_conflict {
504     my ($line, $pie, $missing_flags_ref, @flags_pie) = @_;
505
506     return 0 if not $pie;
507     return 0 if not any_flags_used($line, @def_ldflags_pic);
508
509     my %flags = map { $_ => 1 } @flags_pie;
510
511     # Remove all PIE flags from @missing_flags as they are not required with
512     # -fPIC.
513     my @result = grep {
514         not exists $flags{$_}
515     } @{$missing_flags_ref};
516     @{$missing_flags_ref} = @result;
517
518     # We got a conflict when no flags are left, thus only PIE flags were
519     # missing. If other flags were missing abort because the conflict is not
520     # the problem.
521     return scalar @result == 0;
522 }
523
524 sub is_non_verbose_build {
525     my ($line, $skip_ref, $input_ref, $line_offset, $line_count) = @_;
526
527     if ($line =~ /$libtool_regex/o) {
528         # libtool's --silent hides the real compiler flags.
529         if ($line =~ /\s--silent/) {
530             return 1;
531         # If --silent is not present, skip this line as some compiler flags
532         # might be missing (e.g. -fPIE) which are handled correctly by libtool
533         # internally. libtool displays the real compiler command on the next
534         # line, so the flags are checked as usual.
535         } else {
536             ${$skip_ref} = 1;
537             return 0;
538         }
539     }
540
541     if (not (index($line, 'checking if you want to see long compiling messages... no') == 0
542                 or $line =~ /^\s*\[?(?:CC|CCLD|C\+\+|CXX|CXXLD|LD|LINK)\]?\s+(.+?)$/
543                 or $line =~ /^\s*[][\/0-9 ]*[Cc]ompiling\s+(.+?)(?:\.\.\.)?$/
544                 or $line =~ /^\s*[Bb]uilding (?:program|shared library)\s+(.+?)$/
545                 or $line =~ /^\s*\[[\d ]+%\] Building (?:C|CXX) object (.+?)$/)) {
546         return 0;
547     }
548
549     # False positives.
550     #
551     # C++ compiler setting.
552     return 0 if $line =~ /^\s*C\+\+.+?:\s+(?:yes|no)\s*$/;
553     return 0 if $line =~ /^\s*C\+\+ Library: stdc\+\+$/;
554     # "Compiling" non binary files.
555     return 0 if $line =~ /^\s*Compiling \S+\.(?:py|pyx|el)['"]?\s*(?:\.\.\.|because it changed\.)?$/;
556     return 0 if $line =~ /^\s*[Cc]ompiling catalog \S+\.po\b/;
557     # "Compiling" with no file name.
558     if ($line =~ /^\s*[Cc]ompiling\s+(.+?)(?:\.\.\.)?$/) {
559         # $file_extension_regex may need spaces around the filename.
560         return 0 if not " $1 " =~ /$file_extension_regex/o;
561     }
562
563     my $file = $1;
564
565     # On the first pass we only check if this line is verbose or not.
566     return 1 if not defined $input_ref;
567
568     # Second pass, we have access to the next lines.
569     ${$skip_ref} = 0;
570
571     # CMake and other build systems print the non-verbose messages also when
572     # building verbose. If a compiler and the file name occurs in the next
573     # lines, treat it as verbose build.
574     if (defined $file) {
575         # Get filename, we can't use the complete path as only parts of it are
576         # used in the real compiler command.
577         $file =~ m{/([^/\s]+)$};
578         $file = $1;
579
580         for (my $i = 1; $i <= $line_count; $i++) {
581             my $next_line = $input_ref->[$line_offset + $i];
582             last unless defined $next_line;
583
584             if (index($next_line, $file) != -1 and $next_line =~ /$cc_regex/o) {
585                 # Not a non-verbose line, but we still have to skip the
586                 # current line as it doesn't contain any compiler commands.
587                 ${$skip_ref} = 1;
588                 return 0;
589             }
590         }
591     }
592
593     return 1;
594 }
595
596 # Remove @flags from $flag_refs_ref, uses $flag_renames_ref as reference.
597 sub remove_flags {
598     my ($flag_refs_ref, $flag_renames_ref, @flags) = @_;
599
600     my %removes = map { $_ => 1 } @flags;
601     foreach my $flags (@{$flag_refs_ref}) {
602         @{$flags} = grep {
603             # Flag found as string.
604             not exists $removes{$_}
605             # Flag found as string representation of regexp.
606                 and (not defined $flag_renames_ref->{$_}
607                         or not exists $removes{$flag_renames_ref->{$_}})
608         } @{$flags};
609     }
610
611     return;
612 }
613
614 # Modifies $flag_renames_ref hash.
615 sub compile_flag_regexp {
616     my ($flag_renames_ref, @flags) = @_;
617
618     my @result = ();
619     foreach my $flag (@flags) {
620         # Compile flag regexp for faster execution.
621         my $regex = qr/\s(['"]?)$flag\1(?:\s|\\)/;
622
623         # Store flag name in replacement string for correct flags in messages
624         # with qr//ed flag regexps.
625         $flag_renames_ref->{$regex}
626             = (exists $flag_renames_ref->{$flag})
627                 ? $flag_renames_ref->{$flag}
628                 : $flag;
629
630         push @result, $regex;
631     }
632     return @result;
633 }
634
635 # Does any extension in @extensions exist in %{$extensions_ref}?
636 sub extension_found {
637     my ($extensions_ref, @extensions) = @_;
638
639     foreach my $extension (@extensions) {
640         if (exists $extensions_ref->{$extension}) {
641             return 1;
642         }
643     }
644     return 0;
645 }
646
647
648 # MAIN
649
650 # Parse command line arguments.
651 my $option_help             = 0;
652 my $option_version          = 0;
653 my $option_pie              = 0;
654 my $option_bindnow          = 0;
655 my @option_ignore_arch      = ();
656 my @option_ignore_flag      = ();
657 my @option_ignore_arch_flag = ();
658 my @option_ignore_line      = ();
659 my @option_ignore_arch_line = ();
660 my $option_all              = 0;
661 my $option_arch             = undef;
662 my $option_buildd           = 0;
663 my $option_debian           = 0;
664    $option_color            = 0;
665 my $option_line_numbers     = 0;
666 if (not Getopt::Long::GetOptions(
667             'help|h|?'           => \$option_help,
668             'version'            => \$option_version,
669             # Hardening options.
670             'pie'                => \$option_pie,
671             'bindnow'            => \$option_bindnow,
672             'all'                => \$option_all,
673             # Ignore.
674             'ignore-arch=s'      => \@option_ignore_arch,
675             'ignore-flag=s'      => \@option_ignore_flag,
676             'ignore-arch-flag=s' => \@option_ignore_arch_flag,
677             'ignore-line=s'      => \@option_ignore_line,
678             'ignore-arch-line=s' => \@option_ignore_arch_line,
679             # Misc.
680             'color'              => \$option_color,
681             'arch=s'             => \$option_arch,
682             'buildd'             => \$option_buildd,
683             'debian'             => \$option_debian,
684             'line-numbers'       => \$option_line_numbers,
685         )) {
686     require Pod::Usage;
687     Pod::Usage::pod2usage(2);
688 }
689 if ($option_help) {
690     require Pod::Usage;
691     Pod::Usage::pod2usage(1);
692 }
693 if ($option_version) {
694     print <<"EOF";
695 blhc $VERSION  Copyright (C) 2012-2024  Simon Ruderich
696
697 This program is free software: you can redistribute it and/or modify
698 it under the terms of the GNU General Public License as published by
699 the Free Software Foundation, either version 3 of the License, or
700 (at your option) any later version.
701
702 This program is distributed in the hope that it will be useful,
703 but WITHOUT ANY WARRANTY; without even the implied warranty of
704 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
705 GNU General Public License for more details.
706
707 You should have received a copy of the GNU General Public License
708 along with this program.  If not, see <http://www.gnu.org/licenses/>.
709 EOF
710     exit 0;
711 }
712
713 # Arguments missing.
714 if (scalar @ARGV == 0) {
715     require Pod::Usage;
716     Pod::Usage::pod2usage(2);
717 }
718
719 # Don't load Term::ANSIColor in buildd mode because Term::ANSIColor is not
720 # installed on Debian's buildds.
721 if (not $option_buildd) {
722     require Term::ANSIColor;
723 }
724
725 if ($option_all) {
726     $option_pie     = 1;
727     $option_bindnow = 1;
728 }
729
730 # Precompiled ignores for faster lookup.
731 my %option_ignore_arch_flag = ();
732 my %option_ignore_arch_line = ();
733
734 # Strip flags which should be ignored.
735 if (scalar @option_ignore_flag > 0) {
736     remove_flags(\@flag_refs, \%flag_renames, @option_ignore_flag);
737 }
738 # Same for arch specific ignore flags, but only prepare here.
739 if (scalar @option_ignore_arch_flag > 0) {
740     foreach my $ignore (@option_ignore_arch_flag) {
741         my ($ignore_arch, $ignore_flag) = split /:/, $ignore, 2;
742
743         if (not $ignore_arch or not $ignore_flag) {
744             printf STDERR 'Value "%s" invalid for option ignore-arch-flag '
745                         . '("arch:flag" expected)' . "\n", $ignore;
746             require Pod::Usage;
747             Pod::Usage::pod2usage(2);
748         }
749
750         push @{$option_ignore_arch_flag{$ignore_arch}}, $ignore_flag;
751     }
752 }
753
754 # Precompile all flag regexps. any_flags_used(), all_flags_used() get a lot
755 # faster with this.
756 foreach my $flags (@flag_refs_all) {
757     @{$flags} = compile_flag_regexp(\%flag_renames, @{$flags});
758 }
759
760 # Precompile ignore line regexps, also anchor at beginning and end of line.
761 # Additional entries are also extracted from the build log, see below.
762 foreach my $ignore (@option_ignore_line) {
763     $ignore = qr/^$ignore$/;
764 }
765 # Same for arch specific ignore lines.
766 if (scalar @option_ignore_arch_line > 0) {
767     foreach my $ignore (@option_ignore_arch_line) {
768         my ($ignore_arch, $ignore_line) = split /:/, $ignore, 2;
769
770         if (not $ignore_arch or not $ignore_line) {
771             printf STDERR 'Value "%s" invalid for option ignore-arch-line '
772                         . '("arch:line" expected)' . "\n", $ignore;
773             require Pod::Usage;
774             Pod::Usage::pod2usage(2);
775         }
776
777         push @{$option_ignore_arch_line{$ignore_arch}}, qr/^$ignore_line$/;
778     }
779 }
780
781 # Final exit code.
782 my $exit = 0;
783
784 FILE:
785 foreach my $file (@ARGV) {
786     print "checking '$file'...\n" if scalar @ARGV > 1;
787
788     -f $file or die "No such file: $file";
789
790     open my $fh, '<', $file or die $!;
791
792     # Architecture of this file.
793     my $arch = $option_arch;
794
795     # Hardening options. Not all architectures support all hardening options.
796     my $harden_format  = 1;
797     my $harden_fortify = 1;
798     my $harden_stack   = 1;
799     my $harden_stack_strong = 1;
800     my $harden_relro   = 1;
801     my $harden_bindnow = $option_bindnow; # defaults to 0
802     my $harden_pie     = $option_pie;     # defaults to 0
803
804     # Number of parallel jobs to prevent false positives when detecting
805     # non-verbose builds. As not all jobs declare the number of parallel jobs
806     # use a large enough default.
807     my $parallel = 10;
808
809     # Don't check for PIE flags if automatically applied by the compiler. Only
810     # used in buildd and Debian mode.
811     my $disable_harden_pie = 0;
812     if ($option_debian) {
813         $disable_harden_pie = 1;
814     }
815
816     my $number = 0;
817     while (my $line = <$fh>) {
818         $number++;
819
820         # Detect architecture automatically unless overridden. For buildd logs
821         # only, doesn't use the dpkg-buildpackage header. Necessary to ignore
822         # build logs which aren't built (wrong architecture, build error,
823         # etc.).
824         if (not $arch) {
825             if (index($line, 'Build Architecture: ') == 0) {
826                 $arch = substr $line, 20, -1; # -1 to ignore '\n' at the end
827             # For old logs (sbuild << 0.63.0-1).
828             } elsif (index($line, 'Architecture: ') == 0) {
829                 $arch = substr $line, 14, -1; # -1 to ignore '\n' at the end
830             }
831         }
832
833         # dpkg-buildflags only provides hardening flags since 1.16.1, don't
834         # check for hardening flags in buildd mode if an older dpkg-dev is
835         # used. Default flags (-g -O2) are still checked.
836         #
837         # Packages which were built before 1.16.1 but used their own hardening
838         # flags are not checked.
839         #
840         # Strong stack protector is used since dpkg 1.17.11.
841         #
842         # Recent GCC versions automatically use PIE (only on supported
843         # architectures) and dpkg respects this properly since 1.18.15 and
844         # doesn't pass PIE flags manually.
845         if ($option_buildd
846                 and index($line, 'Toolchain package versions: ') == 0) {
847             require Dpkg::Version;
848
849             my $disable = 1;
850             my $disable_strong = 1;
851
852             if ($line =~ /\bdpkg-dev_(\S+)/) {
853                 if (Dpkg::Version::version_compare($1, '1.16.1') >= 0) {
854                     $disable = 0;
855                 }
856                 if (Dpkg::Version::version_compare($1, '1.17.11') >= 0) {
857                     $disable_strong = 0;
858                 }
859                 if (Dpkg::Version::version_compare($1, '1.18.15') >= 0) {
860                     $disable_harden_pie = 1;
861                 }
862             }
863
864             if ($disable) {
865                 $harden_format  = 0;
866                 $harden_fortify = 0;
867                 $harden_stack   = 0;
868                 $harden_relro   = 0;
869                 $harden_bindnow = 0;
870                 $harden_pie     = 0;
871             }
872             if ($disable_strong) {
873                 $harden_stack_strong = 0;
874             }
875         }
876
877         # The following two versions of CMake in Debian obeyed CPPFLAGS, but
878         # this was later dropped because upstream rejected the patch. Thus
879         # build logs with these versions will have fortify hardening flags
880         # enabled, even though they may be not correctly set and are missing
881         # when build with later CMake versions. Thanks to Aron Xu for letting
882         # me know.
883         if (index($line, 'Package versions: ') == 0
884                 and $line =~ /\bcmake_(\S+)/
885                 and ($1 eq '2.8.7-1' or $1 eq '2.8.7-2')) {
886             if (not $option_buildd) {
887                 error_invalid_cmake($1);
888                 $exit |= $exit_code{invalid_cmake};
889             } else {
890                 print "$buildd_tag{invalid_cmake}|$1|\n";
891             }
892         }
893
894         # Debian's build daemons use "Filtered Build-Depends:" (or just
895         # "Build-Depends:" in older versions) for the build dependencies, but
896         # pbuilder uses "Depends:"; support both.
897         if (index($line, 'Filtered Build-Depends: ') == 0
898                 or index($line, 'Build-Depends: ') == 0
899                 or index($line, 'Depends: ') == 0) {
900             # If hardening wrapper is used (wraps calls to gcc and adds
901             # hardening flags automatically) we can't perform any checks,
902             # abort.
903             if ($line =~ /\bhardening-wrapper\b/) {
904                 if (not $option_buildd) {
905                     error_hardening_wrapper();
906                     $exit |= $exit_code{hardening_wrapper};
907                 } else {
908                     print "$buildd_tag{hardening_wrapper}||\n";
909                 }
910                 next FILE;
911             }
912         }
913
914         # This flags is not always available, but if it is use it.
915         if ($line =~ /^DEB_BUILD_OPTIONS=.*\bparallel=(\d+)/) {
916             $parallel = $1 * 2;
917         }
918
919         # We skip over unimportant lines at the beginning of the log to
920         # prevent false positives.
921         last if index($line, 'dpkg-buildpackage: ') == 0;
922     }
923
924     # Input lines, contain only the lines with compiler commands.
925     my @input = ();
926     # Non-verbose lines in the input. Used to reduce calls to
927     # is_non_verbose_build() (which is quite slow) in the second loop when
928     # it's already clear if a line is non-verbose or not.
929     my @input_nonverbose = ();
930     # Input line number.
931     my @input_number = ();
932
933     my $continuation = 0;
934     my $complete_line = undef;
935     my $non_verbose;
936     while (my $line = <$fh>) {
937         $number++;
938
939         # And stop at the end of the build log. Package details (reported by
940         # the buildd logs) are not important for us. This also prevents false
941         # positives.
942         last if index($line, 'Build finished at ') == 0
943                 and $line =~ /^Build finished at \d{8}-\d{4}$/;
944
945         if (not $continuation) {
946             $non_verbose = 0;
947         }
948
949         # Detect architecture automatically unless overridden.
950         if (not $arch
951                 and index($line, 'dpkg-buildpackage: info: host architecture ') == 0) {
952             $arch = substr $line, 43, -1; # -1 to ignore '\n' at the end
953         # Older versions of dpkg-buildpackage
954         } elsif (not $arch
955                 and index($line, 'dpkg-buildpackage: host architecture ') == 0) {
956             $arch = substr $line, 37, -1; # -1 to ignore '\n' at the end
957
958             # Old buildd logs use e.g. "host architecture is alpha", remove
959             # the "is", otherwise debarch_to_debtriplet() will not detect the
960             # architecture.
961             if (index($arch, 'is ') == 0) {
962                 $arch = substr $arch, 3;
963             }
964         }
965
966         # Permit dynamic excludes from within the build log to ignore false
967         # positives. Cannot use a separate config file as we often only have
968         # the build log itself.
969         if (index($line, 'blhc: ignore-line-regexp: ') == 0) {
970             my $ignore = substr $line, 26, -1; # -1 to ignore '\n' at the end
971             push @option_ignore_line, qr/^$ignore$/;
972             next;
973         }
974
975         next if $line =~ /^\s*#/;
976         # Ignore compiler warnings for now.
977         next if $line =~ /$warning_regex/o;
978
979         if (not $option_buildd and index($line, "\033") != -1) { # \033 = esc
980             # Remove all ANSI color sequences which are sometimes used in
981             # non-verbose builds.
982             $line = Term::ANSIColor::colorstrip($line);
983             # Also strip '\0xf' (delete previous character), used by Elinks'
984             # build system.
985             $line =~ s/\x0f//g;
986             # And "ESC(B" which seems to be used on armhf and hurd (not sure
987             # what it does).
988             $line =~ s/\033\(B//g;
989         }
990
991         # Check if this line indicates a non verbose build.
992         my $skip = 0;
993         $non_verbose |= is_non_verbose_build($line, \$skip);
994         next if $skip;
995
996         # Treat each command as a single line so we don't ignore valid
997         # commands when handling false positives. split_line() is slow, only
998         # use it when necessary.
999         my @line = ($line !~ /(?:;|&&|\|\|)/)
1000                  ? ($line)
1001                  : split_line($line);
1002         foreach my $line (@line) {
1003             if ($continuation) {
1004                 $continuation = 0;
1005
1006                 # Join lines, but leave the "\" in place so it's clear where
1007                 # the original line break was.
1008                 chomp $complete_line;
1009                 $complete_line .= ' ' . $line;
1010             }
1011             # Line continuation, line ends with "\".
1012             if ($line =~ /\\$/) {
1013                 $continuation = 1;
1014                 # Start line continuation.
1015                 if (not defined $complete_line) {
1016                     $complete_line = $line;
1017                 }
1018                 next;
1019             }
1020
1021             # Use the complete line if a line continuation occurred.
1022             if (defined $complete_line) {
1023                 $line = $complete_line;
1024                 $complete_line = undef;
1025             }
1026
1027             my $noenv = $line;
1028             # Strip (basic) environment variables for compiler detection. This
1029             # prevents false positives when environment variables contain
1030             # compiler binaries. Nested quotes, command substitution, etc. is
1031             # not supported.
1032             $noenv =~ s/^
1033                 \s*
1034                 (?:
1035                     [a-zA-Z_]+          # environment variable name
1036                     =
1037                     (?:
1038                         [^\s"'\$`\\]+   # non-quoted string
1039                         |
1040                         '[^"'\$`\\]*'   # single-quoted string
1041                         |
1042                         "[^"'\$`\\]*"   # double-quoted string
1043                     )
1044                     \s+
1045                 )*
1046             //x;
1047             # Ignore lines with no compiler commands.
1048             next if not $non_verbose
1049                     and not $noenv =~ /$cc_regex_normal/o;
1050             # Ignore lines with no filenames with extensions. May miss some
1051             # non-verbose builds (e.g. "gcc -o test" [sic!]), but shouldn't be
1052             # a problem as the log will most likely contain other non-verbose
1053             # commands which are detected.
1054             next if not $non_verbose
1055                     and not $line =~ /$file_extension_regex/o;
1056
1057             # Ignore false positives.
1058             #
1059             # `./configure` output.
1060             next if not $non_verbose
1061                     and $line =~ /^(?:checking|[Cc]onfigure:) /;
1062             next if $line =~ /^\s*(?:Host\s+)?(?:C(?:\+\+)?\s+)?
1063                                 [Cc]ompiler[\s.]*:?\s+
1064                                 /x;
1065             next if $line =~ m{^\s*(?:-\s)?(?:HOST_)?(?:CC|CXX)
1066                                 \s*=\s*$cc_regex_full
1067                                 # optional compiler options, don't allow
1068                                 # "everything" here to prevent false negatives
1069                                 \s*(?:\s-\S+)*\s*$}xo;
1070             # `echo` is never a compiler command
1071             next if $line =~ /^\s*echo\s/;
1072             # Ignore calls to `make` because they can contain environment
1073             # variables which look like compiler commands, e.g. CC=).
1074             next if $line =~ /^\s*make\s/;
1075             # `moc-qt4`/`moc-qt5` contain '-I.../linux-g++' in their command
1076             # line (or similar for other architectures) which gets recognized
1077             # as a compiler line, but `moc-qt*` is only a preprocessor for Qt
1078             # C++ files. No hardening flags are relevant during this step,
1079             # thus ignore `moc-qt*` lines. The resulting files will be
1080             # compiled in a separate step (and therefore checked).
1081             next if $line =~ m{^\S+(?:/bin/moc(?:-qt[45])?|/lib/qt6/libexec/moc)
1082                                \s.+\s
1083                                -I\S+/mkspecs/[a-z]+-g\++(?:-64)?
1084                                \s}x;
1085             # nvcc is not a regular C compiler
1086             next if $line =~ m{^\S+/bin/nvcc\s};
1087             # Ignore false positives when the line contains only CC=gcc but no
1088             # other gcc command.
1089             if ($line =~ /(.*)CC=$cc_regex_full(.*)/o) {
1090                 my $before = $1;
1091                 my $after  = $2;
1092                 next if     not $before =~ /$cc_regex_normal/o
1093                         and not $after  =~ /$cc_regex_normal/o;
1094             }
1095             # Ignore false positives caused by gcc -v. It outputs a line
1096             # looking like a normal compiler line but which is sometimes
1097             # missing hardening flags, although the normal compiler line
1098             # contains them.
1099             next if $line =~ m{^\s+/usr/lib/gcc/$cc_regex_full_prefix/
1100                                    [0-9.]+/cc1(?:plus)?}xo;
1101             # Ignore false positive with `rm` which may remove files which
1102             # look like a compiler executable thus causing the line to be
1103             # treated as a normal compiler line.
1104             next if $line =~ m{^\s*rm\s+};
1105             next if $line =~ m{^\s*dwz\s+};
1106             # Some build systems emit "gcc > file".
1107             next if $line =~ m{$cc_regex_normal\s*>\s*\S+}o;
1108             # Hex output may contain "cc".
1109             next if $line =~ m#(?:\b[0-9a-fA-F]{2,}\b\s*){5}#;
1110             # Meson build output
1111             next if $line =~ /^C\+\+ linker for the host machine: /;
1112             # Embedded `gcc -print-*` commands
1113             next if $line =~ /`$cc_regex_normal\s*[^`]*-print-\S+`/;
1114             # cmake checking for compiler flags without setting CPPFLAGS
1115             next if $line =~ m{^\s*/usr/(bin|lib)/(ccache/)?c\+\+ -dM -E -c /usr/share/cmake-\S+/Modules/CMakeCXXCompilerABI\.cpp};
1116
1117             # Check if additional hardening options were used. Used to ensure
1118             # they are used for the complete build.
1119             $harden_pie     = 1 if any_flags_used($line, @def_cflags_pie,
1120                                                          @def_ldflags_pie);
1121             $harden_bindnow = 1 if any_flags_used($line, @def_ldflags_bindnow);
1122
1123             push @input, $line;
1124             push @input_nonverbose, $non_verbose;
1125             push @input_number, $number if $option_line_numbers;
1126         }
1127     }
1128
1129     close $fh or die $!;
1130
1131     # Ignore arch if requested.
1132     if (scalar @option_ignore_arch > 0 and $arch) {
1133         foreach my $ignore (@option_ignore_arch) {
1134             if ($arch eq $ignore) {
1135                 print "ignoring architecture '$arch'\n";
1136                 next FILE;
1137             }
1138         }
1139     }
1140
1141     if (scalar @input == 0) {
1142         if (not $option_buildd) {
1143             print "No compiler commands!\n";
1144             $exit |= $exit_code{no_compiler_commands};
1145         } else {
1146             print "$buildd_tag{no_compiler_commands}||\n";
1147         }
1148         next FILE;
1149     }
1150
1151     if ($option_buildd) {
1152         $statistics{commands} += scalar @input;
1153     }
1154
1155     # Option or auto detected.
1156     if ($arch) {
1157         # The following was partially copied from dpkg-dev 1.22.0
1158         # (/usr/share/perl5/Dpkg/Vendor/Debian.pm, set_build_features and
1159         # _add_build_flags()), copyright Raphaël Hertzog <hertzog@debian.org>,
1160         # Guillem Jover <guillem@debian.org>, Kees Cook <kees@debian.org>,
1161         # Canonical, Ltd. licensed under GPL version 2 or later. Keep it in
1162         # sync.
1163
1164         require Dpkg::Arch;
1165         my ($os, $cpu);
1166         # Recent dpkg versions use a quadruplet for arch. Support both.
1167         eval {
1168             (undef, undef, $os, $cpu) = Dpkg::Arch::debarch_to_debtuple($arch);
1169         };
1170         if ($@) {
1171             (undef, $os, $cpu) = Dpkg::Arch::debarch_to_debtriplet($arch);
1172         }
1173
1174         my %builtin_pie_arch = map { $_ => 1 } qw(
1175             amd64
1176             arm64
1177             armel
1178             armhf
1179             hurd-amd64
1180             hurd-i386
1181             i386
1182             kfreebsd-amd64
1183             kfreebsd-i386
1184             mips
1185             mips64
1186             mips64el
1187             mips64r6
1188             mips64r6el
1189             mipsel
1190             mipsn32
1191             mipsn32el
1192             mipsn32r6
1193             mipsn32r6el
1194             mipsr6
1195             mipsr6el
1196             powerpc
1197             ppc64
1198             ppc64el
1199             riscv64
1200             s390x
1201             sparc
1202             sparc64
1203         );
1204
1205         # Disable unsupported hardening options.
1206         if ($os !~ /^(?:linux|kfreebsd|knetbsd|hurd)$/ or $cpu eq 'hppa') {
1207             $harden_pie = 0;
1208         }
1209         if ($cpu =~ /^(?:ia64|alpha|hppa|nios2)$/ or $arch eq 'arm') {
1210             $harden_stack = 0;
1211             $harden_stack_strong = 0;
1212         }
1213         if ($cpu =~ /^(?:ia64|hppa)$/) {
1214             $harden_relro   = 0;
1215             $harden_bindnow = 0;
1216         }
1217
1218         if ($disable_harden_pie and exists $builtin_pie_arch{$arch}) {
1219             $harden_pie = 0;
1220         }
1221     }
1222
1223     # Default values.
1224     my @cflags   = @def_cflags;
1225     my @cxxflags = @def_cxxflags;
1226     my @cppflags = @def_cppflags;
1227     my @ldflags  = @def_ldflags;
1228     # Check the specified hardening options, same order as dpkg-buildflags.
1229     if ($harden_pie) {
1230         @cflags   = (@cflags,   @def_cflags_pie);
1231         @cxxflags = (@cxxflags, @def_cflags_pie);
1232         @ldflags  = (@ldflags,  @def_ldflags_pie);
1233     }
1234     if ($harden_stack_strong) {
1235         @cflags   = (@cflags,   @def_cflags_stack_strong);
1236         @cxxflags = (@cxxflags, @def_cflags_stack_strong);
1237     } elsif ($harden_stack) {
1238         @cflags   = (@cflags,   @def_cflags_stack);
1239         @cxxflags = (@cxxflags, @def_cflags_stack);
1240     }
1241     if ($harden_fortify) {
1242         @cflags   = (@cflags,   @def_cflags_fortify);
1243         @cxxflags = (@cxxflags, @def_cflags_fortify);
1244         @cppflags = (@cppflags, @def_cppflags_fortify);
1245     }
1246     if ($harden_format) {
1247         @cflags   = (@cflags,   @def_cflags_format);
1248         @cxxflags = (@cxxflags, @def_cflags_format);
1249     }
1250     if ($harden_relro) {
1251         @ldflags = (@ldflags, @def_ldflags_relro);
1252     }
1253     if ($harden_bindnow) {
1254         @ldflags = (@ldflags, @def_ldflags_bindnow);
1255     }
1256
1257     # Ada doesn't support format hardening flags, see #680117 for more
1258     # information. Same for fortran.
1259     my @cflags_backup;
1260     my @cflags_noformat = grep {
1261         my $ok = 1;
1262         foreach my $flag (@def_cflags_format) {
1263             $ok = 0 if $_ eq $flag;
1264         }
1265         $ok;
1266     } @cflags;
1267
1268     # Hack to fix cppflags_fortify_broken() if --ignore-flag
1269     # -D_FORTIFY_SOURCE=2 is used to ignore missing fortification. Only works
1270     # as long as @def_cppflags_fortify contains only one variable.
1271     if (scalar @def_cppflags_fortify == 0) {
1272         $harden_fortify = 0;
1273     }
1274
1275     # Ignore flags for this arch if requested.
1276     if ($arch and exists $option_ignore_arch_flag{$arch}) {
1277         my @local_flag_refs = (\@cflags, \@cxxflags, \@cppflags, \@ldflags);
1278
1279         remove_flags(\@local_flag_refs,
1280                      \%flag_renames,
1281                      @{$option_ignore_arch_flag{$arch}});
1282     }
1283
1284     my @ignore_line = @option_ignore_line;
1285     # Ignore lines for this arch if requested.
1286     if ($arch and exists $option_ignore_arch_line{$arch}) {
1287         @ignore_line = (@ignore_line, @{$option_ignore_arch_line{$arch}});
1288     }
1289
1290 LINE:
1291     for (my $i = 0; $i < scalar @input; $i++) {
1292         my $line = $input[$i];
1293
1294         # Ignore line if requested.
1295         foreach my $ignore (@ignore_line) {
1296             next LINE if $line =~ /$ignore/;
1297         }
1298
1299         my $skip = 0;
1300         if ($input_nonverbose[$i]
1301                 and is_non_verbose_build($line, \$skip,
1302                                          \@input, $i, $parallel)) {
1303             if (not $option_buildd) {
1304                 error_non_verbose_build($line, $input_number[$i]);
1305                 $exit |= $exit_code{non_verbose_build};
1306             } else {
1307                 $statistics{commands_nonverbose}++;
1308             }
1309             next;
1310         }
1311         # Even if it's a verbose build, we might have to skip this line (see
1312         # is_non_verbose_build()).
1313         next if $skip;
1314
1315         my $orig_line = $line;
1316
1317         # Remove everything until and including the compiler command. Makes
1318         # checks easier and faster.
1319         $line =~ s/^.*?$cc_regex//o;
1320         # "([...] test.c)" is not detected as 'test.c' - fix this by removing
1321         # the brace and similar characters at the line end.
1322         $line =~ s/['")]+$//;
1323
1324         # Skip unnecessary tests when only preprocessing.
1325         my $flag_preprocess = 0;
1326
1327         my $dependency = 0;
1328         my $preprocess = 0;
1329         my $compile    = 0;
1330         my $link       = 0;
1331
1332         # Preprocess, compile, assemble.
1333         if ($line =~ /\s(-E|-S|-c)\b/) {
1334             $preprocess      = 1;
1335             $flag_preprocess = 1 if $1 eq '-E';
1336             $compile         = 1 if $1 eq '-S' or $1 eq '-c';
1337         # Dependency generation for Makefiles. The other flags (-MF -MG -MP
1338         # -MT -MQ) are always used with -M/-MM.
1339         } elsif ($line =~ /\s(?:-M|-MM)\b/) {
1340             $dependency = 1;
1341         # Otherwise assume we are linking.
1342         } else {
1343             $link = 1;
1344         }
1345
1346         # -MD/-MMD also cause dependency generation, but they don't imply -E!
1347         if ($line =~ /\s(?:-MD|-MMD)\b/) {
1348             $dependency      = 0;
1349             $flag_preprocess = 0;
1350         }
1351
1352         # Dependency generation for Makefiles, no preprocessing or other flags
1353         # needed.
1354         next if $dependency;
1355
1356         # Get all file extensions on this line.
1357         my @extensions = $line =~ /$file_extension_regex/go;
1358         # Ignore all unknown extensions to speedup the search below.
1359         @extensions = grep { exists $extension{$_} } @extensions;
1360
1361         # These file types don't require preprocessing.
1362         if (extension_found(\%extensions_no_preprocess, @extensions)) {
1363             $preprocess = 0;
1364         }
1365         # These file types require preprocessing.
1366         if (extension_found(\%extensions_preprocess, @extensions)) {
1367             # Prevent false positives with "libtool: link: g++ -include test.h
1368             # .." compiler lines.
1369             if ($orig_line !~ /$libtool_link_regex/o) {
1370                 $preprocess = 1;
1371             }
1372         }
1373
1374         if (not $flag_preprocess) {
1375             # If there are source files then it's compiling/linking in one
1376             # step and we must check both. We only check for source files
1377             # here, because header files cause too many false positives.
1378             if (extension_found(\%extensions_compile_link, @extensions)) {
1379                 # Assembly files don't need CFLAGS.
1380                 if (not extension_found(\%extensions_compile, @extensions)
1381                         and extension_found(\%extensions_no_compile, @extensions)) {
1382                     $compile = 0;
1383                 # But the rest does.
1384                 } else {
1385                     $compile = 1;
1386                 }
1387             # No compilable extensions found, either linking or compiling
1388             # header flags.
1389             #
1390             # If there are also no object files we are just compiling headers
1391             # (.h -> .h.gch). Don't check for linker flags in this case. Due
1392             # to our liberal checks for compiler lines, this also reduces the
1393             # number of false positives considerably.
1394             } elsif ($link
1395                     and not extension_found(\%extensions_object, @extensions)) {
1396                 $link = 0;
1397             }
1398         }
1399
1400         my $compile_cpp = 0;
1401         my $restore_cflags = 0;
1402         # Assume CXXFLAGS are required when a C++ file is specified in the
1403         # compiler line.
1404         if ($compile
1405                 and extension_found(\%extensions_compile_cpp, @extensions)) {
1406             $compile     = 0;
1407             $compile_cpp = 1;
1408         # Ada needs special CFLAGS
1409         } elsif (extension_found(\%extensions_ada, @extensions)) {
1410             $restore_cflags = 1;
1411             $preprocess = 0; # Ada uses no CPPFLAGS
1412             @cflags_backup = @cflags;
1413             @cflags        = @cflags_noformat;
1414         # Same for fortran
1415         } elsif (extension_found(\%extensions_fortran, @extensions)) {
1416             $restore_cflags = 1;
1417             @cflags_backup = @cflags;
1418             @cflags        = @cflags_noformat;
1419         }
1420
1421         if ($option_buildd) {
1422             $statistics{preprocess}++  if $preprocess;
1423             $statistics{compile}++     if $compile;
1424             $statistics{compile_cpp}++ if $compile_cpp;
1425             $statistics{link}++        if $link;
1426         }
1427
1428         # Check if there are flags indicating a debug build. If that's true,
1429         # skip the check for -O2. This prevents fortification, but that's fine
1430         # for a debug build.
1431         if (any_flags_used($line, @def_cflags_debug)) {
1432             remove_flags([\@cflags], \%flag_renames, $def_cflags[1]);
1433             remove_flags([\@cppflags], \%flag_renames, $def_cppflags_fortify[0]);
1434         }
1435
1436         # Check hardening flags.
1437         my @missing;
1438         if ($compile and (not all_flags_used($line, \@missing, @cflags)
1439                     or (($harden_stack or $harden_stack_strong)
1440                         and cflags_stack_broken($line, \@missing,
1441                                                 $harden_stack_strong)))
1442                 # Libraries linked with -fPIC don't have to (and can't) be
1443                 # linked with -fPIE as well. It's no error if only PIE flags
1444                 # are missing.
1445                 and not pic_pie_conflict($line, $harden_pie, \@missing, @def_cflags_pie)
1446                 # Assume dpkg-buildflags returns the correct flags.
1447                 and index($line, '`dpkg-buildflags --get CFLAGS`') == -1) {
1448             if (not $option_buildd) {
1449                 error_flags('CFLAGS missing', \@missing, \%flag_renames,
1450                             $input[$i], $input_number[$i]);
1451                 $exit |= $exit_code{flags_missing};
1452             } else {
1453                 $statistics{compile_missing}++;
1454             }
1455         } elsif ($compile_cpp and not all_flags_used($line, \@missing, @cflags)
1456                 # Libraries linked with -fPIC don't have to (and can't) be
1457                 # linked with -fPIE as well. It's no error if only PIE flags
1458                 # are missing.
1459                 and not pic_pie_conflict($line, $harden_pie, \@missing, @def_cflags_pie)
1460                 # Assume dpkg-buildflags returns the correct flags.
1461                 and index($line, '`dpkg-buildflags --get CXXFLAGS`') == -1) {
1462             if (not $option_buildd) {
1463                 error_flags('CXXFLAGS missing', \@missing, \%flag_renames,
1464                             $input[$i], $input_number[$i]);
1465                 $exit |= $exit_code{flags_missing};
1466             } else {
1467                 $statistics{compile_cpp_missing}++;
1468             }
1469         }
1470         if ($preprocess
1471                 and (not all_flags_used($line, \@missing, @cppflags)
1472                     # The fortify flag might be overwritten, detect that.
1473                      or ($harden_fortify
1474                          and cppflags_fortify_broken($line, \@missing)))
1475                 # Assume dpkg-buildflags returns the correct flags.
1476                 and index($line, '`dpkg-buildflags --get CPPFLAGS`') == -1) {
1477             if (not $option_buildd) {
1478                 error_flags('CPPFLAGS missing', \@missing, \%flag_renames,
1479                             $input[$i], $input_number[$i]);
1480                 $exit |= $exit_code{flags_missing};
1481             } else {
1482                 $statistics{preprocess_missing}++;
1483             }
1484         }
1485         if ($link and not all_flags_used($line, \@missing, @ldflags)
1486                 # Same here, -fPIC conflicts with -fPIE.
1487                 and not pic_pie_conflict($line, $harden_pie, \@missing, @def_ldflags_pie)
1488                 # Assume dpkg-buildflags returns the correct flags.
1489                 and index($line, '`dpkg-buildflags --get LDFLAGS`') == -1) {
1490             if (not $option_buildd) {
1491                 error_flags('LDFLAGS missing', \@missing, \%flag_renames,
1492                             $input[$i], $input_number[$i]);
1493                 $exit |= $exit_code{flags_missing};
1494             } else {
1495                 $statistics{link_missing}++;
1496             }
1497         }
1498
1499         # Restore normal CFLAGS.
1500         if ($restore_cflags) {
1501             @cflags = @cflags_backup;
1502         }
1503     }
1504 }
1505
1506 # Print statistics for buildd mode, only output in this mode.
1507 if ($option_buildd) {
1508     my @warning;
1509
1510     if ($statistics{preprocess_missing}) {
1511         push @warning, sprintf 'CPPFLAGS %d (of %d)',
1512                                $statistics{preprocess_missing},
1513                                $statistics{preprocess};
1514     }
1515     if ($statistics{compile_missing}) {
1516         push @warning, sprintf 'CFLAGS %d (of %d)',
1517                                $statistics{compile_missing},
1518                                $statistics{compile};
1519     }
1520     if ($statistics{compile_cpp_missing}) {
1521         push @warning, sprintf 'CXXFLAGS %d (of %d)',
1522                                $statistics{compile_cpp_missing},
1523                                $statistics{compile_cpp};
1524     }
1525     if ($statistics{link_missing}) {
1526         push @warning, sprintf 'LDFLAGS %d (of %d)',
1527                                $statistics{link_missing},
1528                                $statistics{link};
1529     }
1530     if (scalar @warning) {
1531         local $" = ', '; # array join string
1532         print "$buildd_tag{flags_missing}|@warning missing|\n";
1533     }
1534
1535     if ($statistics{commands_nonverbose}) {
1536         printf "$buildd_tag{non_verbose_build}|%d (of %d) hidden|\n",
1537                $statistics{commands_nonverbose},
1538                $statistics{commands},
1539     }
1540 }
1541
1542
1543 exit $exit;
1544
1545
1546 __END__
1547
1548 =head1 NAME
1549
1550 blhc - build log hardening check, checks build logs for missing hardening flags
1551
1552 =head1 SYNOPSIS
1553
1554 B<blhc> [I<options>] I<< <dpkg-buildpackage build log file>.. >>
1555
1556 =head1 DESCRIPTION
1557
1558 blhc is a small tool which checks build logs for missing hardening flags. It's
1559 licensed under the GPL 3 or later.
1560
1561 It's designed to check build logs generated by Debian's dpkg-buildpackage (or
1562 tools using dpkg-buildpackage like pbuilder or sbuild (which is used for the
1563 official buildd build logs)) to help maintainers detect missing hardening
1564 flags in their packages.
1565
1566 Only gcc is detected as compiler at the moment. If other compilers support
1567 hardening flags as well, please report them.
1568
1569 If there's no output, no flags are missing and the build log is fine.
1570
1571 See F<README> for details about performed checks, auto-detection and
1572 limitations.
1573
1574 =head1 FALSE POSITIVES
1575
1576 To suppress false positives you can embed the following string in the build
1577 log:
1578
1579     blhc: ignore-line-regexp: REGEXP
1580
1581 All lines fully matching REGEXP (see B<--ignore-line> for details) will be
1582 ignored. The string can be embedded multiple times to ignore different
1583 regexps.
1584
1585 Please use this feature sparingly so that missing flags are not overlooked. If
1586 you find false positives which affect more packages please report a bug.
1587
1588 To generate this string simply use echo in C<debian/rules>; make sure to use @
1589 to suppress the echo command itself as it could also trigger a false positive.
1590 If the build process takes a long time edit the C<.build> file in place and
1591 tweak the ignore string until B<blhc --all --debian package.build> no longer
1592 reports any false positives.
1593
1594 =head1 OPTIONS
1595
1596 =over 8
1597
1598 =item B<--all>
1599
1600 Force check for all +all (+pie, +bindnow) hardening flags. By default it's
1601 auto detected.
1602
1603 =item B<--arch> I<architecture>
1604
1605 Set the specific architecture (e.g. amd64, armel, etc.), automatically
1606 disables hardening flags not available on this architecture. Is detected
1607 automatically if dpkg-buildpackage is used.
1608
1609 =item B<--bindnow>
1610
1611 Force check for all +bindnow hardening flags. By default it's auto detected.
1612
1613 =item B<--buildd>
1614
1615 Special mode for buildds when automatically parsing log files. The following
1616 changes are in effect:
1617
1618 =over 2
1619
1620 =item *
1621
1622 Print tags instead of normal warnings, see L</"BUILDD TAGS"> for a list of
1623 possible tags.
1624
1625 =item *
1626
1627 Don't check hardening flags in old log files (if dpkg-dev << 1.16.1 is
1628 detected).
1629
1630 =item *
1631
1632 Don't require Term::ANSIColor.
1633
1634 =item *
1635
1636 Return exit code 0, unless there was a error (-I, -W messages don't count as
1637 error).
1638
1639 =back
1640
1641 =item B<--debian>
1642
1643 Apply Debian-specific settings. At the moment this only disables checking for
1644 PIE which is automatically applied by Debian's GCC and no longer requires a
1645 compiler command line argument.
1646
1647 =item B<--color>
1648
1649 Use colored (ANSI) output for warning messages.
1650
1651 =item B<--line-numbers>
1652
1653 Display line numbers.
1654
1655 =item B<--ignore-arch> I<arch>
1656
1657 Ignore build logs from architectures matching I<arch>. I<arch> is a string.
1658
1659 Used to prevent false positives. This option can be specified multiple times.
1660
1661 =item B<--ignore-arch-flag> I<arch>:I<flag>
1662
1663 Like B<--ignore-flag>, but only ignore flag on I<arch>.
1664
1665 =item B<--ignore-arch-line> I<arch>:I<line>
1666
1667 Like B<--ignore-line>, but only ignore line on I<arch>.
1668
1669 =item B<--ignore-flag> I<flag>
1670
1671 Don't print an error when the specific flag is missing in a compiler line.
1672 I<flag> is a string.
1673
1674 Used to prevent false positives. This option can be specified multiple times.
1675
1676 =item B<--ignore-line> I<regex>
1677
1678 Ignore lines matching the given Perl regex. I<regex> is automatically anchored
1679 at the beginning and end of the line to prevent false negatives.
1680
1681 B<NOTE>: Not the input lines are checked, but the lines which are displayed in
1682 warnings (which have line continuation resolved).
1683
1684 Used to prevent false positives. This option can be specified multiple times.
1685
1686 =item B<--pie>
1687
1688 Force check for all +pie hardening flags. By default it's auto detected.
1689
1690 =item B<-h -? --help>
1691
1692 Print available options.
1693
1694 =item B<--version>
1695
1696 Print version number and license.
1697
1698 =back
1699
1700 Auto detection for B<--pie> and B<--bindnow> only works if at least one
1701 command uses the required hardening flag (e.g. -fPIE). Then it's required for
1702 all other commands as well.
1703
1704 =head1 EXAMPLES
1705
1706 Normal usage, parse a single log file.
1707
1708     blhc path/to/log/file
1709
1710 If there's no output, no flags are missing and the build log is fine.
1711
1712 Parse multiple log files. The exit code is ORed over all files.
1713
1714     blhc path/to/directory/with/log/files/*
1715
1716 Don't treat missing C<-g> as error:
1717
1718     blhc --ignore-flag -g path/to/log/file
1719
1720 Don't treat missing C<-pie> on kfreebsd-amd64 as error:
1721
1722     blhc --ignore-arch-flag kfreebsd-amd64:-pie path/to/log/file
1723
1724 Ignore lines consisting exactly of C<./script gcc file> which would cause a
1725 false positive.
1726
1727     blhc --ignore-line '\./script gcc file' path/to/log/file
1728
1729 Ignore lines matching C<./script gcc file> somewhere in the line.
1730
1731     blhc --ignore-line '.*\./script gcc file.*' path/to/log/file
1732
1733 Use blhc with pbuilder.
1734
1735     pbuilder path/to/package.dsc | tee path/log/file
1736     blhc path/to/file || echo flags missing
1737
1738 Assume this build log was created on a Debian system and thus don't warn about
1739 missing PIE flags if the current architecture injects them automatically (this
1740 is enabled in buildd mode per default). C<--arch> is necessary if the build
1741 log contains no architecture information as written by dpkg-buildpackage.
1742
1743     blhc --debian --all --arch=amd64 path/to/log/file
1744
1745 =head1 BUILDD TAGS
1746
1747 The following tags are used in I<--buildd> mode. In braces the additional data
1748 which is displayed.
1749
1750 =over 2
1751
1752 =item B<I-hardening-wrapper-used>
1753
1754 The package uses hardening-wrapper which intercepts calls to gcc and adds
1755 hardening flags. The build log doesn't contain any hardening flags and thus
1756 can't be checked by blhc.
1757
1758 =item B<W-compiler-flags-hidden> (summary of hidden lines)
1759
1760 Build log contains lines which hide the real compiler flags. For example:
1761
1762     CC test-a.c
1763     CC test-b.c
1764     CC test-c.c
1765     LD test
1766
1767 Most of the time either C<export V=1> or C<export verbose=1> in
1768 F<debian/rules> fixes builds with hidden compiler flags. Sometimes C<.SILENT>
1769 in a F<Makefile> must be removed. And as last resort the F<Makefile> must be
1770 patched to remove the C<@>s hiding the real compiler commands.
1771
1772 =item B<W-dpkg-buildflags-missing> (summary of missing flags)
1773
1774 CPPFLAGS, CFLAGS, CXXFLAGS, LDFLAGS missing.
1775
1776 =item B<I-invalid-cmake-used> (version)
1777
1778 By default CMake ignores CPPFLAGS thus missing those hardening flags. Debian
1779 patched CMake in versions 2.8.7-1 and 2.8.7-2 to respect CPPFLAGS, but this
1780 patch was rejected by upstream and later reverted in Debian. Thus those two
1781 versions show correct usage of CPPFLAGS even if the package doesn't correctly
1782 handle them (for example by passing them to CFLAGS). To prevent false
1783 negatives just blacklist those two versions.
1784
1785 =item B<I-no-compiler-commands>
1786
1787 No compiler commands were detected. Either the log contains none or they were
1788 not correctly detected by blhc (please report the bug in this case).
1789
1790 =back
1791
1792 =head1 EXIT STATUS
1793
1794 The exit status is a "bit mask", each listed status is ORed when the error
1795 condition occurs to get the result.
1796
1797 =over 4
1798
1799 =item B<0>
1800
1801 Success.
1802
1803 =item B<1>
1804
1805 No compiler commands were found.
1806
1807 =item B<2>
1808
1809 Invalid arguments/options given to blhc.
1810
1811 =item B<4>
1812
1813 Non verbose build.
1814
1815 =item B<8>
1816
1817 Missing hardening flags.
1818
1819 =item B<16>
1820
1821 Hardening wrapper detected, no tests performed.
1822
1823 =item B<32>
1824
1825 Invalid CMake version used. See B<I-invalid-cmake-used> under L</"BUILDD
1826 TAGS"> for a detailed explanation.
1827
1828 =back
1829
1830 =head1 AUTHOR
1831
1832 Simon Ruderich, E<lt>simon@ruderich.orgE<gt>
1833
1834 Thanks to to Bernhard R. Link E<lt>brlink@debian.orgE<gt> and Jaria Alto
1835 E<lt>jari.aalto@cante.netE<gt> for their valuable input and suggestions.
1836
1837 =head1 LICENSE AND COPYRIGHT
1838
1839 Copyright (C) 2012-2024 by Simon Ruderich
1840
1841 This program is free software: you can redistribute it and/or modify
1842 it under the terms of the GNU General Public License as published by
1843 the Free Software Foundation, either version 3 of the License, or
1844 (at your option) any later version.
1845
1846 This program is distributed in the hope that it will be useful,
1847 but WITHOUT ANY WARRANTY; without even the implied warranty of
1848 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
1849 GNU General Public License for more details.
1850
1851 You should have received a copy of the GNU General Public License
1852 along with this program.  If not, see <http://www.gnu.org/licenses/>.
1853
1854 =head1 SEE ALSO
1855
1856 L<hardening-check(1)>, L<dpkg-buildflags(1)>
1857
1858 =cut