[Reproducible-commits] [dpkg] 01/02: Fix multiple security issues with dpkg-source (CVE-2010-1679)

Holger Levsen holger at layer-acht.org
Tue May 3 08:43:07 UTC 2016


This is an automated email from the git hooks/post-receive script.

holger pushed a commit to annotated tag 1.14.31
in repository dpkg.

commit 5674cb9ff0f041e54375c0fdc7d5e765cdecd60c
Author: Raphaël Hertzog <hertzog at debian.org>
Date:   Mon Jan 3 10:01:37 2011 +0100

    Fix multiple security issues with dpkg-source (CVE-2010-1679)
    
    - Enhance checks to catch maliciously crafted patches which could modify
      files outside of the unpacked source package.
    - Do not consider a top-level symlink like a directory when
      extracting a tarball.
    - Exclude .pc while extracting the upstream tarball in 3.0 (quilt)
      as patch blindly writes in that directory during unpack (and
      would follow any existing symlink).
---
 debian/changelog                  | 13 ++++++
 scripts/Dpkg/Source/Archive.pm    |  2 +-
 scripts/Dpkg/Source/Package/V2.pm |  7 ++-
 scripts/Dpkg/Source/Patch.pm      | 98 +++++++++++++++++++++++++--------------
 4 files changed, 83 insertions(+), 37 deletions(-)

diff --git a/debian/changelog b/debian/changelog
index d1763b9..d7b5b5d 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,16 @@
+dpkg (1.14.31) stable-security; urgency=low
+
+  * Fix multiple security issues with dpkg-source (CVE-2010-1679):
+    - Enhance checks to catch maliciously crafted patches which could modify
+      files outside of the unpacked source package.
+    - Do not consider a top-level symlink like a directory when
+      extracting a tarball.
+    - Exclude .pc while extracting the upstream tarball in 3.0 (quilt)
+      as patch blindly writes in that directory during unpack (and would
+      follow any existing symlink).
+
+ -- Raphaël Hertzog <hertzog at debian.org>  Wed, 05 Jan 2011 10:58:17 +0100
+
 dpkg (1.14.30) stable; urgency=low
 
   * Fix dpkg to not lose package metadata on filesystems where readdir()
