(?:[a-z0-9_]+-(?:linux-|kfreebsd-)?gnu(?:eabi|eabihf)?-)?
$cc_regex
/x;
+# Regex to check if a line contains a compiler command.
+my $cc_regex_normal = qr/
+ \b$cc_regex(?:\s|\\)
+ /x;
# Regex to catch (GCC) compiler warnings.
my $warning_regex = qr/^(.+?):(\d+):\d+: warning: (.+?) \[(.+?)\]$/;
# 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.
my %extensions_no_preprocess = map { $_ => 1 } (
+ # There's no @header_no_preprocess.
@source_no_preprocess,
);
my %extensions_preprocess = map { $_ => 1 } (
@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.
$file = $1;
if (index($next_line, $file) != -1 and $next_line =~ /$cc_regex/o) {
- # We still have to skip the current line as it doesn't contain any
- # compiler commands.
+ # Not a non-verbose line, but we still have to skip the current line
+ # as it doesn't contain any compiler commands.
${$skip_ref} = 1;
return 0;
}
# Same for arch specific ignore flags, but only prepare here.
if (scalar @option_ignore_arch_flag > 0) {
foreach my $ignore (@option_ignore_arch_flag) {
- my ($ignore_arch, $ignore_flag) = split ':', $ignore, 2;
+ my ($ignore_arch, $ignore_flag) = split /:/, $ignore, 2;
if (not $ignore_arch or not $ignore_flag) {
printf STDERR 'Value "%s" invalid for option ignore-arch-flag '
# Same for arch specific ignore lines.
if (scalar @option_ignore_arch_line > 0) {
foreach my $ignore (@option_ignore_arch_line) {
- my ($ignore_arch, $ignore_line) = split ':', $ignore, 2;
+ my ($ignore_arch, $ignore_line) = split /:/, $ignore, 2;
if (not $ignore_arch or not $ignore_line) {
printf STDERR 'Value "%s" invalid for option ignore-arch-line '
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.
# only, doesn't use the dpkg-buildpackage header. Necessary to ignore
# build logs which aren't built (wrong architecture, build error,
# etc.).
- if (not $arch
- and $line =~ /^Architecture: (.+)$/) {
- $arch = $1;
+ if (not $arch and index($line, 'Architecture: ') == 0) {
+ $arch = substr $line, 14, -1; # -1 to ignore '\n' at the end
}
# dpkg-buildflags only provides hardening flags since 1.16.1, don't
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";
}
- $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";
}
- $exit |= $exit_code{hardening_wrapper};
next FILE;
}
# Input lines, contain only the lines with compiler commands.
my @input = ();
+ # Non-verbose lines in the input. Used to reduce calls to
+ # is_non_verbose_build() (which is quite slow) in the second loop when
+ # it's already clear if a line is non-verbose or not.
+ my @input_nonverbose = ();
my $continuation = 0;
my $complete_line = undef;
# And stop at the end of the build log. Package details (reported by
# the buildd logs) are not important for us. This also prevents false
# positives.
- last if $line =~ /^Build finished at \d{8}-\d{4}$/;
+ last if index($line, 'Build finished at ') == 0
+ and $line =~ /^Build finished at \d{8}-\d{4}$/;
# Detect architecture automatically unless overridden.
if (not $arch
- and $line =~ /^dpkg-buildpackage: host architecture (.+)$/) {
- $arch = $1;
+ and index($line, 'dpkg-buildpackage: host architecture ') == 0) {
+ $arch = substr $line, 37, -1; # -1 to ignore '\n' at the end
}
# Ignore compiler warnings for now.
next if $line =~ /$warning_regex/o;
- if (not $option_buildd and index($line, "\033") != -1) { # esc
+ if (not $option_buildd and index($line, "\033") != -1) { # \033 = esc
# Remove all ANSI color sequences which are sometimes used in
# non-verbose builds.
$line = Term::ANSIColor::colorstrip($line);
# Ignore lines with no compiler commands.
next if not $non_verbose
- and not $line =~ /\b$cc_regex(?:\s|\\)/o;
+ and not $line =~ /$cc_regex_normal/o;
# Ignore lines with no filenames with extensions. May miss some
# non-verbose builds (e.g. "gcc -o test" [sic!]), but shouldn't be
# a problem as the log will most likely contain other non-verbose
and $line =~ /^(?:checking|[Cc]onfigure:) /;
next if $line =~ /^\s*(?:Host\s+)?(?:C(?:\+\+)?\s+)?
[Cc]ompiler[\s.]*:?\s+
- /xo;
+ /x;
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
\s.+\s
-I/usr/share/qt4/mkspecs/[a-z]+-g\++(?:-64)?
\s}x;
+ # Ignore false positives when the line contains only CC=gcc but no
+ # other gcc command.
+ if ($line =~ /(.*)CC=$cc_regex_full(.*)/o) {
+ my $before = $1;
+ my $after = $2;
+ next if not $before =~ /$cc_regex_normal/o
+ and not $after =~ /$cc_regex_normal/o;
+ }
# Check if additional hardening options were used. Used to ensure
# they are used for the complete build.
$harden_bindnow = 1 if any_flags_used($line, @def_ldflags_bindnow);
push @input, $line;
+ push @input_nonverbose, $non_verbose;
}
}
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";
}
- $exit |= $exit_code{no_compiler_commands};
next FILE;
}
# Option or auto detected.
if ($arch) {
- # The following was partially copied from dpkg-dev 1.16.1.2
+ # The following was partially copied from dpkg-dev 1.16.4.3
# (/usr/share/perl5/Dpkg/Vendor/Debian.pm, add_hardening_flags()),
# copyright Raphaƫl Hertzog <hertzog@debian.org>, Kees Cook
# <kees@debian.org>, Canonical, Ltd. licensed under GPL version 2 or
# Ignore flags for this arch if requested.
if ($arch and exists $option_ignore_arch_flag{$arch}) {
- my @flag_refs = (\@cflags, \@cxxflags, \@cppflags, \@ldflags);
+ my @local_flag_refs = (\@cflags, \@cxxflags, \@cppflags, \@ldflags);
- remove_flags(\@flag_refs,
+ remove_flags(\@local_flag_refs,
\%flag_renames,
@{$option_ignore_arch_flag{$arch}});
}
}
my $skip = 0;
- if (is_non_verbose_build($line, $input[$i + 1], \$skip)) {
+ if ($input_nonverbose[$i]
+ and 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.
# checks easier and faster.
$line =~ s/^.*?$cc_regex//o;
# "([...] test.c)" is not detected as 'test.c' - fix this by removing
- # the brace and similar characters.
+ # the brace and similar characters at the line end.
$line =~ s/['")]+$//;
# Skip unnecessary tests when only preprocessing.
$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)
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};
}
}
}
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
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>
Thanks to to Bernhard R. Link E<lt>brlink@debian.orgE<gt> and Jaria Alto
E<lt>jari.aalto@cante.netE<gt> for their valuable input and suggestions.
-=head1 COPYRIGHT AND LICENSE
+=head1 LICENSE AND COPYRIGHT
Copyright (C) 2012 by Simon Ruderich