]> ruderich.org/simon Gitweb - blhc/blhc.git/blob - blhc
f90fd7a2e0422971cd2a7558d80b243fc18dd97e
[blhc/blhc.git] / 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     return 0 if $line =~ /^\s*CXX\s*:\s*g\+\+\s*$/;
570     # "Compiling" non binary files.
571     return 0 if $line =~ /^\s*Compiling \S+\.(?:py|pyx|el)['"]?\s*(?:\.\.\.|because it changed\.)?$/;
572     return 0 if $line =~ /^\s*[Cc]ompiling catalog \S+\.po\b/;
573     # cargo build
574     return 0 if $cargo and $line =~ m{^\s*Compiling\s+\S+\s+v\S+(?:\s+\(/(?:<<PKGBUILDDIR>>|builds/\S+)\))?$};
575     # "Compiling" with no file name.
576     if ($line =~ /^\s*[Cc]ompiling\s+(.+?)(?:\.\.\.)?$/) {
577         # $file_extension_regex may need spaces around the filename.
578         return 0 if not " $1 " =~ /$file_extension_regex/o;
579     }
580
581     my $file = $1;
582
583     # On the first pass we only check if this line is verbose or not.
584     return 1 if not defined $input_ref;
585
586     # Second pass, we have access to the next lines.
587     ${$skip_ref} = 0;
588
589     # CMake and other build systems print the non-verbose messages also when
590     # building verbose. If a compiler and the file name occurs in the next
591     # lines, treat it as verbose build.
592     if (defined $file) {
593         # Get filename, we can't use the complete path as only parts of it are
594         # used in the real compiler command.
595         $file =~ m{/([^/\s]+)$};
596         $file = $1;
597
598         for (my $i = 1; $i <= $line_count; $i++) {
599             my $next_line = $input_ref->[$line_offset + $i];
600             last unless defined $next_line;
601
602             if (index($next_line, $file) != -1 and $next_line =~ /$cc_regex/o) {
603                 # Not a non-verbose line, but we still have to skip the
604                 # current line as it doesn't contain any compiler commands.
605                 ${$skip_ref} = 1;
606                 return 0;
607             }
608         }
609     }
610
611     return 1;
612 }
613
614 # Remove @flags from $flag_refs_ref, uses $flag_renames_ref as reference.
615 sub remove_flags {
616     my ($flag_refs_ref, $flag_renames_ref, @flags) = @_;
617
618     my %removes = map { $_ => 1 } @flags;
619     foreach my $flags (@{$flag_refs_ref}) {
620         @{$flags} = grep {
621             # Flag found as string.
622             not exists $removes{$_}
623             # Flag found as string representation of regexp.
624                 and (not defined $flag_renames_ref->{$_}
625                         or not exists $removes{$flag_renames_ref->{$_}})
626         } @{$flags};
627     }
628
629     return;
630 }
631
632 # Modifies $flag_renames_ref hash.
633 sub compile_flag_regexp {
634     my ($flag_renames_ref, @flags) = @_;
635
636     my @result = ();
637     foreach my $flag (@flags) {
638         # Compile flag regexp for faster execution.
639         my $regex = qr/\s(['"]?)$flag\1(?:\s|\\)/;
640
641         # Store flag name in replacement string for correct flags in messages
642         # with qr//ed flag regexps.
643         $flag_renames_ref->{$regex}
644             = (exists $flag_renames_ref->{$flag})
645                 ? $flag_renames_ref->{$flag}
646                 : $flag;
647
648         push @result, $regex;
649     }
650     return @result;
651 }
652
653 # Does any extension in @extensions exist in %{$extensions_ref}?
654 sub extension_found {
655     my ($extensions_ref, @extensions) = @_;
656
657     foreach my $extension (@extensions) {
658         if (exists $extensions_ref->{$extension}) {
659             return 1;
660         }
661     }
662     return 0;
663 }
664
665
666 # MAIN
667
668 # Parse command line arguments.
669 my $option_help             = 0;
670 my $option_version          = 0;
671 my $option_pie              = 0;
672 my $option_bindnow          = 0;
673 my @option_ignore_arch      = ();
674 my @option_ignore_flag      = ();
675 my @option_ignore_arch_flag = ();
676 my @option_ignore_line      = ();
677 my @option_ignore_arch_line = ();
678 my $option_all              = 0;
679 my $option_arch             = undef;
680 my $option_buildd           = 0;
681 my $option_debian           = 0;
682    $option_color            = 0;
683 my $option_line_numbers     = 0;
684 if (not Getopt::Long::GetOptions(
685             'help|h|?'           => \$option_help,
686             'version'            => \$option_version,
687             # Hardening options.
688             'pie'                => \$option_pie,
689             'bindnow'            => \$option_bindnow,
690             'all'                => \$option_all,
691             # Ignore.
692             'ignore-arch=s'      => \@option_ignore_arch,
693             'ignore-flag=s'      => \@option_ignore_flag,
694             'ignore-arch-flag=s' => \@option_ignore_arch_flag,
695             'ignore-line=s'      => \@option_ignore_line,
696             'ignore-arch-line=s' => \@option_ignore_arch_line,
697             # Misc.
698             'color'              => \$option_color,
699             'arch=s'             => \$option_arch,
700             'buildd'             => \$option_buildd,
701             'debian'             => \$option_debian,
702             'line-numbers'       => \$option_line_numbers,
703         )) {
704     require Pod::Usage;
705     Pod::Usage::pod2usage(2);
706 }
707 if ($option_help) {
708     require Pod::Usage;
709     Pod::Usage::pod2usage(1);
710 }
711 if ($option_version) {
712     print <<"EOF";
713 blhc $VERSION  Copyright (C) 2012-2024  Simon Ruderich
714
715 This program is free software: you can redistribute it and/or modify
716 it under the terms of the GNU General Public License as published by
717 the Free Software Foundation, either version 3 of the License, or
718 (at your option) any later version.
719
720 This program is distributed in the hope that it will be useful,
721 but WITHOUT ANY WARRANTY; without even the implied warranty of
722 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
723 GNU General Public License for more details.
724
725 You should have received a copy of the GNU General Public License
726 along with this program.  If not, see <http://www.gnu.org/licenses/>.
727 EOF
728     exit 0;
729 }
730
731 # Arguments missing.
732 if (scalar @ARGV == 0) {
733     require Pod::Usage;
734     Pod::Usage::pod2usage(2);
735 }
736
737 # Don't load Term::ANSIColor in buildd mode because Term::ANSIColor is not
738 # installed on Debian's buildds.
739 if (not $option_buildd) {
740     require Term::ANSIColor;
741 }
742
743 if ($option_all) {
744     $option_pie     = 1;
745     $option_bindnow = 1;
746 }
747
748 # Precompiled ignores for faster lookup.
749 my %option_ignore_arch_flag = ();
750 my %option_ignore_arch_line = ();
751
752 # Strip flags which should be ignored.
753 if (scalar @option_ignore_flag > 0) {
754     remove_flags(\@flag_refs, \%flag_renames, @option_ignore_flag);
755 }
756 # Same for arch specific ignore flags, but only prepare here.
757 if (scalar @option_ignore_arch_flag > 0) {
758     foreach my $ignore (@option_ignore_arch_flag) {
759         my ($ignore_arch, $ignore_flag) = split /:/, $ignore, 2;
760
761         if (not $ignore_arch or not $ignore_flag) {
762             printf STDERR 'Value "%s" invalid for option ignore-arch-flag '
763                         . '("arch:flag" expected)' . "\n", $ignore;
764             require Pod::Usage;
765             Pod::Usage::pod2usage(2);
766         }
767
768         push @{$option_ignore_arch_flag{$ignore_arch}}, $ignore_flag;
769     }
770 }
771
772 # Precompile all flag regexps. any_flags_used(), all_flags_used() get a lot
773 # faster with this.
774 foreach my $flags (@flag_refs_all) {
775     @{$flags} = compile_flag_regexp(\%flag_renames, @{$flags});
776 }
777
778 # Precompile ignore line regexps, also anchor at beginning and end of line.
779 # Additional entries are also extracted from the build log, see below.
780 foreach my $ignore (@option_ignore_line) {
781     $ignore = qr/^$ignore$/;
782 }
783 # Same for arch specific ignore lines.
784 if (scalar @option_ignore_arch_line > 0) {
785     foreach my $ignore (@option_ignore_arch_line) {
786         my ($ignore_arch, $ignore_line) = split /:/, $ignore, 2;
787
788         if (not $ignore_arch or not $ignore_line) {
789             printf STDERR 'Value "%s" invalid for option ignore-arch-line '
790                         . '("arch:line" expected)' . "\n", $ignore;
791             require Pod::Usage;
792             Pod::Usage::pod2usage(2);
793         }
794
795         push @{$option_ignore_arch_line{$ignore_arch}}, qr/^$ignore_line$/;
796     }
797 }
798
799 # Final exit code.
800 my $exit = 0;
801
802 FILE:
803 foreach my $file (@ARGV) {
804     print "checking '$file'...\n" if scalar @ARGV > 1;
805
806     -f $file or die "No such file: $file";
807
808     open my $fh, '<', $file or die $!;
809
810     # Architecture of this file.
811     my $arch = $option_arch;
812
813     # Hardening options. Not all architectures support all hardening options.
814     my $harden_format  = 1;
815     my $harden_fortify = 1;
816     my $harden_stack   = 1;
817     my $harden_stack_strong = 1;
818     my $harden_stack_clash = 1;
819     my $harden_branch  = 1;
820     my $harden_relro   = 1;
821     my $harden_bindnow = $option_bindnow; # defaults to 0
822     my $harden_pie     = $option_pie;     # defaults to 0
823
824     # Number of parallel jobs to prevent false positives when detecting
825     # non-verbose builds. As not all jobs declare the number of parallel jobs
826     # use a large enough default.
827     my $parallel = 10;
828
829     # Don't check for PIE flags if automatically applied by the compiler. Only
830     # used in buildd and Debian mode.
831     my $disable_harden_pie = 0;
832     if ($option_debian) {
833         $disable_harden_pie = 1;
834     }
835
836     # Ignore additional false positives if cargo/rust is used
837     my $cargo = 0;
838
839     my $number = 0;
840     while (my $line = <$fh>) {
841         $number++;
842
843         # Detect architecture automatically unless overridden. For buildd logs
844         # only, doesn't use the dpkg-buildpackage header. Necessary to ignore
845         # build logs which aren't built (wrong architecture, build error,
846         # etc.).
847         if (not $arch) {
848             if (index($line, 'Build Architecture: ') == 0) {
849                 $arch = substr $line, 20, -1; # -1 to ignore '\n' at the end
850             # For old logs (sbuild << 0.63.0-1).
851             } elsif (index($line, 'Architecture: ') == 0) {
852                 $arch = substr $line, 14, -1; # -1 to ignore '\n' at the end
853             }
854         }
855
856         # dpkg-buildflags only provides hardening flags since 1.16.1, don't
857         # check for hardening flags in buildd mode if an older dpkg-dev is
858         # used. Default flags (-g -O2) are still checked.
859         #
860         # Packages which were built before 1.16.1 but used their own hardening
861         # flags are not checked.
862         #
863         # Strong stack protector is used since dpkg 1.17.11.
864         #
865         # Recent GCC versions automatically use PIE (only on supported
866         # architectures) and dpkg respects this properly since 1.18.15 and
867         # doesn't pass PIE flags manually.
868         if ($option_buildd
869                 and index($line, 'Toolchain package versions: ') == 0) {
870             require Dpkg::Version;
871
872             my $disable = 1;
873             my $disable_strong = 1;
874             my $disable_clash = 1;
875             my $disable_branch = 1;
876
877             if ($line =~ /\bdpkg-dev_(\S+)/) {
878                 if (Dpkg::Version::version_compare($1, '1.16.1') >= 0) {
879                     $disable = 0;
880                 }
881                 if (Dpkg::Version::version_compare($1, '1.17.11') >= 0) {
882                     $disable_strong = 0;
883                 }
884                 if (Dpkg::Version::version_compare($1, '1.18.15') >= 0) {
885                     $disable_harden_pie = 1;
886                 }
887                 if (Dpkg::Version::version_compare($1, '1.22.0') >= 0) {
888                     $disable_clash = 0;
889                     $disable_branch = 0;
890                 }
891             }
892
893             if ($disable) {
894                 $harden_format  = 0;
895                 $harden_fortify = 0;
896                 $harden_stack   = 0;
897                 $harden_relro   = 0;
898                 $harden_bindnow = 0;
899                 $harden_pie     = 0;
900             }
901             if ($disable_strong) {
902                 $harden_stack_strong = 0;
903             }
904             if ($disable_clash) {
905                 $harden_stack_clash = 0;
906             }
907             if ($disable_branch) {
908                 $harden_branch = 0;
909             }
910         }
911
912         # The following two versions of CMake in Debian obeyed CPPFLAGS, but
913         # this was later dropped because upstream rejected the patch. Thus
914         # build logs with these versions will have fortify hardening flags
915         # enabled, even though they may be not correctly set and are missing
916         # when build with later CMake versions. Thanks to Aron Xu for letting
917         # me know.
918         if (index($line, 'Package versions: ') == 0
919                 and $line =~ /\bcmake_(\S+)/
920                 and ($1 eq '2.8.7-1' or $1 eq '2.8.7-2')) {
921             if (not $option_buildd) {
922                 error_invalid_cmake($1);
923                 $exit |= $exit_code{invalid_cmake};
924             } else {
925                 print "$buildd_tag{invalid_cmake}|$1|\n";
926             }
927         }
928
929         # Debian's build daemons use "Filtered Build-Depends:" (or just
930         # "Build-Depends:" in older versions) for the build dependencies, but
931         # pbuilder uses "Depends:"; support both.
932         if (index($line, 'Filtered Build-Depends: ') == 0
933                 or index($line, 'Build-Depends: ') == 0
934                 or index($line, 'Depends: ') == 0) {
935             # If hardening wrapper is used (wraps calls to gcc and adds
936             # hardening flags automatically) we can't perform any checks,
937             # abort.
938             if ($line =~ /\bhardening-wrapper\b/) {
939                 if (not $option_buildd) {
940                     error_hardening_wrapper();
941                     $exit |= $exit_code{hardening_wrapper};
942                 } else {
943                     print "$buildd_tag{hardening_wrapper}||\n";
944                 }
945                 next FILE;
946             }
947
948             if ($line =~ /\bcargo\b/) {
949                 $cargo = 1;
950             }
951         }
952
953         # This flags is not always available, but if it is use it.
954         if ($line =~ /^DEB_BUILD_OPTIONS=.*\bparallel=(\d+)/) {
955             $parallel = $1 * 2;
956         }
957
958         # We skip over unimportant lines at the beginning of the log to
959         # prevent false positives.
960         last if index($line, 'dpkg-buildpackage: ') == 0;
961     }
962
963     # Input lines, contain only the lines with compiler commands.
964     my @input = ();
965     # Non-verbose lines in the input. Used to reduce calls to
966     # is_non_verbose_build() (which is quite slow) in the second loop when
967     # it's already clear if a line is non-verbose or not.
968     my @input_nonverbose = ();
969     # Input line number.
970     my @input_number = ();
971
972     my $continuation = 0;
973     my $complete_line = undef;
974     my $non_verbose;
975     while (my $line = <$fh>) {
976         $number++;
977
978         # And stop at the end of the build log. Package details (reported by
979         # the buildd logs) are not important for us. This also prevents false
980         # positives.
981         last if index($line, 'Build finished at ') == 0
982                 and $line =~ /^Build finished at \d{8}-\d{4}$/;
983
984         if (not $continuation) {
985             $non_verbose = 0;
986         }
987
988         # Detect architecture automatically unless overridden.
989         if (not $arch
990                 and index($line, 'dpkg-buildpackage: info: host architecture ') == 0) {
991             $arch = substr $line, 43, -1; # -1 to ignore '\n' at the end
992         # Older versions of dpkg-buildpackage
993         } elsif (not $arch
994                 and index($line, 'dpkg-buildpackage: host architecture ') == 0) {
995             $arch = substr $line, 37, -1; # -1 to ignore '\n' at the end
996
997             # Old buildd logs use e.g. "host architecture is alpha", remove
998             # the "is", otherwise debarch_to_debtriplet() will not detect the
999             # architecture.
1000             if (index($arch, 'is ') == 0) {
1001                 $arch = substr $arch, 3;
1002             }
1003         }
1004
1005         # Permit dynamic excludes from within the build log to ignore false
1006         # positives. Cannot use a separate config file as we often only have
1007         # the build log itself.
1008         if (index($line, 'blhc: ignore-line-regexp: ') == 0) {
1009             my $ignore = substr $line, 26, -1; # -1 to ignore '\n' at the end
1010             push @option_ignore_line, qr/^$ignore$/;
1011             next;
1012         }
1013
1014         next if $line =~ /^\s*#/;
1015         # Ignore compiler warnings for now.
1016         next if $line =~ /$warning_regex/o;
1017
1018         if (not $option_buildd and index($line, "\033") != -1) { # \033 = esc
1019             # Remove all ANSI color sequences which are sometimes used in
1020             # non-verbose builds.
1021             $line = Term::ANSIColor::colorstrip($line);
1022             # Also strip '\0xf' (delete previous character), used by Elinks'
1023             # build system.
1024             $line =~ s/\x0f//g;
1025             # And "ESC(B" which seems to be used on armhf and hurd (not sure
1026             # what it does).
1027             $line =~ s/\033\(B//g;
1028         }
1029
1030         # Check if this line indicates a non verbose build.
1031         my $skip = 0;
1032         $non_verbose |= is_non_verbose_build($line, $cargo, \$skip);
1033         next if $skip;
1034
1035         # Treat each command as a single line so we don't ignore valid
1036         # commands when handling false positives. split_line() is slow, only
1037         # use it when necessary.
1038         my @line = ($line !~ /(?:;|&&|\|\|)/)
1039                  ? ($line)
1040                  : split_line($line);
1041         foreach my $line (@line) {
1042             if ($continuation) {
1043                 $continuation = 0;
1044
1045                 # Join lines, but leave the "\" in place so it's clear where
1046                 # the original line break was.
1047                 chomp $complete_line;
1048                 $complete_line .= ' ' . $line;
1049             }
1050             # Line continuation, line ends with "\".
1051             if ($line =~ /\\$/) {
1052                 $continuation = 1;
1053                 # Start line continuation.
1054                 if (not defined $complete_line) {
1055                     $complete_line = $line;
1056                 }
1057                 next;
1058             }
1059
1060             # Use the complete line if a line continuation occurred.
1061             if (defined $complete_line) {
1062                 $line = $complete_line;
1063                 $complete_line = undef;
1064             }
1065
1066             my $noenv = $line;
1067             # Strip (basic) environment variables for compiler detection. This
1068             # prevents false positives when environment variables contain
1069             # compiler binaries. Nested quotes, command substitution, etc. is
1070             # not supported.
1071             $noenv =~ s/^
1072                 \s*
1073                 (?:
1074                     [a-zA-Z_]+          # environment variable name
1075                     =
1076                     (?:
1077                         [^\s"'\$`\\]+   # non-quoted string
1078                         |
1079                         '[^"'\$`\\]*'   # single-quoted string
1080                         |
1081                         "[^"'\$`\\]*"   # double-quoted string
1082                     )
1083                     \s+
1084                 )*
1085             //x;
1086             # Ignore lines with no compiler commands.
1087             next if not $non_verbose
1088                     and not $noenv =~ /$cc_regex_normal/o;
1089             # Ignore lines with no filenames with extensions. May miss some
1090             # non-verbose builds (e.g. "gcc -o test" [sic!]), but shouldn't be
1091             # a problem as the log will most likely contain other non-verbose
1092             # commands which are detected.
1093             next if not $non_verbose
1094                     and not $line =~ /$file_extension_regex/o;
1095
1096             # Ignore false positives.
1097             #
1098             # `./configure` output.
1099             next if not $non_verbose
1100                     and $line =~ /^(?:checking|[Cc]onfigure:) /;
1101             next if $line =~ /^\s*(?:Host\s+)?(?:C(?:\+\+)?\s+)?
1102                                 [Cc]ompiler[\s.]*:?\s+
1103                                 /x;
1104             next if $line =~ m{^\s*(?:-\s)?(?:HOST_)?(?:CC|CXX)
1105                                 \s*=\s*$cc_regex_full
1106                                 # optional compiler options, don't allow
1107                                 # "everything" here to prevent false negatives
1108                                 \s*(?:\s-\S+)*\s*$}xo;
1109             # `echo` is never a compiler command
1110             next if $line =~ /^\s*echo\s/;
1111             # Ignore calls to `make` because they can contain environment
1112             # variables which look like compiler commands, e.g. CC=).
1113             next if $line =~ /^\s*make\s/;
1114             # `moc-qt4`/`moc-qt5` contain '-I.../linux-g++' in their command
1115             # line (or similar for other architectures) which gets recognized
1116             # as a compiler line, but `moc-qt*` is only a preprocessor for Qt
1117             # C++ files. No hardening flags are relevant during this step,
1118             # thus ignore `moc-qt*` lines. The resulting files will be
1119             # compiled in a separate step (and therefore checked).
1120             next if $line =~ m{^\S+(?:/bin/moc(?:-qt[45])?|/lib/qt6/libexec/moc)
1121                                \s.+\s
1122                                -I\S+/mkspecs/[a-z]+-g\++(?:-64)?
1123                                \s}x;
1124             # nvcc is not a regular C compiler
1125             next if $line =~ m{^\S+/bin/nvcc\s};
1126             # Ignore false positives when the line contains only CC=gcc but no
1127             # other gcc command.
1128             if ($line =~ /(.*)CC=$cc_regex_full(.*)/o) {
1129                 my $before = $1;
1130                 my $after  = $2;
1131                 next if     not $before =~ /$cc_regex_normal/o
1132                         and not $after  =~ /$cc_regex_normal/o;
1133             }
1134             # Ignore false positives caused by gcc -v. It outputs a line
1135             # looking like a normal compiler line but which is sometimes
1136             # missing hardening flags, although the normal compiler line
1137             # contains them.
1138             next if $line =~ m{^\s+/usr/lib/gcc/$cc_regex_full_prefix/
1139                                    [0-9.]+/cc1(?:plus)?}xo;
1140             # Ignore false positive with `rm` which may remove files which
1141             # look like a compiler executable thus causing the line to be
1142             # treated as a normal compiler line.
1143             next if $line =~ m{^\s*rm\s+};
1144             next if $line =~ m{^\s*dwz\s+};
1145             # Some build systems emit "gcc > file".
1146             next if $line =~ m{$cc_regex_normal\s*>\s*\S+}o;
1147             # Hex output may contain "cc".
1148             next if $line =~ m#(?:\b[0-9a-fA-F]{2,}\b\s*){5}#;
1149             # Meson build output
1150             next if $line =~ /^C\+\+ linker for the host machine: /;
1151             # Embedded `gcc -print-*` commands
1152             next if $line =~ /`$cc_regex_normal\s*[^`]*-print-\S+`/;
1153             # cmake checking for compiler flags without setting CPPFLAGS
1154             next if $line =~ m{^\s*/usr/(bin|lib)/(ccache/)?c\+\+ (?:-std=\S+ )?-dM -E -c /usr/share/cmake-\S+/Modules/CMakeCXXCompilerABI\.cpp};
1155             # Some rustc lines look like linker commands
1156             next if $cargo and $line =~ /$rustc_regex/o;
1157
1158             # Check if additional hardening options were used. Used to ensure
1159             # they are used for the complete build.
1160             $harden_pie     = 1 if any_flags_used($line, @def_cflags_pie,
1161                                                          @def_ldflags_pie);
1162             $harden_bindnow = 1 if any_flags_used($line, @def_ldflags_bindnow);
1163
1164             push @input, $line;
1165             push @input_nonverbose, $non_verbose;
1166             push @input_number, $number if $option_line_numbers;
1167         }
1168     }
1169
1170     close $fh or die $!;
1171
1172     # Ignore arch if requested.
1173     if (scalar @option_ignore_arch > 0 and $arch) {
1174         foreach my $ignore (@option_ignore_arch) {
1175             if ($arch eq $ignore) {
1176                 print "ignoring architecture '$arch'\n";
1177                 next FILE;
1178             }
1179         }
1180     }
1181
1182     if (scalar @input == 0) {
1183         if (not $cargo) {
1184             if (not $option_buildd) {
1185                 print "No compiler commands!\n";
1186                 $exit |= $exit_code{no_compiler_commands};
1187             } else {
1188                 print "$buildd_tag{no_compiler_commands}||\n";
1189             }
1190         }
1191         next FILE;
1192     }
1193
1194     if ($option_buildd) {
1195         $statistics{commands} += scalar @input;
1196     }
1197
1198     # Option or auto detected.
1199     my @harden_branch_flags;
1200     if ($arch) {
1201         # The following was partially copied from dpkg-dev 1.22.0
1202         # (/usr/share/perl5/Dpkg/Vendor/Debian.pm, set_build_features and
1203         # _add_build_flags()), copyright Raphaël Hertzog <hertzog@debian.org>,
1204         # Guillem Jover <guillem@debian.org>, Kees Cook <kees@debian.org>,
1205         # Canonical, Ltd. licensed under GPL version 2 or later. Keep it in
1206         # sync.
1207
1208         require Dpkg::Arch;
1209         my ($os, $cpu);
1210         # Recent dpkg versions use a quadruplet for arch. Support both.
1211         eval {
1212             (undef, undef, $os, $cpu) = Dpkg::Arch::debarch_to_debtuple($arch);
1213         };
1214         if ($@) {
1215             (undef, $os, $cpu) = Dpkg::Arch::debarch_to_debtriplet($arch);
1216         }
1217
1218         my %builtin_pie_arch = map { $_ => 1 } qw(
1219             amd64
1220             arm64
1221             armel
1222             armhf
1223             hurd-amd64
1224             hurd-i386
1225             i386
1226             kfreebsd-amd64
1227             kfreebsd-i386
1228             loong64
1229             mips
1230             mips64
1231             mips64el
1232             mips64r6
1233             mips64r6el
1234             mipsel
1235             mipsn32
1236             mipsn32el
1237             mipsn32r6
1238             mipsn32r6el
1239             mipsr6
1240             mipsr6el
1241             powerpc
1242             ppc64
1243             ppc64el
1244             riscv64
1245             s390x
1246             sparc
1247             sparc64
1248         );
1249
1250         # Disable unsupported hardening options.
1251         if ($disable_harden_pie and exists $builtin_pie_arch{$arch}) {
1252             $harden_pie = 0;
1253         }
1254         if ($os !~ /^(?:linux|kfreebsd|hurd)$/
1255                 or $cpu =~ /^(?:alpha|hppa|ia64)$/) {
1256             $harden_pie = 0;
1257         }
1258         if ($cpu =~ /^(?:ia64|alpha|hppa|nios2)$/ or $arch eq 'arm') {
1259             $harden_stack = 0;
1260             $harden_stack_strong = 0;
1261         }
1262         if ($arch !~ /^(?:amd64|arm64|armhf|armel)$/) {
1263             $harden_stack_clash = 0;
1264         }
1265         if ($cpu =~ /^(?:ia64|hppa)$/) {
1266             $harden_relro   = 0;
1267             $harden_bindnow = 0;
1268         }
1269         if ($cpu eq 'amd64') {
1270             @harden_branch_flags = @def_cflags_branch_amd64;
1271         } elsif ($cpu eq 'arm64') {
1272             @harden_branch_flags = @def_cflags_branch_arm64;
1273         }
1274     }
1275
1276     # Default values.
1277     my @cflags   = @def_cflags;
1278     my @cxxflags = @def_cxxflags;
1279     my @cppflags = @def_cppflags;
1280     my @ldflags  = @def_ldflags;
1281     # Check the specified hardening options, same order as dpkg-buildflags.
1282     if ($harden_pie) {
1283         @cflags   = (@cflags,   @def_cflags_pie);
1284         @cxxflags = (@cxxflags, @def_cflags_pie);
1285         @ldflags  = (@ldflags,  @def_ldflags_pie);
1286     }
1287     if ($harden_stack_strong) {
1288         @cflags   = (@cflags,   @def_cflags_stack_strong);
1289         @cxxflags = (@cxxflags, @def_cflags_stack_strong);
1290     } elsif ($harden_stack) {
1291         @cflags   = (@cflags,   @def_cflags_stack);
1292         @cxxflags = (@cxxflags, @def_cflags_stack);
1293     }
1294     if ($harden_stack_clash) {
1295         @cflags   = (@cflags,   @def_cflags_stack_clash);
1296         @cxxflags = (@cxxflags, @def_cflags_stack_clash);
1297     }
1298     if ($harden_fortify) {
1299         @cflags   = (@cflags,   @def_cflags_fortify);
1300         @cxxflags = (@cxxflags, @def_cflags_fortify);
1301         @cppflags = (@cppflags, @def_cppflags_fortify);
1302     }
1303     if ($harden_format) {
1304         @cflags   = (@cflags,   @def_cflags_format);
1305         @cxxflags = (@cxxflags, @def_cflags_format);
1306     }
1307     if ($harden_branch and @harden_branch_flags) {
1308         @cflags   = (@cflags,   @harden_branch_flags);
1309         @cxxflags = (@cxxflags, @harden_branch_flags);
1310     }
1311     if ($harden_relro) {
1312         @ldflags = (@ldflags, @def_ldflags_relro);
1313     }
1314     if ($harden_bindnow) {
1315         @ldflags = (@ldflags, @def_ldflags_bindnow);
1316     }
1317
1318     # Ada doesn't support format hardening flags, see #680117 for more
1319     # information. Same for fortran.
1320     my @cflags_backup;
1321     my @cflags_noformat = grep {
1322         my $ok = 1;
1323         foreach my $flag (@def_cflags_format) {
1324             $ok = 0 if $_ eq $flag;
1325         }
1326         $ok;
1327     } @cflags;
1328
1329     # Hack to fix cppflags_fortify_broken() if --ignore-flag
1330     # -D_FORTIFY_SOURCE=2 is used to ignore missing fortification. Only works
1331     # as long as @def_cppflags_fortify contains only one variable.
1332     if (scalar @def_cppflags_fortify == 0) {
1333         $harden_fortify = 0;
1334     }
1335
1336     # Ignore flags for this arch if requested.
1337     if ($arch and exists $option_ignore_arch_flag{$arch}) {
1338         my @local_flag_refs = (\@cflags, \@cxxflags, \@cppflags, \@ldflags);
1339
1340         remove_flags(\@local_flag_refs,
1341                      \%flag_renames,
1342                      @{$option_ignore_arch_flag{$arch}});
1343     }
1344
1345     my @ignore_line = @option_ignore_line;
1346     # Ignore lines for this arch if requested.
1347     if ($arch and exists $option_ignore_arch_line{$arch}) {
1348         @ignore_line = (@ignore_line, @{$option_ignore_arch_line{$arch}});
1349     }
1350
1351 LINE:
1352     for (my $i = 0; $i < scalar @input; $i++) {
1353         my $line = $input[$i];
1354
1355         # Ignore line if requested.
1356         foreach my $ignore (@ignore_line) {
1357             next LINE if $line =~ /$ignore/;
1358         }
1359
1360         my $skip = 0;
1361         if ($input_nonverbose[$i]
1362                 and is_non_verbose_build($line, $cargo, \$skip,
1363                                          \@input, $i, $parallel)) {
1364             if (not $option_buildd) {
1365                 error_non_verbose_build($line, $input_number[$i]);
1366                 $exit |= $exit_code{non_verbose_build};
1367             } else {
1368                 $statistics{commands_nonverbose}++;
1369             }
1370             next;
1371         }
1372         # Even if it's a verbose build, we might have to skip this line (see
1373         # is_non_verbose_build()).
1374         next if $skip;
1375
1376         my $orig_line = $line;
1377
1378         # Remove everything until and including the compiler command. Makes
1379         # checks easier and faster.
1380         $line =~ s/^.*?$cc_regex//o;
1381         # "([...] test.c)" is not detected as 'test.c' - fix this by removing
1382         # the brace and similar characters at the line end.
1383         $line =~ s/['")]+$//;
1384
1385         # Skip unnecessary tests when only preprocessing.
1386         my $flag_preprocess = 0;
1387
1388         my $dependency = 0;
1389         my $preprocess = 0;
1390         my $compile    = 0;
1391         my $link       = 0;
1392
1393         # Preprocess, compile, assemble.
1394         if ($line =~ /\s(-E|-S|-c)\b/) {
1395             $preprocess      = 1;
1396             $flag_preprocess = 1 if $1 eq '-E';
1397             $compile         = 1 if $1 eq '-S' or $1 eq '-c';
1398         # Dependency generation for Makefiles. The other flags (-MF -MG -MP
1399         # -MT -MQ) are always used with -M/-MM.
1400         } elsif ($line =~ /\s(?:-M|-MM)\b/) {
1401             $dependency = 1;
1402         # Otherwise assume we are linking.
1403         } else {
1404             $link = 1;
1405         }
1406
1407         # -MD/-MMD also cause dependency generation, but they don't imply -E!
1408         if ($line =~ /\s(?:-MD|-MMD)\b/) {
1409             $dependency      = 0;
1410             $flag_preprocess = 0;
1411         }
1412
1413         # Dependency generation for Makefiles, no preprocessing or other flags
1414         # needed.
1415         next if $dependency;
1416
1417         # Get all file extensions on this line.
1418         my @extensions = $line =~ /$file_extension_regex/go;
1419         # Ignore all unknown extensions to speedup the search below.
1420         @extensions = grep { exists $extension{$_} } @extensions;
1421
1422         # These file types don't require preprocessing.
1423         if (extension_found(\%extensions_no_preprocess, @extensions)) {
1424             $preprocess = 0;
1425         }
1426         # These file types require preprocessing.
1427         if (extension_found(\%extensions_preprocess, @extensions)) {
1428             # Prevent false positives with "libtool: link: g++ -include test.h
1429             # .." compiler lines.
1430             if ($orig_line !~ /$libtool_link_regex/o) {
1431                 $preprocess = 1;
1432             }
1433         }
1434
1435         if (not $flag_preprocess) {
1436             # If there are source files then it's compiling/linking in one
1437             # step and we must check both. We only check for source files
1438             # here, because header files cause too many false positives.
1439             if (extension_found(\%extensions_compile_link, @extensions)) {
1440                 # Assembly files don't need CFLAGS.
1441                 if (not extension_found(\%extensions_compile, @extensions)
1442                         and extension_found(\%extensions_no_compile, @extensions)) {
1443                     $compile = 0;
1444                 # But the rest does.
1445                 } else {
1446                     $compile = 1;
1447                 }
1448             # No compilable extensions found, either linking or compiling
1449             # header flags.
1450             #
1451             # If there are also no object files we are just compiling headers
1452             # (.h -> .h.gch). Don't check for linker flags in this case. Due
1453             # to our liberal checks for compiler lines, this also reduces the
1454             # number of false positives considerably.
1455             } elsif ($link
1456                     and not extension_found(\%extensions_object, @extensions)) {
1457                 $link = 0;
1458             }
1459         }
1460
1461         my $compile_cpp = 0;
1462         my $restore_cflags = 0;
1463         # Assume CXXFLAGS are required when a C++ file is specified in the
1464         # compiler line.
1465         if ($compile
1466                 and extension_found(\%extensions_compile_cpp, @extensions)) {
1467             $compile     = 0;
1468             $compile_cpp = 1;
1469         # Ada needs special CFLAGS
1470         } elsif (extension_found(\%extensions_ada, @extensions)) {
1471             $restore_cflags = 1;
1472             $preprocess = 0; # Ada uses no CPPFLAGS
1473             @cflags_backup = @cflags;
1474             @cflags        = @cflags_noformat;
1475         # Same for fortran
1476         } elsif (extension_found(\%extensions_fortran, @extensions)) {
1477             $restore_cflags = 1;
1478             @cflags_backup = @cflags;
1479             @cflags        = @cflags_noformat;
1480         }
1481
1482         if ($option_buildd) {
1483             $statistics{preprocess}++  if $preprocess;
1484             $statistics{compile}++     if $compile;
1485             $statistics{compile_cpp}++ if $compile_cpp;
1486             $statistics{link}++        if $link;
1487         }
1488
1489         # Check if there are flags indicating a debug build. If that's true,
1490         # skip the check for -O2. This prevents fortification, but that's fine
1491         # for a debug build.
1492         if (any_flags_used($line, @def_cflags_debug)) {
1493             remove_flags([\@cflags], \%flag_renames, $def_cflags[1]);
1494             remove_flags([\@cppflags], \%flag_renames, $def_cppflags_fortify[0]);
1495         }
1496
1497         # Check hardening flags.
1498         my @missing;
1499         if ($compile and (not all_flags_used($line, \@missing, @cflags)
1500                     or (($harden_stack or $harden_stack_strong)
1501                         and cflags_stack_broken($line, \@missing,
1502                                                 $harden_stack_strong)))
1503                 # Libraries linked with -fPIC don't have to (and can't) be
1504                 # linked with -fPIE as well. It's no error if only PIE flags
1505                 # are missing.
1506                 and not pic_pie_conflict($line, $harden_pie, \@missing, @def_cflags_pie)
1507                 # Assume dpkg-buildflags returns the correct flags.
1508                 and index($line, '`dpkg-buildflags --get CFLAGS`') == -1) {
1509             if (not $option_buildd) {
1510                 error_flags('CFLAGS missing', \@missing, \%flag_renames,
1511                             $input[$i], $input_number[$i]);
1512                 $exit |= $exit_code{flags_missing};
1513             } else {
1514                 $statistics{compile_missing}++;
1515             }
1516         } elsif ($compile_cpp and not all_flags_used($line, \@missing, @cflags)
1517                 # Libraries linked with -fPIC don't have to (and can't) be
1518                 # linked with -fPIE as well. It's no error if only PIE flags
1519                 # are missing.
1520                 and not pic_pie_conflict($line, $harden_pie, \@missing, @def_cflags_pie)
1521                 # Assume dpkg-buildflags returns the correct flags.
1522                 and index($line, '`dpkg-buildflags --get CXXFLAGS`') == -1) {
1523             if (not $option_buildd) {
1524                 error_flags('CXXFLAGS missing', \@missing, \%flag_renames,
1525                             $input[$i], $input_number[$i]);
1526                 $exit |= $exit_code{flags_missing};
1527             } else {
1528                 $statistics{compile_cpp_missing}++;
1529             }
1530         }
1531         if ($preprocess
1532                 and (not all_flags_used($line, \@missing, @cppflags)
1533                     # The fortify flag might be overwritten, detect that.
1534                      or ($harden_fortify
1535                          and cppflags_fortify_broken($line, \@missing)))
1536                 # Assume dpkg-buildflags returns the correct flags.
1537                 and index($line, '`dpkg-buildflags --get CPPFLAGS`') == -1) {
1538             if (not $option_buildd) {
1539                 error_flags('CPPFLAGS missing', \@missing, \%flag_renames,
1540                             $input[$i], $input_number[$i]);
1541                 $exit |= $exit_code{flags_missing};
1542             } else {
1543                 $statistics{preprocess_missing}++;
1544             }
1545         }
1546         if ($link and not all_flags_used($line, \@missing, @ldflags)
1547                 # Same here, -fPIC conflicts with -fPIE.
1548                 and not pic_pie_conflict($line, $harden_pie, \@missing, @def_ldflags_pie)
1549                 # Assume dpkg-buildflags returns the correct flags.
1550                 and index($line, '`dpkg-buildflags --get LDFLAGS`') == -1) {
1551             if (not $option_buildd) {
1552                 error_flags('LDFLAGS missing', \@missing, \%flag_renames,
1553                             $input[$i], $input_number[$i]);
1554                 $exit |= $exit_code{flags_missing};
1555             } else {
1556                 $statistics{link_missing}++;
1557             }
1558         }
1559
1560         # Restore normal CFLAGS.
1561         if ($restore_cflags) {
1562             @cflags = @cflags_backup;
1563         }
1564     }
1565 }
1566
1567 # Print statistics for buildd mode, only output in this mode.
1568 if ($option_buildd) {
1569     my @warning;
1570
1571     if ($statistics{preprocess_missing}) {
1572         push @warning, sprintf 'CPPFLAGS %d (of %d)',
1573                                $statistics{preprocess_missing},
1574                                $statistics{preprocess};
1575     }
1576     if ($statistics{compile_missing}) {
1577         push @warning, sprintf 'CFLAGS %d (of %d)',
1578                                $statistics{compile_missing},
1579                                $statistics{compile};
1580     }
1581     if ($statistics{compile_cpp_missing}) {
1582         push @warning, sprintf 'CXXFLAGS %d (of %d)',
1583                                $statistics{compile_cpp_missing},
1584                                $statistics{compile_cpp};
1585     }
1586     if ($statistics{link_missing}) {
1587         push @warning, sprintf 'LDFLAGS %d (of %d)',
1588                                $statistics{link_missing},
1589                                $statistics{link};
1590     }
1591     if (scalar @warning) {
1592         local $" = ', '; # array join string
1593         print "$buildd_tag{flags_missing}|@warning missing|\n";
1594     }
1595
1596     if ($statistics{commands_nonverbose}) {
1597         printf "$buildd_tag{non_verbose_build}|%d (of %d) hidden|\n",
1598                $statistics{commands_nonverbose},
1599                $statistics{commands},
1600     }
1601 }
1602
1603
1604 exit $exit;
1605
1606
1607 __END__
1608
1609 =head1 NAME
1610
1611 blhc - build log hardening check, checks build logs for missing hardening flags
1612
1613 =head1 SYNOPSIS
1614
1615 B<blhc> [I<options>] I<< <dpkg-buildpackage build log file>.. >>
1616
1617 =head1 DESCRIPTION
1618
1619 blhc is a small tool which checks build logs for missing hardening flags. It's
1620 licensed under the GPL 3 or later.
1621
1622 It's designed to check build logs generated by Debian's dpkg-buildpackage (or
1623 tools using dpkg-buildpackage like pbuilder or sbuild (which is used for the
1624 official buildd build logs)) to help maintainers detect missing hardening
1625 flags in their packages.
1626
1627 Only gcc is detected as compiler at the moment. If other compilers support
1628 hardening flags as well, please report them.
1629
1630 If there's no output, no flags are missing and the build log is fine.
1631
1632 See F<README> for details about performed checks, auto-detection and
1633 limitations.
1634
1635 =head1 FALSE POSITIVES
1636
1637 To suppress false positives you can embed the following string in the build
1638 log:
1639
1640     blhc: ignore-line-regexp: REGEXP
1641
1642 All lines fully matching REGEXP (see B<--ignore-line> for details) will be
1643 ignored. The string can be embedded multiple times to ignore different
1644 regexps.
1645
1646 Please use this feature sparingly so that missing flags are not overlooked. If
1647 you find false positives which affect more packages please report a bug.
1648
1649 To generate this string simply use echo in C<debian/rules>; make sure to use @
1650 to suppress the echo command itself as it could also trigger a false positive.
1651 If the build process takes a long time edit the C<.build> file in place and
1652 tweak the ignore string until B<blhc --all --debian package.build> no longer
1653 reports any false positives.
1654
1655 =head1 OPTIONS
1656
1657 =over 8
1658
1659 =item B<--all>
1660
1661 Force check for all +all (+pie, +bindnow) hardening flags. By default it's
1662 auto detected.
1663
1664 =item B<--arch> I<architecture>
1665
1666 Set the specific architecture (e.g. amd64, armel, etc.), automatically
1667 disables hardening flags not available on this architecture. Is detected
1668 automatically if dpkg-buildpackage is used.
1669
1670 =item B<--bindnow>
1671
1672 Force check for all +bindnow hardening flags. By default it's auto detected.
1673
1674 =item B<--buildd>
1675
1676 Special mode for buildds when automatically parsing log files. The following
1677 changes are in effect:
1678
1679 =over 2
1680
1681 =item *
1682
1683 Print tags instead of normal warnings, see L</"BUILDD TAGS"> for a list of
1684 possible tags.
1685
1686 =item *
1687
1688 Don't check hardening flags in old log files (if dpkg-dev << 1.16.1 is
1689 detected).
1690
1691 =item *
1692
1693 Don't require Term::ANSIColor.
1694
1695 =item *
1696
1697 Return exit code 0, unless there was a error (-I, -W messages don't count as
1698 error).
1699
1700 =back
1701
1702 =item B<--debian>
1703
1704 Apply Debian-specific settings. At the moment this only disables checking for
1705 PIE which is automatically applied by Debian's GCC and no longer requires a
1706 compiler command line argument.
1707
1708 =item B<--color>
1709
1710 Use colored (ANSI) output for warning messages.
1711
1712 =item B<--line-numbers>
1713
1714 Display line numbers.
1715
1716 =item B<--ignore-arch> I<arch>
1717
1718 Ignore build logs from architectures matching I<arch>. I<arch> is a string.
1719
1720 Used to prevent false positives. This option can be specified multiple times.
1721
1722 =item B<--ignore-arch-flag> I<arch>:I<flag>
1723
1724 Like B<--ignore-flag>, but only ignore flag on I<arch>.
1725
1726 =item B<--ignore-arch-line> I<arch>:I<line>
1727
1728 Like B<--ignore-line>, but only ignore line on I<arch>.
1729
1730 =item B<--ignore-flag> I<flag>
1731
1732 Don't print an error when the specific flag is missing in a compiler line.
1733 I<flag> is a string.
1734
1735 Used to prevent false positives. This option can be specified multiple times.
1736
1737 =item B<--ignore-line> I<regex>
1738
1739 Ignore lines matching the given Perl regex. I<regex> is automatically anchored
1740 at the beginning and end of the line to prevent false negatives.
1741
1742 B<NOTE>: Not the input lines are checked, but the lines which are displayed in
1743 warnings (which have line continuation resolved).
1744
1745 Used to prevent false positives. This option can be specified multiple times.
1746
1747 =item B<--pie>
1748
1749 Force check for all +pie hardening flags. By default it's auto detected.
1750
1751 =item B<-h -? --help>
1752
1753 Print available options.
1754
1755 =item B<--version>
1756
1757 Print version number and license.
1758
1759 =back
1760
1761 Auto detection for B<--pie> and B<--bindnow> only works if at least one
1762 command uses the required hardening flag (e.g. -fPIE). Then it's required for
1763 all other commands as well.
1764
1765 =head1 EXAMPLES
1766
1767 Normal usage, parse a single log file.
1768
1769     blhc path/to/log/file
1770
1771 If there's no output, no flags are missing and the build log is fine.
1772
1773 Parse multiple log files. The exit code is ORed over all files.
1774
1775     blhc path/to/directory/with/log/files/*
1776
1777 Don't treat missing C<-g> as error:
1778
1779     blhc --ignore-flag -g path/to/log/file
1780
1781 Don't treat missing C<-pie> on kfreebsd-amd64 as error:
1782
1783     blhc --ignore-arch-flag kfreebsd-amd64:-pie path/to/log/file
1784
1785 Ignore lines consisting exactly of C<./script gcc file> which would cause a
1786 false positive.
1787
1788     blhc --ignore-line '\./script gcc file' path/to/log/file
1789
1790 Ignore lines matching C<./script gcc file> somewhere in the line.
1791
1792     blhc --ignore-line '.*\./script gcc file.*' path/to/log/file
1793
1794 Use blhc with pbuilder.
1795
1796     pbuilder path/to/package.dsc | tee path/log/file
1797     blhc path/to/file || echo flags missing
1798
1799 Assume this build log was created on a Debian system and thus don't warn about
1800 missing PIE flags if the current architecture injects them automatically (this
1801 is enabled in buildd mode per default). C<--arch> is necessary if the build
1802 log contains no architecture information as written by dpkg-buildpackage.
1803
1804     blhc --debian --all --arch=amd64 path/to/log/file
1805
1806 =head1 BUILDD TAGS
1807
1808 The following tags are used in I<--buildd> mode. In braces the additional data
1809 which is displayed.
1810
1811 =over 2
1812
1813 =item B<I-hardening-wrapper-used>
1814
1815 The package uses hardening-wrapper which intercepts calls to gcc and adds
1816 hardening flags. The build log doesn't contain any hardening flags and thus
1817 can't be checked by blhc.
1818
1819 =item B<W-compiler-flags-hidden> (summary of hidden lines)
1820
1821 Build log contains lines which hide the real compiler flags. For example:
1822
1823     CC test-a.c
1824     CC test-b.c
1825     CC test-c.c
1826     LD test
1827
1828 Most of the time either C<export V=1> or C<export verbose=1> in
1829 F<debian/rules> fixes builds with hidden compiler flags. Sometimes C<.SILENT>
1830 in a F<Makefile> must be removed. And as last resort the F<Makefile> must be
1831 patched to remove the C<@>s hiding the real compiler commands.
1832
1833 =item B<W-dpkg-buildflags-missing> (summary of missing flags)
1834
1835 CPPFLAGS, CFLAGS, CXXFLAGS, LDFLAGS missing.
1836
1837 =item B<I-invalid-cmake-used> (version)
1838
1839 By default CMake ignores CPPFLAGS thus missing those hardening flags. Debian
1840 patched CMake in versions 2.8.7-1 and 2.8.7-2 to respect CPPFLAGS, but this
1841 patch was rejected by upstream and later reverted in Debian. Thus those two
1842 versions show correct usage of CPPFLAGS even if the package doesn't correctly
1843 handle them (for example by passing them to CFLAGS). To prevent false
1844 negatives just blacklist those two versions.
1845
1846 =item B<I-no-compiler-commands>
1847
1848 No compiler commands were detected. Either the log contains none or they were
1849 not correctly detected by blhc (please report the bug in this case).
1850
1851 =back
1852
1853 =head1 EXIT STATUS
1854
1855 The exit status is a "bit mask", each listed status is ORed when the error
1856 condition occurs to get the result.
1857
1858 =over 4
1859
1860 =item B<0>
1861
1862 Success.
1863
1864 =item B<1>
1865
1866 No compiler commands were found.
1867
1868 =item B<2>
1869
1870 Invalid arguments/options given to blhc.
1871
1872 =item B<4>
1873
1874 Non verbose build.
1875
1876 =item B<8>
1877
1878 Missing hardening flags.
1879
1880 =item B<16>
1881
1882 Hardening wrapper detected, no tests performed.
1883
1884 =item B<32>
1885
1886 Invalid CMake version used. See B<I-invalid-cmake-used> under L</"BUILDD
1887 TAGS"> for a detailed explanation.
1888
1889 =back
1890
1891 =head1 AUTHOR
1892
1893 Simon Ruderich, E<lt>simon@ruderich.orgE<gt>
1894
1895 Thanks to to Bernhard R. Link E<lt>brlink@debian.orgE<gt> and Jaria Alto
1896 E<lt>jari.aalto@cante.netE<gt> for their valuable input and suggestions.
1897
1898 =head1 LICENSE AND COPYRIGHT
1899
1900 Copyright (C) 2012-2024 by Simon Ruderich
1901
1902 This program is free software: you can redistribute it and/or modify
1903 it under the terms of the GNU General Public License as published by
1904 the Free Software Foundation, either version 3 of the License, or
1905 (at your option) any later version.
1906
1907 This program is distributed in the hope that it will be useful,
1908 but WITHOUT ANY WARRANTY; without even the implied warranty of
1909 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
1910 GNU General Public License for more details.
1911
1912 You should have received a copy of the GNU General Public License
1913 along with this program.  If not, see <http://www.gnu.org/licenses/>.
1914
1915 =head1 SEE ALSO
1916
1917 L<hardening-check(1)>, L<dpkg-buildflags(1)>
1918
1919 =cut