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