use Getopt::Long ();
use Text::ParseWords ();
-our $VERSION = '0.02';
+our $VERSION = '0.03';
# CONSTANTS/VARIABLES
(?:[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.
);
my @def_cppflags_fortify_bad = (
# These flags may overwrite -D_FORTIFY_SOURCE=2.
+ '-U_FORTIFY_SOURCE',
'-D_FORTIFY_SOURCE=0',
'-D_FORTIFY_SOURCE=1',
);
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;
}
# 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
\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.
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;
}
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)
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},
}
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>