[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

[SCM] Debian package checker branch, master, updated. 2.5.11-50-gd75e4f9



The following commit has been merged in the master branch:
commit d75e4f9aafd211f51882b77026bcbcfc7b392911
Author: Niels Thykier <niels@thykier.net>
Date:   Tue Dec 18 21:04:21 2012 +0100

    L::Util: Be very strict around PGP markers
    
    Be very strict in parsing and handling of PGP headers.  Particularly,
    reject any file with a "suspicious" use of PGP messages (like multiple
    signed messages).
    
    Signed-off-by: Niels Thykier <niels@thykier.net>

diff --git a/debian/changelog b/debian/changelog
index c30fcaa..eb8bd0c 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -57,6 +57,10 @@ lintian (2.5.12) UNRELEASED; urgency=low
   * lib/Lintian/Relation/Version.pm:
     + [NT] Add and export "versions_comparator" that can be used for
       sorting purposes.
+  * lib/Lintian/Util.pm:
+    + [NT] Reject partially signed Deb822 files.  Most Deb822 files
+      are not signed at all; but those that are should be completely
+      covered by a signature.  (Closes: #696230)
 
   * reporting/config:
     + [JP] Remove unused $GRAPH_DIR configuration option.
diff --git a/frontend/lintian b/frontend/lintian
index cc5e057..b7694a7 100755
--- a/frontend/lintian
+++ b/frontend/lintian
@@ -915,7 +915,13 @@ while (my $arg = shift) {
     # file?
     if (-f $arg) {
         if ($arg =~ m/\.(?:u?deb|dsc|changes)$/o){
-            $pool->add_file($arg);
+            eval {
+                $pool->add_file($arg);
+            };
+            if ($@) {
+                print STDERR "Skipping $arg: $@";
+                $exit_code = 2;
+            }
         } else {
             fail("bad package file name $arg (neither .deb, .udeb, .changes or .dsc file)");
         }
diff --git a/lib/Lintian/Util.pm b/lib/Lintian/Util.pm
index a12d93a..de61c56 100644
--- a/lib/Lintian/Util.pm
+++ b/lib/Lintian/Util.pm
@@ -242,8 +242,14 @@ refer to L</CONSTANTS> for the list of constants and their meaning.
 The default value for FLAGS is 0.
 
 If the file is empty (i.e. it contains no paragraphs), the method will
-contain an I<empty> list.  Lines looking like a GPG-signature is
-ignored when parsing the file.
+contain an I<empty> list.  The deb822 contents may be inside a
+I<signed> PGP message with a signature.
+
+visit_dpkg_paragraph will require the PGP headers to be correct (if
+present) and require that the entire file is covered by the signature.
+However, it will I<not> validate the signature (in fact, the contents
+of the PGP SIGNATURE part can be empty).  The signature should be
+validated separatedly.
 
 visit_dpkg_paragraph will pass paragraphs to CODE as they are
 completed.  If CODE can process the paragraphs as they are seen, very
@@ -310,6 +316,45 @@ underscores.
 
 A comment line appeared and FLAGS contained DCTRL_NO_COMMENTS.
 
+=item PGP signature seen before start of signed message
+
+A "BEGIN PGP SIGNATURE" header is seen and a "BEGIN PGP MESSAGE" has
+not been seen yet.
+
+=item Two PGP signatures (first one at line %d)
+
+Two "BEGIN PGP SIGNATURE" headers are seen in the same file.
+
+=item Unexpected %s header
+
+A valid PGP header appears (e.g. "BEGIN PUBLIC KEY BLOCK").
+
+=item Malformed PGP header
+
+An invalid or malformed PGP header appears.
+
+=item Expected at most one signed message (previous at line %d)
+
+Two "BEGIN PGP MESSAGE" headers appears in the same message.
+
+=item End of file but expected a "END PGP SIGNATURE" header
+
+The file ended after a "BEGIN PGP SIGNATURE" header without being
+followed by a "END PGP SIGNATURE".
+
+=item PGP MESSAGE header must be first content if present
+
+The file had content before PGP MESSAGE.
+
+=item Data after the PGP SIGNATURE
+
+The file had data after the PGP SIGNATURE block ended.
+
+=item End of file before "BEGIN PGP SIGNATURE"
+
+The file had a "BEGIN PGP MESSAGE" header, but no signature was
+present.
+
 =back
 
 =cut
@@ -322,13 +367,13 @@ sub visit_dpkg_paragraph {
     my $open_section = 0;
     my $last_tag;
     my $debconf = $flags & DCTRL_DEBCONF_TEMPLATE;
+    my $signed = 0;
+    my $signature = 0;
 
     local $_;
     while (<$CONTROL>) {
         chomp;
 
-        # FIXME: comment lines are only allowed in debian/control and should
-        # be an error for other control files.
         if (/^\#/) {
             next unless $flags & DCTRL_NO_COMMENTS;
             die "syntax error at line $.: Comments are not allowed.\n";
@@ -343,18 +388,101 @@ sub visit_dpkg_paragraph {
                 $open_section = 0;
             }
         }
-        # pgp sig?
-        elsif (m/^-----BEGIN PGP SIGNATURE/) { # skip until end of signature
+        # pgp sig? Be strict here (due to #696230)
+        # According to http://tools.ietf.org/html/rfc4880#section-6.2
+        # The header MUST start at the beginning of the line and MUST NOT have
+        # any other text (except whitespace) after the header.
+        elsif (m/^-----BEGIN PGP SIGNATURE-----\s*$/) { # skip until end of signature
+            my $saw_end = 0;
+            if (not $signed or $signature) {
+                die "syntax error at line $.: PGP signature seen before start of signed message\n"
+                    if not $signed;
+                die "syntax error at line $.: Two PGP signatures (first one at line $signature)\n";
+            }
+            $signature = $.;
             while (<$CONTROL>) {
-                last if m/^-----END PGP SIGNATURE/o;
+                if (m/^-----END PGP SIGNATURE-----\s*$/o) {
+                    $saw_end = 1;
+                    last;
+                }
             }
+            # The "at line X" may seem a little weird, but it keeps the
+            # message format identical.
+            die "syntax error at line $.: End of file but expected a \"END PGP SIGNATURE\" header\n"
+                unless $saw_end;
         }
         # other pgp control?
-        elsif (m/^-----BEGIN PGP/) { # skip until the next blank line
+        elsif (m/^-----(?:BEGIN|END) PGP/) {
+            # At this point it could be a malformed PGP header or one
+            # of the following valid headers (RFC4880):
+            #  * BEGIN PGP MESSAGE
+            #    - Possibly a signed Debian CTRL, so okay (for now)
+            #  * BEGIN PGP {PUBLIC,PRIVATE} KEY BLOCK
+            #    - Valid header, but not a Debian CTRL file.
+            #  * BEGIN PGP MESSAGE, PART X{,/Y}
+            #    - Valid, but we don't support partial messages, so
+            #      bail on those.
+
+            unless (m/^-----BEGIN PGP SIGNED MESSAGE-----\s*$/) {
+                # Not a (full) PGP MESSAGE; reject.
+
+                my $key = qr/(?:BEGIN|END) PGP (?:PUBLIC|PRIVATE) KEY BLOCK/;
+                my $msgpart = qr{BEGIN PGP MESSAGE, PART \d+(?:/\d+)?};
+                my $msg = qr/(?:BEGIN|END) PGP (?:(?:COMPRESSED|ENCRYTPED) )?MESSAGE/;
+
+                if (m/^-----($key|$msgpart|$msg)-----\s*$/o) {
+                    die "syntax error at line $.: Unexpected $1 header\n";
+                } else {
+                    die "syntax error at line $.: Malformed PGP header\n";
+                }
+            } else {
+                if ($signed) {
+                    die "syntax error at line $.: Expected at most one signed message" .
+                        " (previous at line $signed)\n"
+                }
+                if ($sline > -1) {
+                    # NB: If you remove this, keep in mind that it may allow two paragraphs to
+                    # merge.  Consider:
+                    #
+                    # Field-P1: some-value
+                    # -----BEGIN PGP SIGANTURE----
+                    #
+                    # Field-P2: another value
+                    #
+                    # At the time of writing: If $open_section is
+                    # true, it will remain so until the empty line
+                    # after the PGP header.
+                    die "syntax error at line $.: PGP MESSAGE header must be first" .
+                        " content if present\n";
+                }
+                $signed = $.;
+            }
+
+            # skip until the next blank line
             while (<$CONTROL>) {
                 last if /^\s*$/o;
             }
         }
+        # did we see a signature already?  We allow all whitespace/comment lines
+        # outside the signature.
+        elsif ($signature) {
+            # Accept empty lines after the signature.
+            next if m/^\s*$/;
+
+            #NB: If you remove this, keep in mind that it may allow two paragraphs to
+            # merge.  Consider:
+            #
+            # Field-P1: some-value
+            # -----BEGIN PGP SIGANTURE----
+            # [...]
+            # -----END PGP SIGANTURE----
+            # Field-P2: another value
+            #
+            # At the time of writing: If $open_section is
+            # true, it will remain so until the empty line
+            # after the PGP header.
+            die "syntax error at line $.: Data after the PGP SIGNATURE\n";
+        }
         # new empty field?
         elsif (m/^([^: \t]+):\s*$/o) {
             $sline = $. if not $open_section;
@@ -410,6 +538,14 @@ sub visit_dpkg_paragraph {
     }
     # pass the last section (if not already done).
     $code->($section, $sline) if $open_section;
+
+    # Given the API, we cannot use this check to prevent any paragraphs from being
+    # emitted to the code argument, so we might as well just do this last.
+    if ($signed and not $signature) {
+        # The "at line X" may seem a little weird, but it keeps the
+        # message format identical.
+        die "syntax error at line $.: End of file before \"BEGIN PGP SIGNATURE\"\n";
+    }
 }
 
 =item read_dpkg_control (FILE[, FLAGS[, LINES]])
diff --git a/t/scripts/Lintian/Util/data/pgp-eof-missing-sign b/t/scripts/Lintian/Util/data/pgp-eof-missing-sign
new file mode 100644
index 0000000..78e5ee9
--- /dev/null
+++ b/t/scripts/Lintian/Util/data/pgp-eof-missing-sign
@@ -0,0 +1,5 @@
+-----BEGIN PGP SIGNED MESSAGE-----
+
+Package: lintian
+
+# Missing signature block
diff --git a/t/scripts/Lintian/Util/data/pgp-leading-unsigned b/t/scripts/Lintian/Util/data/pgp-leading-unsigned
new file mode 100644
index 0000000..0b6b949
--- /dev/null
+++ b/t/scripts/Lintian/Util/data/pgp-leading-unsigned
@@ -0,0 +1,14 @@
+Package: dpkg
+
+# Unsigned above, signed below (bad)
+
+-----BEGIN PGP SIGNED MESSAGE-----
+
+Package: lintian
+
+-----BEGIN PGP SIGNATURE-----
+
+Some signature.
+
+-----END PGP SIGNATURE-----
+
diff --git a/t/scripts/Lintian/Util/data/pgp-malformed-header b/t/scripts/Lintian/Util/data/pgp-malformed-header
new file mode 100644
index 0000000..f83fb77
--- /dev/null
+++ b/t/scripts/Lintian/Util/data/pgp-malformed-header
@@ -0,0 +1,11 @@
+# Missing a dash in the end
+
+-----BEGIN PGP SIGNED MESSAGE----
+
+Package: linitan
+
+-----BEGIN PGP SIGNATURE----
+
+Some signature.
+
+-----END PGP SIGNATURE----
diff --git a/t/scripts/Lintian/Util/data/pgp-no-end-pgp-header b/t/scripts/Lintian/Util/data/pgp-no-end-pgp-header
new file mode 100644
index 0000000..6d15d98
--- /dev/null
+++ b/t/scripts/Lintian/Util/data/pgp-no-end-pgp-header
@@ -0,0 +1,7 @@
+-----BEGIN PGP SIGNED MESSAGE-----
+
+Package: lintian
+
+-----BEGIN PGP SIGNATURE-----
+
+Some signature, missing an end marker.
diff --git a/t/scripts/Lintian/Util/data/pgp-sig-before-start b/t/scripts/Lintian/Util/data/pgp-sig-before-start
new file mode 100644
index 0000000..1d7cf12
--- /dev/null
+++ b/t/scripts/Lintian/Util/data/pgp-sig-before-start
@@ -0,0 +1,7 @@
+Package: lintian
+
+-----BEGIN PGP SIGNATURE-----
+
+Some signature.
+
+-----END PGP SIGNATURE-----
diff --git a/t/scripts/Lintian/Util/data/pgp-trailing-unsigned b/t/scripts/Lintian/Util/data/pgp-trailing-unsigned
new file mode 100644
index 0000000..1d29d87
--- /dev/null
+++ b/t/scripts/Lintian/Util/data/pgp-trailing-unsigned
@@ -0,0 +1,14 @@
+-----BEGIN PGP SIGNED MESSAGE-----
+
+Package: lintian
+
+-----BEGIN PGP SIGNATURE-----
+
+Some signature.
+
+-----END PGP SIGNATURE-----
+
+# Signed above, unsigned below (bad)
+
+Package: dpkg
+
diff --git a/t/scripts/Lintian/Util/data/pgp-two-signatures b/t/scripts/Lintian/Util/data/pgp-two-signatures
new file mode 100644
index 0000000..0c0b7d6
--- /dev/null
+++ b/t/scripts/Lintian/Util/data/pgp-two-signatures
@@ -0,0 +1,16 @@
+-----BEGIN PGP SIGNED MESSAGE-----
+
+Package: lintian
+
+-----BEGIN PGP SIGNATURE-----
+
+Some signature.
+
+-----END PGP SIGNATURE-----
+
+-----BEGIN PGP SIGNATURE-----
+
+Another signature.
+
+-----END PGP SIGNATURE-----
+
diff --git a/t/scripts/Lintian/Util/data/pgp-two-signed-msgs b/t/scripts/Lintian/Util/data/pgp-two-signed-msgs
new file mode 100644
index 0000000..c8fcf9d
--- /dev/null
+++ b/t/scripts/Lintian/Util/data/pgp-two-signed-msgs
@@ -0,0 +1,19 @@
+-----BEGIN PGP SIGNED MESSAGE-----
+
+Package: lintian
+
+-----BEGIN PGP SIGNATURE-----
+
+Some signature.
+
+-----END PGP SIGNATURE-----
+
+-----BEGIN PGP SIGNED MESSAGE-----
+
+Package: dpkg
+
+-----BEGIN PGP SIGNATURE-----
+
+Aother signature.
+
+-----END PGP SIGNATURE-----
diff --git a/t/scripts/Lintian/Util/data/pgp-unexpected-header b/t/scripts/Lintian/Util/data/pgp-unexpected-header
new file mode 100644
index 0000000..743ac85
--- /dev/null
+++ b/t/scripts/Lintian/Util/data/pgp-unexpected-header
@@ -0,0 +1,6 @@
+-----BEGIN PGP MESSAGE-----
+
+We are expecting a "SIGNED" message.
+
+-----END PGP MESSAGE-----
+
diff --git a/t/scripts/Lintian/Util/dctrl-parser.t b/t/scripts/Lintian/Util/dctrl-parser.t
new file mode 100644
index 0000000..7dd5a5f
--- /dev/null
+++ b/t/scripts/Lintian/Util/dctrl-parser.t
@@ -0,0 +1,52 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More;
+
+use Lintian::Util qw(visit_dpkg_paragraph);
+
+my $syntax_error = qr/syntax error at line \d+/;
+my %TESTS_BAD = (
+    'pgp-sig-before-start' => qr/${syntax_error}: PGP signature seen before start of signed message/,
+    'pgp-two-signatures' => qr/${syntax_error}: Two PGP signatures \(first one at line \d+\)/,
+    'pgp-unexpected-header' => qr/${syntax_error}: Unexpected .+ header/,
+    'pgp-malformed-header' => qr/${syntax_error}: Malformed PGP header/,
+    'pgp-two-signed-msgs' => qr/${syntax_error}: Expected at most one signed message \(previous at line \d+\)/,
+    'pgp-no-end-pgp-header' => qr/${syntax_error}: End of file but expected a "END PGP SIGNATURE" header/,
+    'pgp-leading-unsigned' => qr/${syntax_error}: PGP MESSAGE header must be first content if present/,
+    'pgp-trailing-unsigned' => qr/${syntax_error}: Data after the PGP SIGNATURE/,
+    'pgp-eof-missing-sign' => qr/${syntax_error}: End of file before "BEGIN PGP SIGNATURE"/,
+);
+
+my $DATADIR = $0;
+$DATADIR =~ s,[^/]+$,,o;
+if ($DATADIR) {
+    # invokved in some other dir
+    $DATADIR = "$DATADIR/data";
+} else {
+    # current dir
+    $DATADIR = 'data';
+}
+
+plan skip_all => 'Data files not available'
+    unless -d $DATADIR;
+
+plan tests => scalar keys %TESTS_BAD;
+
+foreach my $filename (sort keys %TESTS_BAD) {
+    my $fail_regex = $TESTS_BAD{$filename};
+    my $path = "$DATADIR/$filename";
+    open my $fd, '<', $path or die "open $path: $!";
+    eval {
+        visit_dpkg_paragraph (sub {}, $fd);
+    };
+    close $fd;
+    if (my $err = $@) {
+        like ($err, $fail_regex, $filename);
+    } else {
+        fail ("$path was parsed successfully");
+    }
+}
+

-- 
Debian package checker


Reply to: