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