Hi Raphael,

On Fri, Dec 09, 2011 at 12:02:21PM +0100, Raphael Hertzog wrote:
> On Thu, 08 Dec 2011, Kees Cook wrote:
> > This patch adds that ability, and lets the environment correctly adjust it:
> > 
> > $ dpkg-buildflags --features hardening
> > -bindnow,+format,+fortify,-pie,+relro,+stackprotector
> > 
> > $ DEB_HOST_ARCH=ia64 dpkg-buildflags --features hardening
> > -bindnow,+format,+fortify,-pie,-relro,-stackprotector
> 
> This syntax is great for debian/rules because we want to be expressive in
> a single line but it's really not great for standardized output.
> 
> I would suggest to follow the standard set for "update-alternatives
> --query" and to use the well known RFC-2822 header block:

Sure! I've adjusted the output now.

> > I'm obviously open to changing how this works, what the output looks
> > like, etc. I'm just interested in the behavior to query expected
> > hardening features.
> 
> The set of features is also adjusted by the maintainer in debian/rules and
> this part can't easily be queried.

Right, and I'm not too worried about this yet.

> This limits the usefulness of this query feature.

It is a limitation, but it's not a large one in the face of answering the
question "what _should_ the feature set be for this combination of
arch/cpu/etc?" which is what both "hardening-check" and "lintian" need to
be useful for their checks.

> > --- a/scripts/Dpkg/BuildFlags.pm
> > +++ b/scripts/Dpkg/BuildFlags.pm
> 
> You're extending the API, please bump the minor number in $VERSION.

Ah, yes. Done.

> > +=item $bf->get_features($area)
> > +
> > +Return the list of features enabled for the given area.
> > +
> > +=cut
> 
> It's a hash reference that is returned. Keys are features identifiers and
> values are booleans indicating whether the feature is enabled or not.

Thanks, fixed the docs.

> You might want to return a hash so that the caller can't modify the
> content of $features.

Also a good idea; done.

> > +=item $bf->features($area)
> > +
> > +Returns a list of enabled features for a given area. Currently the
> > +only recognized area is "hardening".
> 
> Bad description. The function returns true if the $area is known, and false 
> otherwise.

Fixed.

> $flags->{'features'}{'hardening'} is mostly the same than %use_feature,
> please do not duplicate it but rather modify the code so that it works
> that way:
> 1/ generate %use_feature by directly taking into account the architecture
>    specificities
> 2/ record %use_feature with a $flags->set_feature()
> 3/ do the various $flags->apppend() based on %use_feature (and there must
>    be no supplementary conditions left in the tests at this point, it's
>    solely "if ($use_feature{"foo"}) { $flags->append() }" since the
>    supplementary conditions have been taken into account in 1/ already)

While doing this, it seemed that creating a full "set_feature()" callback
was more work than it needed to be. I can certainly add it, but I thought
I'd show you where I am now first. If you still want the set_feature()
calls, I can add those, but I think it'll make the code less readable.

> Please put the "else" on the same line than the preceding closing curly
> braces.

Done!

Thanks for the review! New patch attached...

-Kees

(and for my own reference, I ran local tests using:
DEB_VENDOR=Debian DPKG_DATADIR=/usr/share/dpkg perl -I scripts 
./scripts/dpkg-buildflags.pl
)

-- 
Kees Cook                                            @debian.org
>From 96f647889ad84ad16cf64ebc63365a7c3238177f Mon Sep 17 00:00:00 2001
From: Kees Cook <k...@outflux.net>
Date: Thu, 8 Dec 2011 15:53:14 -0800
Subject: [PATCH] dpkg-buildflags: provide feature query ability

Since the logic for having a hardening flag enabled or disabled depends
on the architecture, and since the flags may change over time for each
hardening feature, there needs to be a way to externally query the state
of the hardening features. Specifically, lintian needs this to be able
to figure out if a binary package is missing expected hardening features.
Instead of maintaining multiple hard-coded lists of expected hardening
features, this makes dpkg-buildflags the canonical location of the
information, which can be queried by externally. (See bug 650536.)

Signed-off-by: Kees Cook <k...@debian.org>
---
 man/dpkg-buildflags.1         |    8 +++++-
 scripts/Dpkg/BuildFlags.pm    |   27 +++++++++++++++++-
 scripts/Dpkg/Vendor/Debian.pm |   61 +++++++++++++++++++++++++++-------------
 scripts/dpkg-buildflags.pl    |   19 ++++++++++++-
 4 files changed, 92 insertions(+), 23 deletions(-)

