unmerge 286922
found 286905 5.8.8-1
found 286922 5.8.8-1
fixed 286922 5.10.0-1
thanks

As discussed around

 http://www.gossamer-threads.com/lists/perl/porters/233695#233695

CVE-2005-0448 (File::Path::rmtree races) has resurfaced and is present
in all of etch, lenny, and sid.

To be precise, CVE-2005-0448 was about two bugs (#286922 and #286905).
Both of those apply to the etch package, while only #286905 applies to
the lenny/sid package.

I'm unsure if this needs a new CVE id.

For etch, I see no option but to reintroduce the File::Path::rmtree()
implementation by Brendan O'Dea originally introduced in 5.8.4-7. I'm
attaching the diff (debian/patches/03_fix_file_path from 5.8.7-x).
I have informed the security team and they will look at this.

For lenny/sid, #286922 is fixed and the attached patch combined from
File-Path-2.07 and 

 http://www.gossamer-threads.com/lists/perl/porters/233699#233699

should fix #286905. Please note that the above message seems to have
the CVE ids confused; I think CVE-2004-0452 is really fixed for good.

I'll upload 5.10.0-18 with the patch at urgency=high soon, hope it can
make it into lenny without a separate testing-security package.

I'd love more eyeballs on this. I've been able to exploit the issues
with breakpoints in the perl debugger and the patches seem to work.
-- 
Niko Tyni   [EMAIL PROTECTED]
SECURITY [CAN-2005-0448]:

Rewrite File::Path::rmtree to avoid race condition which allows an
attacker with write permission on directories in the tree being
removed to make files setuid or to remove arbitrary files (see
http://bugs.debian.org/286905 and http://bugs.debian.org/286922).

diff -Naur --exclude=debian perl-5.8.7.orig/lib/File/Path.pm 
perl-5.8.7/lib/File/Path.pm
--- perl-5.8.7.orig/lib/File/Path.pm    2005-05-06 02:00:32.000000000 +1000
+++ perl-5.8.7/lib/File/Path.pm 2005-06-02 23:49:34.000000000 +1000
@@ -72,33 +72,17 @@
 
 =item *
 
-a boolean value, which if TRUE will cause C<rmtree> to
-skip any files to which you do not have delete access
-(if running under VMS) or write access (if running
-under another OS).  This will change in the future when
-a criterion for 'delete permission' under OSs other
-than VMS is settled.  (defaults to FALSE)
+a boolean value, which if FALSE (the default for non-root users) will
+cause C<rmtree> to adjust the mode of directories (if required) prior
+to attempting to remove the contents.  Note that on interruption or
+failure of C<rmtree>, directories may be left with more permissive
+modes for the owner.
 
 =back
 
 It returns the number of files successfully deleted.  Symlinks are
 simply deleted and not followed.
 
-B<NOTE:> There are race conditions internal to the implementation of
-C<rmtree> making it unsafe to use on directory trees which may be
-altered or moved while C<rmtree> is running, and in particular on any
-directory trees with any path components or subdirectories potentially
-writable by untrusted users.
-
-Additionally, if the third parameter is not TRUE and C<rmtree> is
-interrupted, it may leave files and directories with permissions altered
-to allow deletion (and older versions of this module would even set
-files and directories to world-read/writable!)
-
-Note also that the occurrence of errors in C<rmtree> can be determined I<only>
-by trapping diagnostic messages using C<$SIG{__WARN__}>; it is not apparent
-from the return value.
-
 =head1 DIAGNOSTICS
 
 =over 4
@@ -124,6 +108,7 @@
 use Exporter ();
 use strict;
 use warnings;
+use Cwd 'getcwd';
 
 our $VERSION = "1.07";
 our @ISA = qw( Exporter );
@@ -172,111 +157,129 @@
     @created;
 }
 
-sub rmtree {
-    my($roots, $verbose, $safe) = @_;
-    my(@files);
-    my($count) = 0;
-    $verbose ||= 0;
-    $safe ||= 0;
-
-    if ( defined($roots) && length($roots) ) {
-      $roots = [$roots] unless ref $roots;
-    }
-    else {
-      carp "No root path(s) specified\n";
-      return 0;
-    }
-
-    my($root);
-    foreach $root (@{$roots}) {
-       if ($Is_MacOS) {
-           $root = ":$root" if $root !~ /:/;
-           $root =~ s#([^:])\z#$1:#;
-       } else {
-           $root =~ s#/\z##;
+sub _rmtree;
+sub _rmtree
+{
+    my ($path, $prefix, $up, $up_dev, $up_ino, $verbose, $safe) = @_;
+
+    my ($dev, $ino) = lstat $path or return 0;
+    unless (-d _)
+    {
+       print "unlink $prefix$path\n" if $verbose;
+       unless (unlink $path)
+       {
+           carp "Can't remove file $prefix$path ($!)";
+           return 0;
        }
-       (undef, undef, my $rp) = lstat $root or next;
-       $rp &= 07777;   # don't forget setuid, setgid, sticky bits
-       if ( -d _ ) {
-           # notabene: 0700 is for making readable in the first place,
-           # it's also intended to change it to writable in case we have
-           # to recurse in which case we are better than rm -rf for 
-           # subtrees with strange permissions
-           chmod($rp | 0700, ($Is_VMS ? VMS::Filespec::fileify($root) : $root))
-             or carp "Can't make directory $root read+writeable: $!"
-               unless $safe;
-
-           if (opendir my $d, $root) {
-               no strict 'refs';
-               if (!defined ${"\cTAINT"} or ${"\cTAINT"}) {
-                   # Blindly untaint dir names
-                   @files = map { /^(.*)$/s ; $1 } readdir $d;
-               } else {
-                   @files = readdir $d;
-               }
-               closedir $d;
-           }
-           else {
-               carp "Can't read $root: $!";
-               @files = ();
-           }
 
-           # Deleting large numbers of files from VMS Files-11 filesystems
-           # is faster if done in reverse ASCIIbetical order 
-           @files = reverse @files if $Is_VMS;
-           ($root = VMS::Filespec::unixify($root)) =~ s#\.dir\z## if $Is_VMS;
-           if ($Is_MacOS) {
-               @files = map("$root$_", @files);
-           } else {
-               @files = map("$root/$_", grep $_!~/^\.{1,2}\z/s,@files);
-           }
-           $count += rmtree([EMAIL PROTECTED],$verbose,$safe);
-           if ($safe &&
-               ($Is_VMS ? !&VMS::Filespec::candelete($root) : !-w $root)) {
-               print "skipped $root\n" if $verbose;
-               next;
-           }
-           chmod $rp | 0700, $root
-             or carp "Can't make directory $root writeable: $!"
-               if $force_writeable;
-           print "rmdir $root\n" if $verbose;
-           if (rmdir $root) {
-               ++$count;
-           }
-           else {
-               carp "Can't remove directory $root: $!";
-               chmod($rp, ($Is_VMS ? VMS::Filespec::fileify($root) : $root))
-                   or carp("and can't restore permissions to "
-                           . sprintf("0%o",$rp) . "\n");
-           }
-       }
-       else { 
-           if ($safe &&
-               ($Is_VMS ? !&VMS::Filespec::candelete($root)
-                        : !(-l $root || -w $root)))
-           {
-               print "skipped $root\n" if $verbose;
-               next;
-           }
-           chmod $rp | 0600, $root
-             or carp "Can't make file $root writeable: $!"
-               if $force_writeable;
-           print "unlink $root\n" if $verbose;
-           # delete all versions under VMS
-           for (;;) {
-               unless (unlink $root) {
-                   carp "Can't unlink file $root: $!";
-                   if ($force_writeable) {
-                       chmod $rp, $root
-                           or carp("and can't restore permissions to "
-                                   . sprintf("0%o",$rp) . "\n");
-                   }
-                   last;
-               }
-               ++$count;
-               last unless $Is_VMS && lstat $root;
-           }
+       return 1;
+    }
+
+    unless (chdir $path)
+    {
+       carp "Can't chdir to $prefix$path ($!)";
+       return 0;
+    }
+
+    # avoid a race condition where a directory may be replaced by a
+    # symlink between the lstat and the chdir
+    my ($new_dev, $new_ino, $perm) = stat '.';
+    unless ("$new_dev:$new_ino" eq "$dev:$ino")
+    {
+       croak "Directory $prefix$path changed before chdir, aborting";
+    }
+
+    $perm &= 07777;
+    my $nperm = $perm | 0700;
+    unless ($safe or $nperm == $perm or chmod $nperm, '.')
+    {
+       carp "Can't make directory $prefix$path read+writeable ($!)";
+       $nperm = $perm;
+    }
+
+    my $count = 0;
+    if (opendir my $dir, '.')
+    {
+       my $entry;
+       while (defined ($entry = readdir $dir))
+       {
+           next if $entry =~ /^\.\.?$/;
+           $entry =~ /^(.*)$/s; $entry = $1; # untaint
+           $count += _rmtree $entry, "$prefix$path/", '..', $dev, $ino,
+               $verbose, $safe;
        }
+
+       closedir $dir;
+    }
+
+    # restore directory permissions if required (in case the rmdir
+    # below fails) now, while we're still in the directory and may do
+    # so without a race via '.'
+    unless ($nperm == $perm or chmod $perm, '.')
+    {
+       carp "Can't restore permissions on directory $prefix$path ($!)";
+    }
+
+    # don't leave the caller in an unexpected directory
+    unless (chdir $up)
+    {
+       croak "Can't return to $up from $prefix$path ($!)";
+    }
+
+    # ensure that a chdir ..  didn't take us somewhere other than
+    # where we expected (see CVE-2002-0435)
+    unless (($new_dev, $new_ino) = stat '.'
+       and "$new_dev:$new_ino" eq "$up_dev:$up_ino")
+    {
+       croak "Previous directory $up changed since entering $prefix$path";
+    }
+
+    print "rmdir $prefix$path\n" if $verbose;
+    if (rmdir $path)
+    {
+       $count++;
+    }
+    else
+    {
+       carp "Can't remove directory $prefix$path ($!)";
+    }
+
+    return $count;
+}
+
+sub rmtree
+{
+    my ($p, $verbose, $safe) = @_;
+    $p = [] unless defined $p and length $p;
+    $p = [ $p ] unless ref $p;
+    my @paths = grep defined && length, @$p;
+
+    # default to "unsafe" for non-root (will chmod dirs)
+    $safe = $> ? 0 : 1 unless defined $safe;
+
+    unless (@paths)
+    {
+       carp "No root path(s) specified";
+       return;
+    }
+
+    my $oldpwd = getcwd or do {
+       carp "Can't fetch initial working directory";
+       return;
+    };
+
+    my ($dev, $ino) = stat '.' or do {
+       carp "Can't stat initial working directory";
+       return;
+    };
+
+    # untaint
+    for ($oldpwd) { /^(.*)$/s; $_ = $1 }
+
+    my $count = 0;
+    for my $path (@paths)
+    {
+       $count += _rmtree $path, '', $oldpwd, $dev, $ino, $verbose, $safe;
     }
 
     $count;
>From b2f78918e3932f7d9ed30ab819b979726afad8d9 Mon Sep 17 00:00:00 2001
From: Niko Tyni <[EMAIL PROTECTED]>
Date: Fri, 21 Nov 2008 00:36:03 +0200
Subject: [PATCH] [SECURITY] CVE-2005-0448 revisited: File::Path::rmtree no 
longer allows creating of setuid files. (Closes: #286905)

This vulnerability was fixed in 5.8.4-7 but re-introduced in 5.8.8-1.
It's also present in File::Path 2.xx, up to and including 2.07 which
has only a partial fix.
---
 lib/File/Path.pm |    8 +++-----
 1 files changed, 3 insertions(+), 5 deletions(-)

diff --git a/lib/File/Path.pm b/lib/File/Path.pm
index f20424d..3c0e417 100644
--- a/lib/File/Path.pm
+++ b/lib/File/Path.pm
@@ -316,10 +316,8 @@ sub _rmtree {
                     print "skipped $root\n" if $arg->{verbose};
                     next ROOT_DIR;
                 }
-                if (!chmod $perm | 0700, $root) {
-                    if ($Force_Writeable) {
-                        _error($arg, "cannot make directory writeable", 
$canon);
-                    }
+                if ($Force_Writeable && !chmod $perm | 0700, $root) {
+                    _error($arg, "cannot make directory writeable", $canon);
                 }
                 print "rmdir $root\n" if $arg->{verbose};
                 if (rmdir $root) {
@@ -328,7 +326,7 @@ sub _rmtree {
                 }
                 else {
                     _error($arg, "cannot remove directory", $canon);
-                    if (!chmod($perm, ($Is_VMS ? VMS::Filespec::fileify($root) 
: $root))
+                    if ($Force_Writeable && !chmod($perm, ($Is_VMS ? 
VMS::Filespec::fileify($root) : $root))
                     ) {
                         _error($arg, sprintf("cannot restore permissions to 
0%o",$perm), $canon);
                     }
-- 
1.5.6.5

Reply via email to