diff --git a/scripts/Dpkg/Source/Archive.pm b/scripts/Dpkg/Source/Archive.pm
index 04d1c2d..a5b4f31 100644
--- a/scripts/Dpkg/Source/Archive.pm
+++ b/scripts/Dpkg/Source/Archive.pm
@@ -140,7 +140,7 @@ sub extract {
     closedir(D);
     my $done = 0;
     erasedir($dest);
-    if (scalar(@entries) == 1 && -d "$tmp/$entries[0]") {
+    if (scalar(@entries) == 1 && ! -l "$tmp/$entries[0]" && -d _) {
 	rename("$tmp/$entries[0]", $dest) ||
 		syserr(_g("Unable to rename %s to %s"),
 		       "$tmp/$entries[0]", $dest);
diff --git a/scripts/Dpkg/Source/Package/V2.pm b/scripts/Dpkg/Source/Package/V2.pm
index e7fb9d8..b5467f2 100644
--- a/scripts/Dpkg/Source/Package/V2.pm
+++ b/scripts/Dpkg/Source/Package/V2.pm
@@ -115,7 +115,12 @@ sub do_extract {
     # Extract main tarball
     info(_g("unpacking %s"), $tarfile);
     my $tar = Dpkg::Source::Archive->new(filename => "$dscdir$tarfile");
-    $tar->extract($newdirectory, no_fixperms => 1);
+    $tar->extract($newdirectory, no_fixperms => 1,
+                  options => [ "--anchored", "--no-wildcards-match-slash",
+                               "--exclude", "*/.pc", "--exclude", ".pc" ]);
+    # The .pc exclusion is only needed for 3.0 (quilt) and to avoid
+    # having an upstream tarball provide a directory with symlinks
+    # that would be blindly followed when applying the patches
 
     # Extract additional orig tarballs
     foreach my $subdir (keys %origtar) {
diff --git a/scripts/Dpkg/Source/Patch.pm b/scripts/Dpkg/Source/Patch.pm
index 079440a..865bd4a 100644
--- a/scripts/Dpkg/Source/Patch.pm
+++ b/scripts/Dpkg/Source/Patch.pm
@@ -301,11 +301,41 @@ sub analyze {
         $header =~ s/\s.*// unless ($header =~ s/\t.*//);
         return $header;
     }
+    sub intuit_file_patched {
+	my ($old, $new) = @_;
+	return $new unless defined $old;
+	return $old unless defined $new;
+	return $new if -e $new and not -e $old;
+	return $old if -e $old and not -e $new;
+	# We don't consider the case where both files are non-existent and
+	# where patch picks the one with the fewest directories to create
+	# since dpkg-source will pre-create the required directories
+	#
+	# Precalculate metrics used by patch
+	my ($tmp_o, $tmp_n) = ($old, $new);
+	my ($len_o, $len_n) = (length($old), length($new));
+	$tmp_o =~ s{[/\\]+}{/}g;
+	$tmp_n =~ s{[/\\]+}{/}g;
+	my $nb_comp_o = ($tmp_o =~ tr{/}{/});
+	my $nb_comp_n = ($tmp_n =~ tr{/}{/});
+	$tmp_o =~ s{^.*/}{};
+	$tmp_n =~ s{^.*/}{};
+	my ($blen_o, $blen_n) = (length($tmp_o), length($tmp_n));
+	# Decide like patch would
+	if ($nb_comp_o != $nb_comp_n) {
+	    return ($nb_comp_o < $nb_comp_n) ? $old : $new;
+	} elsif ($blen_o != $blen_n) {
+	    return ($blen_o < $blen_n) ? $old : $new;
+	} elsif ($len_o != $len_n) {
+	    return ($len_o < $len_n) ? $old : $new;
+	}
+	return $old;
+    }
     $_ = getline($diff_handle);
 
   HUNK:
     while (defined($_) || not eof($diff_handle)) {
-	my ($fn, $fn2);
+	my (%path, %fn);
 	# skip comments leading up to patch (if any)
 	until (/^--- /) {
 	    last HUNK if not defined($_ = getline($diff_handle));
@@ -315,11 +345,8 @@ sub analyze {
 	unless(s/^--- //) {
 	    error(_g("expected ^--- in line %d of diff `%s'"), $., $diff);
 	}
-        $_ = strip_ts($_);
-        if ($_ eq '/dev/null' or s{^[^/]+/}{$destdir/}) {
-            $fn = $_;
-	    error(_g("%s contains an insecure path: %s"), $diff, $_) if m{/\.\./};
-        }
+        $path{'old'} = $_ = strip_ts($_);
+	$fn{'old'} = $_ if $_ ne '/dev/null' and s{^[^/]*/+}{$destdir/};
 	if (/\.dpkg-orig$/) {
 	    error(_g("diff `%s' patches file with name ending .dpkg-orig"), $diff);
 	}
@@ -330,46 +357,47 @@ sub analyze {
 	unless (s/^\+\+\+ //) {
 	    error(_g("line after --- isn't as expected in diff `%s' (line %d)"), $diff, $.);
 	}
-        $_ = strip_ts($_);
-        if ($_ eq '/dev/null' or s{^[^/]+/}{$destdir/}) {
-            $fn2 = $_;
-	    error(_g("%s contains an insecure path: %s"), $diff, $_) if m{/\.\./};
-        } else {
-            unless (defined $fn) {
-                error(_g("none of the filenames in ---/+++ are relative in diff `%s' (line %d)"),
-                      $diff, $.);
-            }
-        }
+        $path{'new'} = $_ = strip_ts($_);
+	$fn{'new'} = $_ if $_ ne '/dev/null' and s{^[^/]*/+}{$destdir/};
+
+	unless (defined $fn{'old'} or defined $fn{'new'}) {
+	    error(_g("none of the filenames in ---/+++ are relative in diff '%s' (line %d)"),
+		  $diff, $.);
+	}
 
-        if (defined($fn) and $fn eq '/dev/null') {
+	# Safety checks on both filenames that patch could use
+	foreach my $key ("old", "new") {
+	    next unless defined $fn{$key};
+	    if ($path{$key} =~ m{(^|/)\.\./}) {
+		error(_g("%s contains an insecure path: %s"), $diff, $path{$key});
+	    }
+	    my $path = $fn{$key};
+	    while (1) {
+		if (-l $path) {
+		    error(_g("diff %s modifies file %s through a symlink: %s"),
+			  $diff, $fn{$key}, $path);
+		}
+		last unless $path =~ s{/+[^/]*$}{};
+		last if length($path) <= length($destdir); # $destdir is assumed safe
+	    }
+	}
+
+        if ($path{'old'} eq '/dev/null' and $path{'new'} eq '/dev/null') {
             error(_g("original and modified files are /dev/null in diff `%s' (line %d)"),
-                  $diff, $.) if (defined($fn2) and $fn2 eq '/dev/null');
-            $fn = $fn2;
-        } elsif (defined($fn2) and $fn2 ne '/dev/null') {
-            $fn = $fn2 unless defined $fn;
-            $fn = $fn2 if ((not -e $fn) and -e $fn2);
-        } elsif (defined($fn2) and $fn2 eq '/dev/null') {
+                  $diff, $.);
+        } elsif ($path{'new'} eq '/dev/null') {
             error(_g("file removal without proper filename in diff `%s' (line %d)"),
-                  $diff, $. - 1) unless defined $fn;
+                  $diff, $. - 1) unless defined $fn{'old'};
             warning(_g("diff %s removes a non-existing file %s (line %d)"),
-                    $diff, $fn, $.) unless -e $fn;
+                    $diff, $fn{'old'}, $.) unless -e $fn{'old'};
         }
+	my $fn = intuit_file_patched($fn{'old'}, $fn{'new'});
 
 	my $dirname = $fn;
 	if ($dirname =~ s{/[^/]+$}{} && not -d $dirname) {
 	    $dirtocreate{$dirname} = 1;
 	}
 
-	# Sanity check, refuse to patch through a symlink
-	$dirname = $fn;
-	while (1) {
-	    if (-l $dirname) {
-		error(_g("diff %s modifies file %s through a symlink: %s"),
-		      $diff, $fn, $dirname);
-	    }
-	    last unless $dirname =~ s{/[^/]+$}{};
-	}
-
 	if (-e $fn and not -f _) {
 	    error(_g("diff `%s' patches something which is not a plain file"), $diff);
 	}

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/reproducible/dpkg.git



More information about the Reproducible-commits mailing list