Просмотр исходного кода

Dpkg::Control::HashCore: Fix OpenPGP Armor Header Line parsing

Cherry picked from commit b4ccfe4982161b8beb44f1d0c98f791c4f238edd.

We should only accept [\r\t ] as trailing whitespace, although RFC4880
does not clarify what whitespace really maps to, we should really match
the GnuPG implementation anyway, as that is what we use to verify the
signatures.

Fixes: CVE-2015-0840
Reported-by: Jann Horn <jann@thejh.net>
Guillem Jover лет назад: 11
Родитель
Сommit
aea291e3db

+ 5 - 0
debian/changelog

@@ -77,6 +77,11 @@ dpkg (1.18.0) UNRELEASED; urgency=low
       Digest modules. Obsolete the 'program' property, and add a 'name' one.
       Digest modules. Obsolete the 'program' property, and add a 'name' one.
     - Add support for $DEFAULT_TEXT_DOMAIN to Dpkg::Gettext, so that the Dpkg
     - Add support for $DEFAULT_TEXT_DOMAIN to Dpkg::Gettext, so that the Dpkg
       perl modules can always produce localized messages.
       perl modules can always produce localized messages.
+    - Fix OpenPGP Armor Header Line parsing in Dpkg::Control::Hash. We should
+      only accept [\r\t ] as trailing whitespace, although RFC4880 does not
+      clarify what whitespace really maps to, we should really match the GnuPG
+      implementation anyway, as that's what we use to verify the signatures.
+      Reported by Jann Horn <jann@thejh.net>. Fixes CVE-2015-0840.
   * Test suite:
   * Test suite:
     - Check perl code compilation, warnings and strictness.
     - Check perl code compilation, warnings and strictness.
     - Fix dpkg-divert unit test to work on BSD «rm -rf» that cannot traverse
     - Fix dpkg-divert unit test to work on BSD «rm -rf» that cannot traverse

+ 12 - 9
scripts/Dpkg/Control/HashCore.pm

