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