X-Git-Url: https://ruderich.org/simon/gitweb/?p=blhc%2Fblhc.git;a=blobdiff_plain;f=bin%2Fblhc;h=d6e269091878869dbe8ab371ae954913003af2e8;hp=b37bf6ee30d8605ce35903fd79eb488b4b03491e;hb=c4814aa2ad8a7a35e71839347b7fae2507be2ded;hpb=3ea6e44ffc1cf56f95831c108825f39985d913c9 diff --git a/bin/blhc b/bin/blhc index b37bf6e..d6e2690 100755 --- a/bin/blhc +++ b/bin/blhc @@ -2,7 +2,7 @@ # Build log hardening check, checks build logs for missing hardening flags. -# Copyright (C) 2012-2018 Simon Ruderich +# Copyright (C) 2012-2019 Simon Ruderich # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -24,7 +24,7 @@ use warnings; use Getopt::Long (); use Text::ParseWords (); -our $VERSION = '0.07'; +our $VERSION = '0.09'; # CONSTANTS/VARIABLES @@ -53,7 +53,7 @@ my $cc_regex_normal = qr/ my $warning_regex = qr/^(.+?):(\d+):\d+: warning: (.+?) \[(.+?)\]$/; # Regex to catch libtool commands and not lines which show commands executed # by libtool (e.g. libtool: link: ...). -my $libtool_regex = qr/\blibtool\s.*--mode=/; +my $libtool_regex = qr/\blibtool["']?\s.*--mode=/; my $libtool_link_regex = qr/\blibtool: link: /; # List of source file extensions which require preprocessing. @@ -222,11 +222,17 @@ my @def_cflags_fortify = ( # fortify needs at least -O1, but -O2 is recommended anyway ); my @def_cflags_stack = ( - '-fstack-protector', + '-fstack-protector', # keep first, used by cflags_stack_broken() '--param[= ]ssp-buffer-size=4', ); my @def_cflags_stack_strong = ( - '-fstack-protector-strong', + '-fstack-protector-strong', # keep first, used by cflags_stack_broken() +); +my @def_cflags_stack_bad = ( + # Blacklist all stack protector options for simplicity. + '-fno-stack-protector', + '-fno-stack-protector-all', + '-fno-stack-protector-strong', ); my @def_cflags_pie = ( '-fPIE', @@ -270,6 +276,7 @@ my @flag_refs = ( \@def_cflags_fortify, \@def_cflags_stack, \@def_cflags_stack_strong, + \@def_cflags_stack_bad, \@def_cflags_pie, \@def_cxxflags, \@def_cppflags, @@ -427,21 +434,61 @@ sub all_flags_used { @{$missing_flags_ref} = @missing_flags; return 0; } +# Check if any of \@bad_flags occurs after $good_flag. Doesn't check if +# $good_flag is present. +sub flag_overwritten { + my ($line, $good_flag, $bad_flags) = @_; + + if (not any_flags_used($line, @{$bad_flags})) { + return 0; + } + + my $bad_pos = 0; + foreach my $flag (@{$bad_flags}) { + while ($line =~ /$flag/g) { + if ($bad_pos < $+[0]) { + $bad_pos = $+[0]; + } + } + } + my $good_pos = 0; + while ($line =~ /$good_flag/g) { + $good_pos = $+[0]; + } + if ($good_pos > $bad_pos) { + return 0; + } + return 1; +} sub cppflags_fortify_broken { my ($line, $missing_flags) = @_; - # This doesn't take the position into account, but is a simple solution. - # And if the build system tries to force -D_FORTIFY_SOURCE=0/1, something - # is wrong anyway. + # $def_cppflags_fortify[0] must be -D_FORTIFY_SOURCE=2! + my $fortify_source = $def_cppflags_fortify[0]; - if (any_flags_used($line, @def_cppflags_fortify_bad)) { - # $def_cppflags_fortify[0] must be -D_FORTIFY_SOURCE=2! - push @{$missing_flags}, $def_cppflags_fortify[0]; - return 1; + # Some build systems enable/disable fortify source multiple times, check + # the final result. + if (not flag_overwritten($line, + $fortify_source, + \@def_cppflags_fortify_bad)) { + return 0; } + push @{$missing_flags}, $fortify_source; + return 1; +} - return 0; +sub cflags_stack_broken { + my ($line, $missing_flags, $strong) = @_; + + my $flag = $strong ? $def_cflags_stack_strong[0] + : $def_cflags_stack[0]; + + if (not flag_overwritten($line, $flag, \@def_cflags_stack_bad)) { + return 0; + } + push @{$missing_flags}, $flag; + return 1; } # Modifies $missing_flags_ref array. @@ -497,7 +544,8 @@ sub is_non_verbose_build { return 0 if $line =~ /^\s*C\+\+.+?:\s+(?:yes|no)\s*$/; return 0 if $line =~ /^\s*C\+\+ Library: stdc\+\+$/; # "Compiling" non binary files. - return 0 if $line =~ /^\s*Compiling \S+\.(?:py|el)['"]?\s*(?:\.\.\.)?$/; + return 0 if $line =~ /^\s*Compiling \S+\.(?:py|pyx|el)['"]?\s*(?:\.\.\.|because it changed\.)?$/; + return 0 if $line =~ /^\s*[Cc]ompiling catalog \S+\.po\b/; # "Compiling" with no file name. if ($line =~ /^\s*[Cc]ompiling\s+(.+?)(?:\.\.\.)?$/) { # $file_extension_regex may need spaces around the filename. @@ -636,7 +684,7 @@ if ($option_help) { } if ($option_version) { print <<"EOF"; -blhc $VERSION Copyright (C) 2012-2018 Simon Ruderich +blhc $VERSION Copyright (C) 2012-2019 Simon Ruderich This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -744,13 +792,6 @@ foreach my $file (@ARGV) { my $harden_bindnow = $option_bindnow; # defaults to 0 my $harden_pie = $option_pie; # defaults to 0 - # Does this build log use ada? Ada also uses gcc as compiler but uses - # different CFLAGS. But only perform ada checks if an ada compiler is used - # for performance reasons. - my $ada = 0; - # Fortran also requires different CFLAGS. - my $fortran = 0; - # Number of parallel jobs to prevent false positives when detecting # non-verbose builds. As not all jobs declare the number of parallel jobs # use a large enough default. @@ -859,20 +900,11 @@ foreach my $file (@ARGV) { } next FILE; } - - # Ada compiler. - if ($line =~ /\bgnat\b/) { - $ada = 1; - } - # Fortran compiler. - if ($line =~ /\bgfortran\b/) { - $fortran = 1; - } } # This flags is not always available, but if it is use it. if ($line =~ /^DEB_BUILD_OPTIONS=.*\bparallel=(\d+)/) { - $parallel = $1; + $parallel = $1 * 2; } # We skip over unimportant lines at the beginning of the log to @@ -907,6 +939,10 @@ foreach my $file (@ARGV) { # Detect architecture automatically unless overridden. if (not $arch + and index($line, 'dpkg-buildpackage: info: host architecture ') == 0) { + $arch = substr $line, 43, -1; # -1 to ignore '\n' at the end + # Older versions of dpkg-buildpackage + } elsif (not $arch and index($line, 'dpkg-buildpackage: host architecture ') == 0) { $arch = substr $line, 37, -1; # -1 to ignore '\n' at the end @@ -1026,7 +1062,9 @@ foreach my $file (@ARGV) { # treated as a normal compiler line. next if $line =~ m{^\s*rm\s+}; # Some build systems emit "gcc > file". - next if $line =~ m{$cc_regex_normal\s*>\s*\S+}; + next if $line =~ m{$cc_regex_normal\s*>\s*\S+}o; + # Hex output may contain "cc". + next if $line =~ m#(?:\b[0-9a-fA-F]{2,}\b\s*){5}#; # Check if additional hardening options were used. Used to ensure # they are used for the complete build. @@ -1068,7 +1106,7 @@ foreach my $file (@ARGV) { # Option or auto detected. if ($arch) { - # The following was partially copied from dpkg-dev 1.19.0.5 + # The following was partially copied from dpkg-dev 1.19.7 # (/usr/share/perl5/Dpkg/Vendor/Debian.pm, _add_build_flags()), # copyright Raphaël Hertzog , Guillem Jover # , Kees Cook , Canonical, Ltd. @@ -1085,8 +1123,24 @@ foreach my $file (@ARGV) { } my %builtin_pie_arch = map { $_ => 1 } qw( - amd64 arm64 armel armhf hurd-i386 i386 kfreebsd-amd64 kfreebsd-i386 - mips mipsel mips64el powerpc ppc64 ppc64el s390x sparc sparc64 + amd64 + arm64 + armel + armhf + hurd-i386 + i386 + kfreebsd-amd64 + kfreebsd-i386 + mips + mipsel + mips64el + powerpc + ppc64 + ppc64el + riscv64 + s390x + sparc + sparc64 ); # Disable unsupported hardening options. @@ -1143,19 +1197,15 @@ foreach my $file (@ARGV) { } # Ada doesn't support format hardening flags, see #680117 for more - # information. Same for fortran. Filter them out if either language is - # used. + # information. Same for fortran. my @cflags_backup; - my @cflags_noformat; - if (($ada or $fortran) and $harden_format) { - @cflags_noformat = grep { - my $ok = 1; - foreach my $flag (@def_cflags_format) { - $ok = 0 if $_ eq $flag; - } - $ok; - } @cflags; - } + my @cflags_noformat = grep { + my $ok = 1; + foreach my $flag (@def_cflags_format) { + $ok = 0 if $_ eq $flag; + } + $ok; + } @cflags; # Hack to fix cppflags_fortify_broken() if --ignore-flag # -D_FORTIFY_SOURCE=2 is used to ignore missing fortification. Only works @@ -1297,16 +1347,14 @@ LINE: and extension_found(\%extensions_compile_cpp, @extensions)) { $compile = 0; $compile_cpp = 1; - # Ada needs special CFLAGS, use them if only ada files are compiled. - } elsif ($ada - and extension_found(\%extensions_ada, @extensions)) { + # Ada needs special CFLAGS + } elsif (extension_found(\%extensions_ada, @extensions)) { $restore_cflags = 1; $preprocess = 0; # Ada uses no CPPFLAGS @cflags_backup = @cflags; @cflags = @cflags_noformat; - # Same for fortran. - } elsif ($fortran - and extension_found(\%extensions_fortran, @extensions)) { + # Same for fortran + } elsif (extension_found(\%extensions_fortran, @extensions)) { $restore_cflags = 1; @cflags_backup = @cflags; @cflags = @cflags_noformat; @@ -1329,7 +1377,10 @@ LINE: # Check hardening flags. my @missing; - if ($compile and not all_flags_used($line, \@missing, @cflags) + if ($compile and (not all_flags_used($line, \@missing, @cflags) + or (($harden_stack or $harden_stack_strong) + and cflags_stack_broken($line, \@missing, + $harden_stack_strong))) # Libraries linked with -fPIC don't have to (and can't) be # linked with -fPIE as well. It's no error if only PIE flags # are missing. @@ -1707,7 +1758,7 @@ Ejari.aalto@cante.netE for their valuable input and suggestions. =head1 LICENSE AND COPYRIGHT -Copyright (C) 2012-2018 by Simon Ruderich +Copyright (C) 2012-2019 by Simon Ruderich This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by