use Getopt::Long ();
use Text::ParseWords ();
-our $VERSION = '0.02';
+our $VERSION = '0.03';
# CONSTANTS/VARIABLES
# C++
qw( hh H hp hxx hpp HPP h++ tcc ),
);
+# Object files.
+my @object = (
+ # Normal object files.
+ qw ( o ),
+ # Libtool object files.
+ qw ( lo la ),
+ # Dynamic libraries. bzip2 uses .sho.
+ qw ( so sho ),
+ # Static libraries.
+ qw ( a ),
+);
# Hashes for fast extensions lookup to check if a file falls in one of these
# categories.
@source_preprocess_compile_cpp,
@source_no_preprocess_compile_cpp,
);
+my %extensions_object = map { $_ => 1 } (
+ @object,
+);
my %extension = map { $_ => 1 } (
@source_no_preprocess,
@header_preprocess,
@source_preprocess,
+ @object,
);
# Regexp to match file extensions.
# @def_cxxflags_* is the same as @def_cflags_*.
my @def_cppflags = ();
my @def_cppflags_fortify = (
- '-D_FORTIFY_SOURCE=2',
+ '-D_FORTIFY_SOURCE=2', # must be first, see cppflags_fortify_broken()
+ # If you add another flag fix hack below (search for "Hack to fix").
+);
+my @def_cppflags_fortify_bad = (
+ # These flags may overwrite -D_FORTIFY_SOURCE=2.
+ '-U_FORTIFY_SOURCE',
+ '-D_FORTIFY_SOURCE=0',
+ '-D_FORTIFY_SOURCE=1',
);
my @def_ldflags = ();
my @def_ldflags_relro = (
'-fpic',
'-shared',
);
-# References to all flags checked by the parser.
+# References to all flags checked by the flag checker.
my @flag_refs = (
\@def_cflags,
\@def_cflags_format,
# References to all used flags.
my @flag_refs_all = (
@flag_refs,
+ \@def_cppflags_fortify_bad,
\@def_ldflags_pic,
);
# Renaming rules for the output so the regex parts are not visible. Also
return 0;
}
+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.
+
+ 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;
+ }
+
+ return 0;
+}
+
# Modifies $missing_flags_ref array.
sub pic_pie_conflict {
my ($line, $pie, $missing_flags_ref, @flags_pie) = @_;
foreach my $file (@ARGV) {
print "checking '$file'...\n" if scalar @ARGV > 1;
+ -f $file or die "No such file: $file";
+
open my $fh, '<', $file or die $!;
# Architecture of this file.
and ($1 eq '2.8.7-1' or $1 eq '2.8.7-2')) {
if (not $option_buildd) {
error_invalid_cmake($1);
+ $exit |= $exit_code{invalid_cmake};
} else {
- print "$buildd_tag{invalid_cmake} $1\n";
+ print "$buildd_tag{invalid_cmake}|$1|\n";
}
- $exit |= $exit_code{invalid_cmake};
}
# If hardening wrapper is used (wraps calls to gcc and adds hardening
and $line =~ /\bhardening-wrapper\b/) {
if (not $option_buildd) {
error_hardening_wrapper();
+ $exit |= $exit_code{hardening_wrapper};
} else {
- print "$buildd_tag{hardening_wrapper}\n";
+ print "$buildd_tag{hardening_wrapper}||\n";
}
- $exit |= $exit_code{hardening_wrapper};
next FILE;
}
[Cc]ompiler[\s.]*:?\s+
/xo;
next if $line =~ /^\s*(?:- )?(?:HOST_)?(?:CC|CXX)\s*=\s*$cc_regex_full\s*$/o;
+ # `moc-qt4`, contains '-I/usr/share/qt4/mkspecs/linux-g++' (or
+ # similar for other architectures) which gets recognized as a
+ # compiler line. Ignore it.
+ next if $line =~ m{^/usr/bin/moc-qt4
+ \s.+\s
+ -I/usr/share/qt4/mkspecs/[a-z]+-g\++(?:-64)?
+ \s}x;
# 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, @def_cflags_pie, @def_ldflags_pie);
+ $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;
if (scalar @input == 0) {
if (not $option_buildd) {
print "No compiler commands!\n";
+ $exit |= $exit_code{no_compiler_commands};
} else {
- print "$buildd_tag{no_compiler_commands}\n";
+ print "$buildd_tag{no_compiler_commands}||\n";
}
- $exit |= $exit_code{no_compiler_commands};
next FILE;
}
@ldflags = (@ldflags, @def_ldflags_bindnow);
}
+ # Hack to fix cppflags_fortify_broken() if --ignore-flag
+ # -D_FORTIFY_SOURCE=2 is used to ignore missing fortification. Only works
+ # as long as @def_cppflags_fortify contains only one variable.
+ if (scalar @def_cppflags_fortify == 0) {
+ $harden_fortify = 0;
+ }
+
# Ignore flags for this arch if requested.
if ($arch and exists $option_ignore_arch_flag{$arch}) {
my @flag_refs = (\@cflags, \@cxxflags, \@cppflags, \@ldflags);
if (is_non_verbose_build($line, $input[$i + 1], \$skip)) {
if (not $option_buildd) {
error_non_verbose_build($line);
+ $exit |= $exit_code{non_verbose_build};
} else {
$statistics{commands_nonverbose}++;
}
- $exit |= $exit_code{non_verbose_build};
next;
}
# Even if it's a verbose build, we might have to skip this line.
$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 extension_found(\%extensions_compile_link, @extensions)) {
- # Assembly files don't need CFLAGS.
- if (not extension_found(\%extensions_compile, @extensions)
- and extension_found(\%extensions_no_compile, @extensions)) {
- $compile = 0;
- # But the rest does.
- } else {
- $compile = 1;
+ if (not $flag_preprocess) {
+ # 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 (extension_found(\%extensions_compile_link, @extensions)) {
+ # Assembly files don't need CFLAGS.
+ if (not extension_found(\%extensions_compile, @extensions)
+ and extension_found(\%extensions_no_compile, @extensions)) {
+ $compile = 0;
+ # But the rest does.
+ } else {
+ $compile = 1;
+ }
+ # No compilable extensions found, either linking or compiling
+ # header flags.
+ #
+ # If there are also no object files we are just compiling headers
+ # (.h -> .h.gch). Don't check for linker flags in this case. Due
+ # to our liberal checks for compiler lines, this also reduces the
+ # number of false positives considerably.
+ } elsif ($link
+ and not extension_found(\%extensions_object, @extensions)) {
+ $link = 0;
}
}
and index($line, '`dpkg-buildflags --get CFLAGS`') == -1) {
if (not $option_buildd) {
error_flags('CFLAGS missing', \@missing, \%flag_renames, $input[$i]);
+ $exit |= $exit_code{flags_missing};
} else {
$statistics{compile_missing}++;
}
- $exit |= $exit_code{flags_missing};
} 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
and index($line, '`dpkg-buildflags --get CXXFLAGS`') == -1) {
if (not $option_buildd) {
error_flags('CXXFLAGS missing', \@missing, \%flag_renames, $input[$i]);
+ $exit |= $exit_code{flags_missing};
} else {
$statistics{compile_cpp_missing}++;
}
- $exit |= $exit_code{flags_missing};
}
- if ($preprocess and not all_flags_used($line, \@missing, @cppflags)
+ if ($preprocess
+ and (not all_flags_used($line, \@missing, @cppflags)
+ # The fortify flag might be overwritten, detect that.
+ or ($harden_fortify
+ and cppflags_fortify_broken($line, \@missing)))
# Assume dpkg-buildflags returns the correct flags.
and index($line, '`dpkg-buildflags --get CPPFLAGS`') == -1) {
if (not $option_buildd) {
error_flags('CPPFLAGS missing', \@missing, \%flag_renames, $input[$i]);
+ $exit |= $exit_code{flags_missing};
} else {
$statistics{preprocess_missing}++;
}
- $exit |= $exit_code{flags_missing};
}
if ($link and not all_flags_used($line, \@missing, @ldflags)
# Same here, -fPIC conflicts with -fPIE.
and index($line, '`dpkg-buildflags --get LDFLAGS`') == -1) {
if (not $option_buildd) {
error_flags('LDFLAGS missing', \@missing, \%flag_renames, $input[$i]);
+ $exit |= $exit_code{flags_missing};
} else {
$statistics{link_missing}++;
}
- $exit |= $exit_code{flags_missing};
}
}
}
}
if (scalar @warning) {
local $" = ', '; # array join string
- print "$buildd_tag{flags_missing} @warning missing\n";
+ print "$buildd_tag{flags_missing}|@warning missing|\n";
}
if ($statistics{commands_nonverbose}) {
- printf "$buildd_tag{non_verbose_build} %d (of %d) hidden\n",
+ printf "$buildd_tag{non_verbose_build}|%d (of %d) hidden|\n",
$statistics{commands_nonverbose},
$statistics{commands},
}
=head1 SYNOPSIS
-B<blhc> [I<options>] I<E<lt>dpkg-buildpackage build log fileE<gt>..>
+B<blhc> [I<options>] I<< <dpkg-buildpackage build log file>.. >>
=head1 DESCRIPTION
tools using dpkg-buildpackage like pbuilder or the official buildd build logs)
to help maintainers detect missing hardening flags in their packages.
+Only gcc is detected as compiler at the moment. If other compilers support
+hardening flags as well, please report them.
+
+If there's no output, no flags are missing and the build log is fine.
+
=head1 OPTIONS
=over 8
Don't require Term::ANSIColor.
+=item *
+
+Return exit code 0, unless there was a error (-I, -W messages don't count as
+error).
+
=back
=item B<--color>
blhc path/to/log/file
+If there's no output, no flags are missing and the build log is fine.
+
Parse multiple log files. The exit code is ORed over all files.
blhc path/to/directory/with/log/files/*