diff --git a/man/dpkg-buildflags.1 b/man/dpkg-buildflags.1
index a018edb..7bfbe46 100644
--- a/man/dpkg-buildflags.1
+++ b/man/dpkg-buildflags.1
@@ -87,6 +87,11 @@ the flag is set/modified by a user-specific configuration;
 the flag is set/modified by an environment-specific configuration.
 .RE
 .TP
+.BI \-\-features " area"
+Print the features enabled for a given area. The only currently recognized
+area is \fBhardening\fP. Exits with 0 if the area is known otherwise exits
+with 1.
+.TP
 .B \-\-help
 Show the usage message and exit.
 .TP
@@ -239,7 +244,8 @@ This setting (disabled by default) adds
 .B \-Wl,\-z,now
 to \fBLDFLAGS\fP. During program load, all dynamic symbols are resolved,
 allowing for the entire PLT to be marked read-only (due to \fBrelro\fP
-above).
+above). This option cannot be enabled without \fBrelro\fP being enabled
+at the same time.
 .
 .TP
 .B pie
diff --git a/scripts/Dpkg/BuildFlags.pm b/scripts/Dpkg/BuildFlags.pm
index 6112a9f..09c3b81 100644
--- a/scripts/Dpkg/BuildFlags.pm
+++ b/scripts/Dpkg/BuildFlags.pm
@@ -18,7 +18,7 @@ package Dpkg::BuildFlags;
 use strict;
 use warnings;
 
-our $VERSION = "1.01";
+our $VERSION = "1.02";
 
 use Dpkg::Gettext;
 use Dpkg::BuildOptions;