@@ -197,8 +197,8 @@ sub parse {
     local $_;
     local $_;
 
 
     while (<$fh>) {
     while (<$fh>) {
-	s/\s*\n$//;
-	next if length == 0 and $paraborder;
+	chomp;
+	next if m/^\s*$/ and $paraborder;
 	next if (m/^#/);
 	next if (m/^#/);
 	$paraborder = 0;
 	$paraborder = 0;
 	if (m/^(\S+?)\s*:\s*(.*)$/) {
 	if (m/^(\S+?)\s*:\s*(.*)$/) {
@@ -212,6 +212,7 @@ sub parse {
 		    $self->parse_error($desc, g_('duplicate field %s found'), $name);
 		    $self->parse_error($desc, g_('duplicate field %s found'), $name);
 		}
 		}
 	    }
 	    }
+	    $value =~ s/\s*$//;
 	    $self->{$name} = $value;
 	    $self->{$name} = $value;
 	    $cf = $name;
 	    $cf = $name;
 	} elsif (m/^\s(\s*\S.*)$/) {
 	} elsif (m/^\s(\s*\S.*)$/) {
@@ -222,8 +223,9 @@ sub parse {
 	    if ($line =~ /^\.+$/) {
 	    if ($line =~ /^\.+$/) {
 		$line = substr $line, 1;
 		$line = substr $line, 1;
 	    }
 	    }
+	    $line =~ s/\s*$//;
 	    $self->{$cf} .= "\n$line";
 	    $self->{$cf} .= "\n$line";
-	} elsif (m/^-----BEGIN PGP SIGNED MESSAGE-----$/) {
+	} elsif (m/^-----BEGIN PGP SIGNED MESSAGE-----[\r\t ]*$/) {
 	    $expect_pgp_sig = 1;
 	    $expect_pgp_sig = 1;
 	    if ($$self->{allow_pgp} and not $parabody) {
 	    if ($$self->{allow_pgp} and not $parabody) {
 		# Skip OpenPGP headers
 		# Skip OpenPGP headers
@@ -233,7 +235,8 @@ sub parse {
 	    } else {
 	    } else {
 		$self->parse_error($desc, g_('OpenPGP signature not allowed here'));
 		$self->parse_error($desc, g_('OpenPGP signature not allowed here'));
 	    }
 	    }
-	} elsif (length == 0 || ($expect_pgp_sig && m/^-----BEGIN PGP SIGNATURE-----$/)) {
+	} elsif (m/^\s*$/ ||
+	         ($expect_pgp_sig && m/^-----BEGIN PGP SIGNATURE-----[\r\t ]*$/)) {
 	    if ($expect_pgp_sig) {
 	    if ($expect_pgp_sig) {
 		# Skip empty lines
 		# Skip empty lines
 		$_ = <$fh> while defined && m/^\s*$/;
 		$_ = <$fh> while defined && m/^\s*$/;
@@ -241,15 +244,15 @@ sub parse {
 		    $self->parse_error($desc, g_('expected OpenPGP signature, ' .
 		    $self->parse_error($desc, g_('expected OpenPGP signature, ' .
 		                                 'found end of file after blank line'));
 		                                 'found end of file after blank line'));
 		}
 		}
-		s/\s*\n$//;
-		unless (m/^-----BEGIN PGP SIGNATURE-----$/) {
+		chomp;
+		unless (m/^-----BEGIN PGP SIGNATURE-----[\r\t ]*$/) {
 		    $self->parse_error($desc, g_('expected OpenPGP signature, ' .
 		    $self->parse_error($desc, g_('expected OpenPGP signature, ' .
-		                                 "found something else '%s'"), $_);
+		                                 "found something else \`%s'"), $_);
                 }
                 }
 		# Skip OpenPGP signature
 		# Skip OpenPGP signature
 		while (<$fh>) {
 		while (<$fh>) {
-		    s/\s*\n$//;
-		    last if m/^-----END PGP SIGNATURE-----$/;
+		    chomp;
+		    last if m/^-----END PGP SIGNATURE-----[\r\t ]*$/;
 		}
 		}
 		unless (defined) {
 		unless (defined) {
 		    $self->parse_error($desc, g_('unfinished OpenPGP signature'));
 		    $self->parse_error($desc, g_('unfinished OpenPGP signature'));

+ 1 - 0
scripts/Makefile.am

@@ -277,6 +277,7 @@ test_data = \
 	t/Dpkg_Control/control-1 \
 	t/Dpkg_Control/control-1 \
 	t/Dpkg_Control/bogus-unsigned.dsc \
 	t/Dpkg_Control/bogus-unsigned.dsc \
 	t/Dpkg_Control/bogus-armor-double.dsc \
 	t/Dpkg_Control/bogus-armor-double.dsc \
+	t/Dpkg_Control/bogus-armor-formfeed.dsc \
 	t/Dpkg_Control/bogus-armor-no-sig.dsc \
 	t/Dpkg_Control/bogus-armor-no-sig.dsc \
 	t/Dpkg_Control/bogus-armor-trail.dsc \
 	t/Dpkg_Control/bogus-armor-trail.dsc \
 	t/Dpkg_Control/bogus-armor-inline.dsc \
 	t/Dpkg_Control/bogus-armor-inline.dsc \

+ 4 - 1
scripts/t/Dpkg_Control.t

@@ -16,7 +16,7 @@
 use strict;
 use strict;
 use warnings;
 use warnings;
 
 
-use Test::More tests => 23;
+use Test::More tests => 24;
 
 
 use IO::String;
 use IO::String;
 
 
@@ -121,6 +121,9 @@ is($dsc, undef, 'Signed .dsc w/ bogus OpenPGP armor trailer');
 $dsc = parse_dsc("$datadir/bogus-armor-inline.dsc");
 $dsc = parse_dsc("$datadir/bogus-armor-inline.dsc");
 is($dsc, undef, 'Signed .dsc w/ bogus OpenPGP inline armor');
 is($dsc, undef, 'Signed .dsc w/ bogus OpenPGP inline armor');
 
 
+$dsc = parse_dsc("$datadir/bogus-armor-formfeed.dsc");
+is($dsc, undef, 'Signed .dsc w/ bogus OpenPGP armor line');
+
 $dsc = parse_dsc("$datadir/bogus-armor-double.dsc");
 $dsc = parse_dsc("$datadir/bogus-armor-double.dsc");
 ok(defined $dsc, 'Signed .dsc w/ two OpenPGP armor signatures');
 ok(defined $dsc, 'Signed .dsc w/ two OpenPGP armor signatures');
 is($dsc->{Source}, 'pass', 'Signed spaced .dsc package name');
 is($dsc->{Source}, 'pass', 'Signed spaced .dsc package name');

+ 19 - 0
scripts/t/Dpkg_Control/bogus-armor-formfeed.dsc

@@ -0,0 +1,19 @@
+-----BEGIN PGP SIGNED MESSAGE-----
+
+Source: fail
+
+-----BEGIN PGP SIGNATURE-----
+Version: vim v7.3.547 (GNU/Linux)
+
+Fake signature here.
+-----END PGP SIGNATURE-----
+-----BEGIN PGP SIGNED MESSAGE-----
+Hash: SHA1
+
+Source: pass
+
+-----BEGIN PGP SIGNATURE
+Version: GnuPG v1.4.12 (GNU/Linux)
+
+Valid signature here.
+-----END PGP SIGNATURE-----