]> ruderich.org/simon Gitweb - blhc/blhc.git/blob - bin/blhc
Release 0.14
[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>>\))?$};
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 $option_buildd) {
1183             print "No compiler commands!\n";
1184             $exit |= $exit_code{no_compiler_commands};
1185         } else {
1186             print "$buildd_tag{no_compiler_commands}||\n";
1187         }
1188         next FILE;
1189     }
1190
1191     if ($option_buildd) {
1192         $statistics{commands} += scalar @input;
1193     }
1194
1195     # Option or auto detected.
1196     my @harden_branch_flags;
1197     if ($arch) {
1198         # The following was partially copied from dpkg-dev 1.22.0
1199         # (/usr/share/perl5/Dpkg/Vendor/Debian.pm, set_build_features and
1200         # _add_build_flags()), copyright Raphaël Hertzog <hertzog@debian.org>,
1201         # Guillem Jover <guillem@debian.org>, Kees Cook <kees@debian.org>,
1202         # Canonical, Ltd. licensed under GPL version 2 or later. Keep it in
1203         # sync.
1204
1205         require Dpkg::Arch;
1206         my ($os, $cpu);
1207         # Recent dpkg versions use a quadruplet for arch. Support both.
1208         eval {
1209             (undef, undef, $os, $cpu) = Dpkg::Arch::debarch_to_debtuple($arch);
1210         };
1211         if ($@) {
1212             (undef, $os, $cpu) = Dpkg::Arch::debarch_to_debtriplet($arch);
1213         }
1214
1215         my %builtin_pie_arch = map { $_ => 1 } qw(
1216             amd64
1217             arm64
1218             armel
1219             armhf
1220             hurd-amd64
1221             hurd-i386
1222             i386
1223             kfreebsd-amd64
1224             kfreebsd-i386
1225             loong64
1226             mips
1227             mips64
1228             mips64el
1229             mips64r6
1230             mips64r6el
1231             mipsel
1232             mipsn32
1233             mipsn32el
1234             mipsn32r6
1235             mipsn32r6el
1236             mipsr6
1237             mipsr6el
1238             powerpc
1239             ppc64
1240             ppc64el
1241             riscv64
1242             s390x
1243             sparc
1244             sparc64
1245         );
1246
1247         # Disable unsupported hardening options.
1248         if ($disable_harden_pie and exists $builtin_pie_arch{$arch}) {
1249             $harden_pie = 0;
1250         }
1251         if ($os !~ /^(?:linux|kfreebsd|hurd)$/
1252                 or $cpu =~ /^(?:alpha|hppa|ia64)$/) {
1253             $harden_pie = 0;
1254         }
1255         if ($cpu =~ /^(?:ia64|alpha|hppa|nios2)$/ or $arch eq 'arm') {
1256             $harden_stack = 0;
1257             $harden_stack_strong = 0;
1258         }
1259         if ($arch !~ /^(?:amd64|arm64|armhf|armel)$/) {
1260             $harden_stack_clash = 0;
1261         }
1262         if ($cpu =~ /^(?:ia64|hppa)$/) {
1263             $harden_relro   = 0;
1264             $harden_bindnow = 0;
1265         }
1266         if ($cpu eq 'amd64') {
1267             @harden_branch_flags = @def_cflags_branch_amd64;
1268         } elsif ($cpu eq 'arm64') {
1269             @harden_branch_flags = @def_cflags_branch_arm64;
1270         }
1271     }
1272
1273     # Default values.
1274     my @cflags   = @def_cflags;
1275     my @cxxflags = @def_cxxflags;
1276     my @cppflags = @def_cppflags;
1277     my @ldflags  = @def_ldflags;
1278     # Check the specified hardening options, same order as dpkg-buildflags.
1279     if ($harden_pie) {
1280         @cflags   = (@cflags,   @def_cflags_pie);
1281         @cxxflags = (@cxxflags, @def_cflags_pie);
1282         @ldflags  = (@ldflags,  @def_ldflags_pie);
1283     }
1284     if ($harden_stack_strong) {
1285         @cflags   = (@cflags,   @def_cflags_stack_strong);
1286         @cxxflags = (@cxxflags, @def_cflags_stack_strong);
1287     } elsif ($harden_stack) {
1288         @cflags   = (@cflags,   @def_cflags_stack);
1289         @cxxflags = (@cxxflags, @def_cflags_stack);
1290     }
1291     if ($harden_stack_clash) {
1292         @cflags   = (@cflags,   @def_cflags_stack_clash);
1293         @cxxflags = (@cxxflags, @def_cflags_stack_clash);
1294     }
1295     if ($harden_fortify) {
1296         @cflags   = (@cflags,   @def_cflags_fortify);
1297         @cxxflags = (@cxxflags, @def_cflags_fortify);
1298         @cppflags = (@cppflags, @def_cppflags_fortify);
1299     }
1300     if ($harden_format) {
1301         @cflags   = (@cflags,   @def_cflags_format);
1302         @cxxflags = (@cxxflags, @def_cflags_format);
1303     }
1304     if ($harden_branch and @harden_branch_flags) {
1305         @cflags   = (@cflags,   @harden_branch_flags);
1306         @cxxflags = (@cxxflags, @harden_branch_flags);
1307     }
1308     if ($harden_relro) {
1309         @ldflags = (@ldflags, @def_ldflags_relro);
1310     }
1311     if ($harden_bindnow) {
1312         @ldflags = (@ldflags, @def_ldflags_bindnow);
1313     }
1314
1315     # Ada doesn't support format hardening flags, see #680117 for more
1316     # information. Same for fortran.
1317     my @cflags_backup;
1318     my @cflags_noformat = grep {
1319         my $ok = 1;
1320         foreach my $flag (@def_cflags_format) {
1321             $ok = 0 if $_ eq $flag;
1322         }
1323         $ok;
1324     } @cflags;
1325
1326     # Hack to fix cppflags_fortify_broken() if --ignore-flag
1327     # -D_FORTIFY_SOURCE=2 is used to ignore missing fortification. Only works
1328     # as long as @def_cppflags_fortify contains only one variable.
1329     if (scalar @def_cppflags_fortify == 0) {
1330         $harden_fortify = 0;
1331     }
1332
1333     # Ignore flags for this arch if requested.
1334     if ($arch and exists $option_ignore_arch_flag{$arch}) {
1335         my @local_flag_refs = (\@cflags, \@cxxflags, \@cppflags, \@ldflags);
1336
1337         remove_flags(\@local_flag_refs,
1338                      \%flag_renames,
1339                      @{$option_ignore_arch_flag{$arch}});
1340     }
1341
1342     my @ignore_line = @option_ignore_line;
1343     # Ignore lines for this arch if requested.
1344     if ($arch and exists $option_ignore_arch_line{$arch}) {
1345         @ignore_line = (@ignore_line, @{$option_ignore_arch_line{$arch}});
1346     }
1347
1348 LINE:
1349     for (my $i = 0; $i < scalar @input; $i++) {
1350         my $line = $input[$i];
1351
1352         # Ignore line if requested.
1353         foreach my $ignore (@ignore_line) {
1354             next LINE if $line =~ /$ignore/;
1355         }
1356
1357         my $skip = 0;
1358         if ($input_nonverbose[$i]
1359                 and is_non_verbose_build($line, $cargo, \$skip,
1360                                          \@input, $i, $parallel)) {
1361             if (not $option_buildd) {
1362                 error_non_verbose_build($line, $input_number[$i]);
1363                 $exit |= $exit_code{non_verbose_build};
1364             } else {
1365                 $statistics{commands_nonverbose}++;
1366             }
1367             next;
1368         }
1369         # Even if it's a verbose build, we might have to skip this line (see
1370         # is_non_verbose_build()).
1371         next if $skip;
1372
1373         my $orig_line = $line;
1374
1375         # Remove everything until and including the compiler command. Makes
1376         # checks easier and faster.
1377         $line =~ s/^.*?$cc_regex//o;
1378         # "([...] test.c)" is not detected as 'test.c' - fix this by removing
1379         # the brace and similar characters at the line end.
1380         $line =~ s/['")]+$//;
1381
1382         # Skip unnecessary tests when only preprocessing.
1383         my $flag_preprocess = 0;
1384
1385         my $dependency = 0;
1386         my $preprocess = 0;
1387         my $compile    = 0;
1388         my $link       = 0;
1389
1390         # Preprocess, compile, assemble.
1391         if ($line =~ /\s(-E|-S|-c)\b/) {
1392             $preprocess      = 1;
1393             $flag_preprocess = 1 if $1 eq '-E';
1394             $compile         = 1 if $1 eq '-S' or $1 eq '-c';
1395         # Dependency generation for Makefiles. The other flags (-MF -MG -MP
1396         # -MT -MQ) are always used with -M/-MM.
1397         } elsif ($line =~ /\s(?:-M|-MM)\b/) {
1398             $dependency = 1;
1399         # Otherwise assume we are linking.
1400         } else {
1401             $link = 1;
1402         }
1403
1404         # -MD/-MMD also cause dependency generation, but they don't imply -E!
1405         if ($line =~ /\s(?:-MD|-MMD)\b/) {
1406             $dependency      = 0;
1407             $flag_preprocess = 0;
1408         }
1409
1410         # Dependency generation for Makefiles, no preprocessing or other flags
1411         # needed.
1412         next if $dependency;
1413
1414         # Get all file extensions on this line.
1415         my @extensions = $line =~ /$file_extension_regex/go;
1416         # Ignore all unknown extensions to speedup the search below.
1417         @extensions = grep { exists $extension{$_} } @extensions;
1418
1419         # These file types don't require preprocessing.
1420         if (extension_found(\%extensions_no_preprocess, @extensions)) {
1421             $preprocess = 0;
1422         }
1423         # These file types require preprocessing.
1424         if (extension_found(\%extensions_preprocess, @extensions)) {
1425             # Prevent false positives with "libtool: link: g++ -include test.h
1426             # .." compiler lines.
1427             if ($orig_line !~ /$libtool_link_regex/o) {
1428                 $preprocess = 1;
1429             }
1430         }
1431
1432         if (not $flag_preprocess) {
1433             # If there are source files then it's compiling/linking in one
1434             # step and we must check both. We only check for source files
1435             # here, because header files cause too many false positives.
1436             if (extension_found(\%extensions_compile_link, @extensions)) {
1437                 # Assembly files don't need CFLAGS.
1438                 if (not extension_found(\%extensions_compile, @extensions)
1439                         and extension_found(\%extensions_no_compile, @extensions)) {
1440                     $compile = 0;
1441                 # But the rest does.
1442                 } else {
1443                     $compile = 1;
1444                 }
1445             # No compilable extensions found, either linking or compiling
1446             # header flags.
1447             #
1448             # If there are also no object files we are just compiling headers
1449             # (.h -> .h.gch). Don't check for linker flags in this case. Due
1450             # to our liberal checks for compiler lines, this also reduces the
1451             # number of false positives considerably.
1452             } elsif ($link
1453                     and not extension_found(\%extensions_object, @extensions)) {
1454                 $link = 0;
1455             }
1456         }
1457
1458         my $compile_cpp = 0;
1459         my $restore_cflags = 0;
1460         # Assume CXXFLAGS are required when a C++ file is specified in the
1461         # compiler line.
1462         if ($compile
1463                 and extension_found(\%extensions_compile_cpp, @extensions)) {
1464             $compile     = 0;
1465             $compile_cpp = 1;
1466         # Ada needs special CFLAGS
1467         } elsif (extension_found(\%extensions_ada, @extensions)) {
1468             $restore_cflags = 1;
1469             $preprocess = 0; # Ada uses no CPPFLAGS
1470             @cflags_backup = @cflags;
1471             @cflags        = @cflags_noformat;
1472         # Same for fortran
1473         } elsif (extension_found(\%extensions_fortran, @extensions)) {
1474             $restore_cflags = 1;
1475             @cflags_backup = @cflags;
1476             @cflags        = @cflags_noformat;
1477         }
1478
1479         if ($option_buildd) {
1480             $statistics{preprocess}++  if $preprocess;
1481             $statistics{compile}++     if $compile;
1482             $statistics{compile_cpp}++ if $compile_cpp;
1483             $statistics{link}++        if $link;
1484         }
1485
1486         # Check if there are flags indicating a debug build. If that's true,
1487         # skip the check for -O2. This prevents fortification, but that's fine
1488         # for a debug build.
1489         if (any_flags_used($line, @def_cflags_debug)) {
1490             remove_flags([\@cflags], \%flag_renames, $def_cflags[1]);
1491             remove_flags([\@cppflags], \%flag_renames, $def_cppflags_fortify[0]);
1492         }
1493
1494         # Check hardening flags.
1495         my @missing;
1496         if ($compile and (not all_flags_used($line, \@missing, @cflags)
1497                     or (($harden_stack or $harden_stack_strong)
1498                         and cflags_stack_broken($line, \@missing,
1499                                                 $harden_stack_strong)))
1500                 # Libraries linked with -fPIC don't have to (and can't) be
1501                 # linked with -fPIE as well. It's no error if only PIE flags
1502                 # are missing.
1503                 and not pic_pie_conflict($line, $harden_pie, \@missing, @def_cflags_pie)
1504                 # Assume dpkg-buildflags returns the correct flags.
1505                 and index($line, '`dpkg-buildflags --get CFLAGS`') == -1) {
1506             if (not $option_buildd) {
1507                 error_flags('CFLAGS missing', \@missing, \%flag_renames,
1508                             $input[$i], $input_number[$i]);
1509                 $exit |= $exit_code{flags_missing};
1510             } else {
1511                 $statistics{compile_missing}++;
1512             }
1513         } elsif ($compile_cpp and not all_flags_used($line, \@missing, @cflags)
1514                 # Libraries linked with -fPIC don't have to (and can't) be
1515                 # linked with -fPIE as well. It's no error if only PIE flags
1516                 # are missing.
1517                 and not pic_pie_conflict($line, $harden_pie, \@missing, @def_cflags_pie)
1518                 # Assume dpkg-buildflags returns the correct flags.
1519                 and index($line, '`dpkg-buildflags --get CXXFLAGS`') == -1) {
1520             if (not $option_buildd) {
1521                 error_flags('CXXFLAGS missing', \@missing, \%flag_renames,
1522                             $input[$i], $input_number[$i]);
1523                 $exit |= $exit_code{flags_missing};
1524             } else {
1525                 $statistics{compile_cpp_missing}++;
1526             }
1527         }
1528         if ($preprocess
1529                 and (not all_flags_used($line, \@missing, @cppflags)
1530                     # The fortify flag might be overwritten, detect that.
1531                      or ($harden_fortify
1532                          and cppflags_fortify_broken($line, \@missing)))
1533                 # Assume dpkg-buildflags returns the correct flags.
1534                 and index($line, '`dpkg-buildflags --get CPPFLAGS`') == -1) {
1535             if (not $option_buildd) {
1536                 error_flags('CPPFLAGS missing', \@missing, \%flag_renames,
1537                             $input[$i], $input_number[$i]);
1538                 $exit |= $exit_code{flags_missing};
1539             } else {
1540                 $statistics{preprocess_missing}++;
1541             }
1542         }
1543         if ($link and not all_flags_used($line, \@missing, @ldflags)
1544                 # Same here, -fPIC conflicts with -fPIE.
1545                 and not pic_pie_conflict($line, $harden_pie, \@missing, @def_ldflags_pie)
1546                 # Assume dpkg-buildflags returns the correct flags.
1547                 and index($line, '`dpkg-buildflags --get LDFLAGS`') == -1) {
1548             if (not $option_buildd) {
1549                 error_flags('LDFLAGS missing', \@missing, \%flag_renames,
1550                             $input[$i], $input_number[$i]);
1551                 $exit |= $exit_code{flags_missing};
1552             } else {
1553                 $statistics{link_missing}++;
1554             }
1555         }
1556
1557         # Restore normal CFLAGS.
1558         if ($restore_cflags) {
1559             @cflags = @cflags_backup;
1560         }
1561     }
1562 }
1563
1564 # Print statistics for buildd mode, only output in this mode.
1565 if ($option_buildd) {
1566     my @warning;
1567
1568     if ($statistics{preprocess_missing}) {
1569         push @warning, sprintf 'CPPFLAGS %d (of %d)',
1570                                $statistics{preprocess_missing},
1571                                $statistics{preprocess};
1572     }
1573     if ($statistics{compile_missing}) {
1574         push @warning, sprintf 'CFLAGS %d (of %d)',
1575                                $statistics{compile_missing},
1576                                $statistics{compile};
1577     }
1578     if ($statistics{compile_cpp_missing}) {
1579         push @warning, sprintf 'CXXFLAGS %d (of %d)',
1580                                $statistics{compile_cpp_missing},
1581                                $statistics{compile_cpp};
1582     }
1583     if ($statistics{link_missing}) {
1584         push @warning, sprintf 'LDFLAGS %d (of %d)',
1585                                $statistics{link_missing},
1586                                $statistics{link};
1587     }
1588     if (scalar @warning) {
1589         local $" = ', '; # array join string
1590         print "$buildd_tag{flags_missing}|@warning missing|\n";
1591     }
1592
1593     if ($statistics{commands_nonverbose}) {
1594         printf "$buildd_tag{non_verbose_build}|%d (of %d) hidden|\n",
1595                $statistics{commands_nonverbose},
1596                $statistics{commands},
1597     }
1598 }
1599
1600
1601 exit $exit;
1602
1603
1604 __END__
1605
1606 =head1 NAME
1607
1608 blhc - build log hardening check, checks build logs for missing hardening flags
1609
1610 =head1 SYNOPSIS
1611
1612 B<blhc> [I<options>] I<< <dpkg-buildpackage build log file>.. >>
1613
1614 =head1 DESCRIPTION
1615
1616 blhc is a small tool which checks build logs for missing hardening flags. It's
1617 licensed under the GPL 3 or later.
1618
1619 It's designed to check build logs generated by Debian's dpkg-buildpackage (or
1620 tools using dpkg-buildpackage like pbuilder or sbuild (which is used for the
1621 official buildd build logs)) to help maintainers detect missing hardening
1622 flags in their packages.
1623
1624 Only gcc is detected as compiler at the moment. If other compilers support
1625 hardening flags as well, please report them.
1626
1627 If there's no output, no flags are missing and the build log is fine.
1628
1629 See F<README> for details about performed checks, auto-detection and
1630 limitations.
1631
1632 =head1 FALSE POSITIVES
1633
1634 To suppress false positives you can embed the following string in the build
1635 log:
1636
1637     blhc: ignore-line-regexp: REGEXP
1638
1639 All lines fully matching REGEXP (see B<--ignore-line> for details) will be
1640 ignored. The string can be embedded multiple times to ignore different
1641 regexps.
1642
1643 Please use this feature sparingly so that missing flags are not overlooked. If
1644 you find false positives which affect more packages please report a bug.
1645
1646 To generate this string simply use echo in C<debian/rules>; make sure to use @
1647 to suppress the echo command itself as it could also trigger a false positive.
1648 If the build process takes a long time edit the C<.build> file in place and
1649 tweak the ignore string until B<blhc --all --debian package.build> no longer
1650 reports any false positives.
1651
1652 =head1 OPTIONS
1653
1654 =over 8
1655
1656 =item B<--all>
1657
1658 Force check for all +all (+pie, +bindnow) hardening flags. By default it's
1659 auto detected.
1660
1661 =item B<--arch> I<architecture>
1662
1663 Set the specific architecture (e.g. amd64, armel, etc.), automatically
1664 disables hardening flags not available on this architecture. Is detected
1665 automatically if dpkg-buildpackage is used.
1666
1667 =item B<--bindnow>
1668
1669 Force check for all +bindnow hardening flags. By default it's auto detected.
1670
1671 =item B<--buildd>
1672
1673 Special mode for buildds when automatically parsing log files. The following
1674 changes are in effect:
1675
1676 =over 2
1677
1678 =item *
1679
1680 Print tags instead of normal warnings, see L</"BUILDD TAGS"> for a list of
1681 possible tags.
1682
1683 =item *
1684
1685 Don't check hardening flags in old log files (if dpkg-dev << 1.16.1 is
1686 detected).
1687
1688 =item *
1689
1690 Don't require Term::ANSIColor.
1691
1692 =item *
1693
1694 Return exit code 0, unless there was a error (-I, -W messages don't count as
1695 error).
1696
1697 =back
1698
1699 =item B<--debian>
1700
1701 Apply Debian-specific settings. At the moment this only disables checking for
1702 PIE which is automatically applied by Debian's GCC and no longer requires a
1703 compiler command line argument.
1704
1705 =item B<--color>
1706
1707 Use colored (ANSI) output for warning messages.
1708
1709 =item B<--line-numbers>
1710
1711 Display line numbers.
1712
1713 =item B<--ignore-arch> I<arch>
1714
1715 Ignore build logs from architectures matching I<arch>. I<arch> is a string.
1716
1717 Used to prevent false positives. This option can be specified multiple times.
1718
1719 =item B<--ignore-arch-flag> I<arch>:I<flag>
1720
1721 Like B<--ignore-flag>, but only ignore flag on I<arch>.
1722
1723 =item B<--ignore-arch-line> I<arch>:I<line>
1724
1725 Like B<--ignore-line>, but only ignore line on I<arch>.
1726
1727 =item B<--ignore-flag> I<flag>
1728
1729 Don't print an error when the specific flag is missing in a compiler line.
1730 I<flag> is a string.
1731
1732 Used to prevent false positives. This option can be specified multiple times.
1733
1734 =item B<--ignore-line> I<regex>
1735
1736 Ignore lines matching the given Perl regex. I<regex> is automatically anchored
1737 at the beginning and end of the line to prevent false negatives.
1738
1739 B<NOTE>: Not the input lines are checked, but the lines which are displayed in
1740 warnings (which have line continuation resolved).
1741
1742 Used to prevent false positives. This option can be specified multiple times.
1743
1744 =item B<--pie>
1745
1746 Force check for all +pie hardening flags. By default it's auto detected.
1747
1748 =item B<-h -? --help>
1749
1750 Print available options.
1751
1752 =item B<--version>
1753
1754 Print version number and license.
1755
1756 =back
1757
1758 Auto detection for B<--pie> and B<--bindnow> only works if at least one
1759 command uses the required hardening flag (e.g. -fPIE). Then it's required for
1760 all other commands as well.
1761
1762 =head1 EXAMPLES
1763
1764 Normal usage, parse a single log file.
1765
1766     blhc path/to/log/file
1767
1768 If there's no output, no flags are missing and the build log is fine.
1769
1770 Parse multiple log files. The exit code is ORed over all files.
1771
1772     blhc path/to/directory/with/log/files/*
1773
1774 Don't treat missing C<-g> as error:
1775
1776     blhc --ignore-flag -g path/to/log/file
1777
1778 Don't treat missing C<-pie> on kfreebsd-amd64 as error:
1779
1780     blhc --ignore-arch-flag kfreebsd-amd64:-pie path/to/log/file
1781
1782 Ignore lines consisting exactly of C<./script gcc file> which would cause a
1783 false positive.
1784
1785     blhc --ignore-line '\./script gcc file' path/to/log/file
1786
1787 Ignore lines matching C<./script gcc file> somewhere in the line.
1788
1789     blhc --ignore-line '.*\./script gcc file.*' path/to/log/file
1790
1791 Use blhc with pbuilder.
1792
1793     pbuilder path/to/package.dsc | tee path/log/file
1794     blhc path/to/file || echo flags missing
1795
1796 Assume this build log was created on a Debian system and thus don't warn about
1797 missing PIE flags if the current architecture injects them automatically (this
1798 is enabled in buildd mode per default). C<--arch> is necessary if the build
1799 log contains no architecture information as written by dpkg-buildpackage.
1800
1801     blhc --debian --all --arch=amd64 path/to/log/file
1802
1803 =head1 BUILDD TAGS
1804
1805 The following tags are used in I<--buildd> mode. In braces the additional data
1806 which is displayed.
1807
1808 =over 2
1809
1810 =item B<I-hardening-wrapper-used>
1811
1812 The package uses hardening-wrapper which intercepts calls to gcc and adds
1813 hardening flags. The build log doesn't contain any hardening flags and thus
1814 can't be checked by blhc.
1815
1816 =item B<W-compiler-flags-hidden> (summary of hidden lines)
1817
1818 Build log contains lines which hide the real compiler flags. For example:
1819
1820     CC test-a.c
1821     CC test-b.c
1822     CC test-c.c
1823     LD test
1824
1825 Most of the time either C<export V=1> or C<export verbose=1> in
1826 F<debian/rules> fixes builds with hidden compiler flags. Sometimes C<.SILENT>
1827 in a F<Makefile> must be removed. And as last resort the F<Makefile> must be
1828 patched to remove the C<@>s hiding the real compiler commands.
1829
1830 =item B<W-dpkg-buildflags-missing> (summary of missing flags)
1831
1832 CPPFLAGS, CFLAGS, CXXFLAGS, LDFLAGS missing.
1833
1834 =item B<I-invalid-cmake-used> (version)
1835
1836 By default CMake ignores CPPFLAGS thus missing those hardening flags. Debian
1837 patched CMake in versions 2.8.7-1 and 2.8.7-2 to respect CPPFLAGS, but this
1838 patch was rejected by upstream and later reverted in Debian. Thus those two
1839 versions show correct usage of CPPFLAGS even if the package doesn't correctly
1840 handle them (for example by passing them to CFLAGS). To prevent false
1841 negatives just blacklist those two versions.
1842
1843 =item B<I-no-compiler-commands>
1844
1845 No compiler commands were detected. Either the log contains none or they were
1846 not correctly detected by blhc (please report the bug in this case).
1847
1848 =back
1849
1850 =head1 EXIT STATUS
1851
1852 The exit status is a "bit mask", each listed status is ORed when the error
1853 condition occurs to get the result.
1854
1855 =over 4
1856
1857 =item B<0>
1858
1859 Success.
1860
1861 =item B<1>
1862
1863 No compiler commands were found.
1864
1865 =item B<2>
1866
1867 Invalid arguments/options given to blhc.
1868
1869 =item B<4>
1870
1871 Non verbose build.
1872
1873 =item B<8>
1874
1875 Missing hardening flags.
1876
1877 =item B<16>
1878
1879 Hardening wrapper detected, no tests performed.
1880
1881 =item B<32>
1882
1883 Invalid CMake version used. See B<I-invalid-cmake-used> under L</"BUILDD
1884 TAGS"> for a detailed explanation.
1885
1886 =back
1887
1888 =head1 AUTHOR
1889
1890 Simon Ruderich, E<lt>simon@ruderich.orgE<gt>
1891
1892 Thanks to to Bernhard R. Link E<lt>brlink@debian.orgE<gt> and Jaria Alto
1893 E<lt>jari.aalto@cante.netE<gt> for their valuable input and suggestions.
1894
1895 =head1 LICENSE AND COPYRIGHT
1896
1897 Copyright (C) 2012-2024 by Simon Ruderich
1898
1899 This program is free software: you can redistribute it and/or modify
1900 it under the terms of the GNU General Public License as published by
1901 the Free Software Foundation, either version 3 of the License, or
1902 (at your option) any later version.
1903
1904 This program is distributed in the hope that it will be useful,
1905 but WITHOUT ANY WARRANTY; without even the implied warranty of
1906 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
1907 GNU General Public License for more details.
1908
1909 You should have received a copy of the GNU General Public License
1910 along with this program.  If not, see <http://www.gnu.org/licenses/>.
1911
1912 =head1 SEE ALSO
1913
1914 L<hardening-check(1)>, L<dpkg-buildflags(1)>
1915
1916 =cut