@@ -68,6 +68,7 @@ sub load_vendor_defaults {
     my ($self) = @_;
     $self->{'options'} = {};
     $self->{'source'} = {};
+    $self->{'features'} = {};
     my $build_opts = Dpkg::BuildOptions->new();
     my $default_flags = $build_opts->has("noopt") ? "-g -O0" : "-g -O2";
     $self->{flags} = {
@@ -306,6 +307,18 @@ sub get {
     return $self->{'flags'}{$key};
 }
 
+=item $bf->get_features($area)
+
+Return, for the given area, a hash with keys as feature names, and values
+as booleans indicating whether the feature is enabled or not.
+
+=cut
+
+sub get_features {
+    my ($self, $key) = @_;
+    return %{$self->{'features'}{$key}};
+}
+
 =item $bf->get_origin($flag)
 
 Return the origin associated to the flag. It might be undef if the
@@ -318,6 +331,18 @@ sub get_origin {
     return $self->{'origin'}{$key};
 }
 
+=item $bf->features($area)
+
+Returns true if the given area is a known, and false otherwise. The only
+recognized area is "hardening".
+
+=cut
+
+sub features {
+    my ($self, $key) = @_;
+    return exists $self->{'features'}{$key};
+}
+
 =item $bf->has($option)
 
 Returns a boolean indicating whether the flags exists in the object.
diff --git a/scripts/Dpkg/Vendor/Debian.pm b/scripts/Dpkg/Vendor/Debian.pm
index e824d0e..f946a99 100644
--- a/scripts/Dpkg/Vendor/Debian.pm
+++ b/scripts/Dpkg/Vendor/Debian.pm
@@ -83,7 +83,7 @@ sub add_hardening_flags {
     my $arch = get_host_arch();
     my ($abi, $os, $cpu) = debarch_to_debtriplet($arch);
 
-    # Decide what's enabled
+    # Features enabled by default for all builds.
     my %use_feature = (
 	"pie" => 0,
 	"stackprotector" => 1,
@@ -92,6 +92,9 @@ sub add_hardening_flags {
 	"relro" => 1,
 	"bindnow" => 0
     );
+    $flags->{'features'}{'hardening'} = \%use_feature;
+
+    # Adjust features based on Maintainer's desires.
     my $opts = Dpkg::BuildOptions->new(envvar => "DEB_BUILD_MAINT_OPTIONS");
     foreach my $feature (split(",", $opts->get("hardening") // "")) {
 	$feature = lc($feature);
@@ -112,43 +115,61 @@ sub add_hardening_flags {
 	}
     }
 
-    # PIE
-    if ($use_feature{"pie"} and
-	$os =~ /^(linux|knetbsd|hurd)$/ and
-	$cpu !~ /^(hppa|m68k|mips|mipsel|avr32)$/) {
-	# Only on linux/knetbsd/hurd (see #430455 and #586215)
+    # Mask features that are not available on certain architectures.
+    if ($os !~ /^(linux|knetbsd|hurd)$/ or
+	$cpu =~ /^(hppa|m68k|mips|mipsel|avr32)$/) {
+	# Disabled on non-linux/knetbsd/hurd (see #430455 and #586215).
 	# Disabled on hppa, m68k (#451192), mips/mipsel (#532821), avr32
-	# (#574716)
-	$flags->append("CFLAGS", "-fPIE");
-	$flags->append("CXXFLAGS", "-fPIE");
-	$flags->append("LDFLAGS", "-fPIE -pie");
+	#  (#574716).
+	$use_feature{"pie"} = 0;
     }
-    # Stack protector
-    if ($use_feature{"stackprotector"} and
-	$cpu !~ /^(ia64|alpha|mips|mipsel|hppa)$/ and $arch ne "arm") {
+    if ($cpu =~ /^(ia64|alpha|mips|mipsel|hppa)$/ or $arch eq "arm") {
 	# Stack protector disabled on ia64, alpha, mips, mipsel, hppa.
 	#   "warning: -fstack-protector not supported for this target"
 	# Stack protector disabled on arm (ok on armel).
 	#   compiler supports it incorrectly (leads to SEGV)
+	$use_feature{"stackprotector"} = 0;
+    }
+    if ($cpu =~ /^(ia64|hppa|avr32)$/) {
+	# relro not implemented on ia64, hppa, avr32.
+	$use_feature{"relro"} = 0;
+    }
+
+    # Handle logical feature interactions.
+    if ($use_feature{"relro"} == 0) {
+	# Disable bindnow if relro is not enabled, since it has no
+	# hardening ability without relro and may incur load penalties.
+	$use_feature{"bindnow"} = 0;
+    }
+
+    # PIE
+    if ($use_feature{"pie"}) {
+	$flags->append("CFLAGS", "-fPIE");
+	$flags->append("CXXFLAGS", "-fPIE");
+	$flags->append("LDFLAGS", "-fPIE -pie");
+    }
+
+    # Stack protector
+    if ($use_feature{"stackprotector"}) {
 	$flags->append("CFLAGS", "-fstack-protector --param=ssp-buffer-size=4");
 	$flags->append("CXXFLAGS", "-fstack-protector --param=ssp-buffer-size=4");
     }
-    # Fortify
+    # Fortify Source
     if ($use_feature{"fortify"}) {
 	$flags->append("CPPFLAGS", "-D_FORTIFY_SOURCE=2");
     }
-    # Format
+
+    # Format Security
     if ($use_feature{"format"}) {
 	$flags->append("CFLAGS", "-Wformat -Wformat-security -Werror=format-security");
 	$flags->append("CXXFLAGS", "-Wformat -Wformat-security -Werror=format-security");
     }
-    # Relro
-    if ($use_feature{"relro"} and $cpu !~ /^(ia64|hppa|avr32)$/) {
+
+    # Read-only Relocations
+    if ($use_feature{"relro"}) {
 	$flags->append("LDFLAGS", "-Wl,-z,relro");
-    } else {
-	# Disable full relro if relro is not enabled.
-	$use_feature{"bindnow"} = 0;
     }
+
     # Bindnow
     if ($use_feature{"bindnow"}) {
 	$flags->append("LDFLAGS", "-Wl,-z,now");
diff --git a/scripts/dpkg-buildflags.pl b/scripts/dpkg-buildflags.pl
index ee33961..cdfd74e 100755
--- a/scripts/dpkg-buildflags.pl
+++ b/scripts/dpkg-buildflags.pl
@@ -47,6 +47,7 @@ Actions:
   --get <flag>       output the requested flag to stdout.
   --origin <flag>    output the origin of the flag to stdout:
                      value is one of vendor, system, user, env.
+  --features <area>  output the enabled features for the given area.
   --list             output a list of the flags supported by the current vendor.
   --export=(sh|make|configure)
                      output something convenient to import the
@@ -62,7 +63,7 @@ my ($param, $action);
 
 while (@ARGV) {
     $_ = shift(@ARGV);
-    if (m/^--(get|origin)$/) {
+    if (m/^--(get|origin|features)$/) {
         usageerr(_g("two commands specified: --%s and --%s"), $1, $action)
             if defined($action);
         $action = $1;
@@ -115,6 +116,22 @@ if ($action eq "get") {
 	print $build_flags->get_origin($param) . "\n";
 	exit(0);
     }
+} elsif ($action eq "features") {
+    if ($build_flags->features($param)) {
+	my %features = $build_flags->get_features($param);
+	my @report;
+	foreach my $feature (sort keys %features) {
+	    my $item = "Feature: $feature\nEnabled: ";
+	    if ($features{$feature} == 1) {
+		$item .= "yes";
+	    } else {
+		$item .= "no";
+	    }
+	    push(@report, $item);
+	}
+	print join("\n\n", @report), "\n";
+	exit(0);
+    }
 } elsif ($action =~ m/^export-(.*)$/) {
     my $export_type = $1;
     foreach my $flag ($build_flags->list()) {
-- 
1.7.5.4

Reply via email to