X-Git-Url: https://ruderich.org/simon/gitweb/?a=blobdiff_plain;f=bin%2Fblhc;h=8b21c8f2d4cfd509f928aa745f2c761f918b76d3;hb=48f6d8ed0c3c7ae59c3a55c9d2e9dd6c8aa5f052;hp=5c01ada03ff269953838fef909e31f7b7cbffd96;hpb=95bb26de690e971a07780da13c5fa19ab61864e8;p=blhc%2Fblhc.git diff --git a/bin/blhc b/bin/blhc index 5c01ada..8b21c8f 100755 --- a/bin/blhc +++ b/bin/blhc @@ -37,113 +37,150 @@ my $cc_regex = qr/(?:[a-z0-9_]+-(?:linux-|kfreebsd-)?gnu(?:eabi|eabihf)?-)? # Regex to catch (GCC) compiler warnings. my $warning_regex = qr/^(.+?):([0-9]+):[0-9]+: warning: (.+?) \[(.+?)\]$/; -# Regex for source files which require preprocessing. -my $source_preprocess_compile_regex = qr/ - # C - c - # Objective-C - | m +# List of source file extensions which require preprocessing. +my @source_preprocess_compile_cpp = ( # C++ - | cc | cp | cxx | cpp | CPP | c\+\+ | C + qw( cc cp cxx cpp CPP c++ C ), # Objective-C++ - | mm | M + qw( mm Mr), +); +my @source_preprocess_compile = ( + # C + qw( c ), + # Objective-C + qw( m ), + # (Objective-)C++ + @source_preprocess_compile_cpp, # Fortran - | F | FOR | fpp | FPP | FTN | F90 | F95 | F03 | F08 - /x; -my $source_preprocess_no_compile_regex = qr/ + qw( F FOR fpp FPP FTN F90 F95 F03 F08 ), +); +my @source_preprocess_no_compile = ( # Assembly - s - /x; -my $source_preprocess_regex = qr/ - $source_preprocess_compile_regex - | $source_preprocess_no_compile_regex - /x; -# Regex for source files which don't require preprocessing. -my $source_no_preprocess_compile_regex = qr/ - # C - i + qw( s ), +); +my @source_preprocess = ( + @source_preprocess_compile, + @source_preprocess_no_compile, +); +# List of source file extensions which don't require preprocessing. +my @source_no_preprocess_compile_cpp = ( # C++ - | ii - # Objective-C - | mi + qw( ii ), # Objective-C++ - | mii + qw( mii ), +); +my @source_no_preprocess_compile = ( + # C + qw( i ), + # (Objective-)C++ + @source_no_preprocess_compile_cpp, + # Objective-C + qw( mi ), # Fortran - | f | for | ftn | f90 | f95 | f03 | f08 - /x; -my $source_no_preprocess_no_compile_regex = qr/ + qw( f for ftn f90 f95 f03 f08 ), +); +my @source_no_preprocess_no_compile = ( # Assembly - S | sx - /x; -my $source_no_preprocess_regex = qr/ - $source_no_preprocess_compile_regex - | $source_no_preprocess_no_compile_regex - /x; -# Regex for header files which require preprocessing. -my $header_preprocess_regex = qr/ + qw( S sx ), +); +my @source_no_preprocess = ( + @source_no_preprocess_compile, + @source_no_preprocess_no_compile, +); +# List of header file extensions which require preprocessing. +my @header_preprocess = ( # C, C++, Objective-C, Objective-C++ - h + qw( h ), # C++ - | hh | H | hp | hxx | hpp | HPP | h\+\+ | tcc - /x; -# Regexps to match files with the given characteristics. -my $file_no_preprocess_regex = qr/ - $cc_regex.+? - \.(?: $source_no_preprocess_regex)\b - /x; -my $file_preprocess_regex = qr/ - $cc_regex.+? - \.(?: $header_preprocess_regex - | $source_preprocess_regex)\b - /x; -my $file_compile_link_regex = qr/ - $cc_regex.+? - \.(?: $source_preprocess_regex - | $source_no_preprocess_regex)\b - /x; -my $file_compile_regex = qr/ - $cc_regex.+? - \.(?: $source_preprocess_compile_regex - | $source_no_preprocess_compile_regex)\b - /x; -my $file_no_compile_regex = qr/ - $cc_regex.+ - \.(?: $source_preprocess_no_compile_regex - | $source_no_preprocess_no_compile_regex)\b + qw( hh H hp hxx hpp HPP h++ tcc ), +); + +# Hashes for fast extensions lookup to check if a file falls in one of these +# categories. +my %extensions_no_preprocess = map { $_ => 1 } ( + @source_no_preprocess, +); +my %extensions_preprocess = map { $_ => 1 } ( + @header_preprocess, + @source_preprocess, +); +my %extensions_compile_link = map { $_ => 1 } ( + @source_preprocess, + @source_no_preprocess, +); +my %extensions_compile = map { $_ => 1 } ( + @source_preprocess_compile, + @source_no_preprocess_compile, +); +my %extensions_no_compile = map { $_ => 1 } ( + @source_preprocess_no_compile, + @source_no_preprocess_no_compile, +); +my %extensions_compile_cpp = map { $_ => 1 } ( + @source_preprocess_compile_cpp, + @source_no_preprocess_compile_cpp, +); +my %extension = map { $_ => 1 } ( + @source_no_preprocess, + @source_no_preprocess_compile, + @source_no_preprocess_compile_cpp, + @source_no_preprocess_no_compile, + @header_preprocess, + @source_preprocess, + @source_preprocess_compile, + @source_preprocess_compile_cpp, + @source_preprocess_no_compile, +); + +# Regexp to match file extensions. +my $file_extension_regex = qr/ + \s + \S+ # Filename without extension. + \. + ([^\\.\s]+) # File extension. + (?=\s|\\) # At end of word. Can't use \b because some files have non + # word characters at the end and because \b matches double + # extensions (like .cpp.o). Works always as all lines are + # terminated with "\n". /x; # Expected (hardening) flags. All flags are used as regexps. -my @cflags = ( +my @def_cflags = ( '-g', '-O(?:2|3)', ); -my @cflags_format = ( +my @def_cflags_format = ( '-Wformat', '-Wformat-security', '-Werror=format-security', ); -my @cflags_fortify = ( +my @def_cflags_fortify = ( # fortify needs at least -O1, but -O2 is recommended anyway ); -my @cflags_stack = ( +my @def_cflags_stack = ( '-fstack-protector', '--param=ssp-buffer-size=4', ); -my @cflags_pie = ( +my @def_cflags_pie = ( '-fPIE', ); -my @cppflags = (); -my @cppflags_fortify = ( +my @def_cxxflags = ( + '-g', + '-O(?:2|3)', +); +# @def_cxxflags_* is the same as @def_cflags_*. +my @def_cppflags = (); +my @def_cppflags_fortify = ( '-D_FORTIFY_SOURCE=2', ); -my @ldflags = (); -my @ldflags_relro = ( +my @def_ldflags = (); +my @def_ldflags_relro = ( '-Wl,(-z,)?relro', ); -my @ldflags_bindnow = ( +my @def_ldflags_bindnow = ( '-Wl,(-z,)?now', ); -my @ldflags_pie = ( +my @def_ldflags_pie = ( '-fPIE', '-pie', ); @@ -274,7 +311,7 @@ sub is_non_verbose_build { $file =~ m{/([a-zA-Z0-9._-]+)$}; $file = $1; - if ($next_line =~ /\Q$file\E/ and $next_line =~ /$cc_regex/) { + if ($next_line =~ /\Q$file\E/ and $next_line =~ /$cc_regex/o) { # We still have to skip the current line as it doesn't contain any # compiler commands. ${$skip_ref} = 1; @@ -285,20 +322,27 @@ sub is_non_verbose_build { return 1; } +sub extension_found { + my ($extensions_ref, @extensions) = @_; -# MAIN + my $found = 0; + foreach my $extension (@extensions) { + if (exists $extensions_ref->{$extension}) { + $found = 1; + last; + } + } + return $found; +} -# Hardening options. Not all architectures support all hardening options. -my $harden_format = 1; -my $harden_fortify = 1; -my $harden_stack = 1; -my $harden_relro = 1; -my $harden_bindnow = 0; -my $harden_pie = 0; + +# MAIN # Parse command line arguments. my $option_help = 0; my $option_version = 0; +my $option_pie = 0; +my $option_bindnow = 0; my $option_all = 0; my $option_arch = undef; my $option_buildd = 0; @@ -307,12 +351,12 @@ if (not Getopt::Long::GetOptions( 'help|h|?' => \$option_help, 'version' => \$option_version, # Hardening options. - 'pie' => \$harden_pie, - 'bindnow' => \$harden_bindnow, + 'pie' => \$option_pie, + 'bindnow' => \$option_bindnow, 'all' => \$option_all, # Misc. 'color' => \$option_color, - 'arch' => \$option_arch, + 'arch=s' => \$option_arch, 'buildd' => \$option_buildd, )) { require Pod::Usage; @@ -342,20 +386,31 @@ along with this program. If not, see . } if ($option_all) { - $harden_pie = 1; - $harden_bindnow = 1; + $option_pie = 1; + $option_bindnow = 1; } # Final exit code. my $exit = 0; +FILE: foreach my $file (@ARGV) { +open my $fh, '<', $file or die "$!: $file"; + +# Hardening options. Not all architectures support all hardening options. +my $harden_format = 1; +my $harden_fortify = 1; +my $harden_stack = 1; +my $harden_relro = 1; +my $harden_bindnow = $option_bindnow; # defaults to 0 +my $harden_pie = $option_pie; # defaults to 0 + # Input lines, contain only the lines with compiler commands. my @input = (); my $start = 0; my $continuation = 0; my $complete_line = undef; -while (my $line = <>) { +while (my $line = <$fh>) { # dpkg-buildflags only provides hardening flags since 1.16.1, don't check # for hardening flags in buildd mode if an older dpkg-dev is used. Default # flags (-g -O2) are still checked. @@ -381,7 +436,7 @@ while (my $line = <>) { if (not $start and $line =~ /^Build-Depends: .*\bhardening-wrapper\b/) { error_hardening_wrapper(); $exit |= 1 << 4; - exit $exit; + next FILE; } # We skip over unimportant lines at the beginning of the log to prevent @@ -400,17 +455,19 @@ while (my $line = <>) { } # Ignore compiler warnings for now. - next if $line =~ /$warning_regex/; - - # Remove all ANSI color sequences which are sometimes used in non-verbose - # builds. - $line = Term::ANSIColor::colorstrip($line); - # Also strip '\0xf' (delete previous character), used by Elinks' build - # system. - $line =~ s/\x0f//g; - # And "ESC(B" which seems to be used on armhf and hurd (not sure what it - # does). - $line =~ s/\033\(B//g; + next if $line =~ /$warning_regex/o; + + if ($line =~ /\033/) { # esc + # Remove all ANSI color sequences which are sometimes used in + # non-verbose builds. + $line = Term::ANSIColor::colorstrip($line); + # Also strip '\0xf' (delete previous character), used by Elinks' build + # system. + $line =~ s/\x0f//g; + # And "ESC(B" which seems to be used on armhf and hurd (not sure what + # it does). + $line =~ s/\033\(B//g; + } # Check if this line indicates a non verbose build. my $non_verbose = is_non_verbose_build($line); @@ -419,12 +476,13 @@ while (my $line = <>) { # line. parse_line() is slow, only use it when necessary. my @line = (not $line =~ /;/) ? ($line) - : Text::ParseWords::parse_line(';', 1, $line); + : map { + # Ensure newline at the line end - necessary for correct + # parsing later. + $_ =~ s/\s+$//; + $_ .= "\n"; + } Text::ParseWords::parse_line(';', 1, $line); foreach $line (@line) { - # Add newline, drop all other whitespace at the end of a line. - $line =~ s/\s+$//; - $line .= "\n"; - if ($continuation) { $continuation = 0; @@ -451,7 +509,7 @@ while (my $line = <>) { } # Ignore lines with no compiler commands. - next if $line !~ /\b$cc_regex(?:\s|\\)/ and not $non_verbose; + next if $line !~ /\b$cc_regex(?:\s|\\)/o and not $non_verbose; # Ignore false positives. # @@ -462,25 +520,29 @@ while (my $line = <>) { (?:C|c)ompiler[\s.]*:?\s+ $cc_regex (?:\s-std=[a-z0-9:+]+)?\s*$ - /x - or $line =~ /^\s*(?:- )?(?:HOST_)?(?:CC|CXX)\s*=\s*$cc_regex\s*$/ + /xo + or $line =~ /^\s*(?:- )?(?:HOST_)?(?:CC|CXX)\s*=\s*$cc_regex\s*$/o or $line =~ /^\s*-- Check for working (?:C|CXX) compiler: / or $line =~ /^\s*(?:echo )?Using [A-Z_]+\s*=\s*/; + # `make` output. + next if $line =~ /^Making [a-z]+ in \S+/; # e.g. "[...] in c++" # Check if additional hardening options were used. Used to ensure # they are used for the complete build. - $harden_pie = 1 if any_flags_used($line, @cflags_pie, @ldflags_pie); - $harden_bindnow = 1 if any_flags_used($line, @ldflags_bindnow); + $harden_pie = 1 if any_flags_used($line, @def_cflags_pie, @def_ldflags_pie); + $harden_bindnow = 1 if any_flags_used($line, @def_ldflags_bindnow); push @input, $line; } } } +close $fh; + if (scalar @input == 0) { print "No compiler commands!\n"; $exit |= 1; - exit $exit; + next FILE; } # Option or auto detected. @@ -504,26 +566,35 @@ if ($option_arch) { } } +# Default values. +my @cflags = @def_cflags; +my @cxxflags = @def_cxxflags; +my @cppflags = @def_cppflags; +my @ldflags = @def_ldflags; # Check the specified hardening options, same order as dpkg-buildflags. if ($harden_pie) { - @cflags = (@cflags, @cflags_pie); - @ldflags = (@ldflags, @ldflags_pie); + @cflags = (@cflags, @def_cflags_pie); + @cxxflags = (@cxxflags, @def_cflags_pie); + @ldflags = (@ldflags, @def_ldflags_pie); } if ($harden_stack) { - @cflags = (@cflags, @cflags_stack); + @cflags = (@cflags, @def_cflags_stack); + @cxxflags = (@cxxflags, @def_cflags_stack); } if ($harden_fortify) { - @cflags = (@cflags, @cflags_fortify); - @cppflags = (@cppflags, @cppflags_fortify); + @cflags = (@cflags, @def_cflags_fortify); + @cxxflags = (@cxxflags, @def_cflags_fortify); + @cppflags = (@cppflags, @def_cppflags_fortify); } if ($harden_format) { - @cflags = (@cflags, @cflags_format); + @cflags = (@cflags, @def_cflags_format); + @cxxflags = (@cxxflags, @def_cflags_format); } if ($harden_relro) { - @ldflags = (@ldflags, @ldflags_relro); + @ldflags = (@ldflags, @def_ldflags_relro); } if ($harden_bindnow) { - @ldflags = (@ldflags, @ldflags_bindnow); + @ldflags = (@ldflags, @def_ldflags_bindnow); } for (my $i = 0; $i < scalar @input; $i++) { @@ -538,6 +609,10 @@ for (my $i = 0; $i < scalar @input; $i++) { # Even if it's a verbose build, we might have to skip this line. next if $skip; + # Remove everything until and including the compiler command. Makes checks + # easier and faster. + $line =~ s/^.*?$cc_regex//o; + # Skip unnecessary tests when only preprocessing. my $flag_preprocess = 0; @@ -546,7 +621,7 @@ for (my $i = 0; $i < scalar @input; $i++) { my $link = 0; # Preprocess, compile, assemble. - if ($line =~ /$cc_regex.*?\s(-E|-S|-c)\b/) { + if ($line =~ /\s(-E|-S|-c)\b/) { $preprocess = 1; $flag_preprocess = 1 if $1 eq '-E'; $compile = 1 if $1 eq '-S' or $1 eq '-c'; @@ -555,22 +630,28 @@ for (my $i = 0; $i < scalar @input; $i++) { $link = 1; } + # Get all file extensions on this line. + my @extensions = $line =~ /$file_extension_regex/go; + # Ignore all unknown extensions to speedup the search below. + @extensions = grep { exists $extension{$_} } @extensions; + # These file types don't require preprocessing. - if ($line =~ /$file_no_preprocess_regex/) { + if (extension_found(\%extensions_no_preprocess, @extensions)) { $preprocess = 0; } # These file types require preprocessing. - if ($line =~ /$file_preprocess_regex/) { + if (extension_found(\%extensions_preprocess, @extensions)) { $preprocess = 1; } # If there are source files then it's compiling/linking in one step and we # must check both. We only check for source files here, because header # files cause too many false positives. - if (not $flag_preprocess and $line =~ /$file_compile_link_regex/) { + if (not $flag_preprocess + and extension_found(\%extensions_compile_link, @extensions)) { # Assembly files don't need CFLAGS. - if (not $line =~ /$file_compile_regex/ - and $line =~ /$file_no_compile_regex/) { + if (not extension_found(\%extensions_compile, @extensions) + and extension_found(\%extensions_no_compile, @extensions)) { $compile = 0; # But the rest does. } else { @@ -578,32 +659,50 @@ for (my $i = 0; $i < scalar @input; $i++) { } } + # Assume CXXFLAGS are required when a C++ file is specified in the + # compiler line. + my $compile_cpp = 0; + if ($compile + and extension_found(\%extensions_compile_cpp, @extensions)) { + $compile = 0; + $compile_cpp = 1; + } + # Check hardening flags. my @missing; if ($compile and not all_flags_used($line, \@missing, @cflags) # 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. - and not pic_pie_conflict($line, $harden_pie, \@missing, @cflags_pie) + and not pic_pie_conflict($line, $harden_pie, \@missing, @def_cflags_pie) + # Assume dpkg-buildflags returns the correct flags. + and not $line =~ /`dpkg-buildflags --get CFLAGS`/) { + error_flags('CFLAGS missing', \@missing, \%flag_renames, $input[$i]); + $exit |= 1 << 3; + } elsif ($compile_cpp and not all_flags_used($line, \@missing, @cflags) + # 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. + and not pic_pie_conflict($line, $harden_pie, \@missing, @def_cflags_pie) # Assume dpkg-buildflags returns the correct flags. - and not $line =~ /`dpkg-buildflags --get (?:CFLAGS|CXXFLAGS)`/) { - error_flags('CFLAGS missing', \@missing, \%flag_renames, $line); + and not $line =~ /`dpkg-buildflags --get CXXFLAGS`/) { + error_flags('CXXFLAGS missing', \@missing, \%flag_renames, $input[$i]); $exit |= 1 << 3; } if ($preprocess and not all_flags_used($line, \@missing, @cppflags) # Assume dpkg-buildflags returns the correct flags. and not $line =~ /`dpkg-buildflags --get CPPFLAGS`/) { - error_flags('CPPFLAGS missing', \@missing, \%flag_renames, $line); + error_flags('CPPFLAGS missing', \@missing, \%flag_renames, $input[$i]); $exit |= 1 << 3; } if ($link and not all_flags_used($line, \@missing, @ldflags) # Same here, -fPIC conflicts with -fPIE. - and not pic_pie_conflict($line, $harden_pie, \@missing, @ldflags_pie) + and not pic_pie_conflict($line, $harden_pie, \@missing, @def_ldflags_pie) # Assume dpkg-buildflags returns the correct flags. and not $line =~ /`dpkg-buildflags --get LDFLAGS`/) { - error_flags('LDFLAGS missing', \@missing, \%flag_renames, $line); + error_flags('LDFLAGS missing', \@missing, \%flag_renames, $input[$i]); $exit |= 1 << 3; } } +} exit $exit; @@ -616,7 +715,7 @@ blhc - build log hardening check, checks build logs for missing hardening flags =head1 SYNOPSIS -B [options] +B [options] .. --all force +all (+pie, +bindnow) check --arch set architecture (autodetected)