217608
From e9cc25a6109e9191bcbf59a967ed6c60b0156f72 Mon Sep 17 00:00:00 2001
217608
From: John Lightsey <john@nixnuts.net>
217608
Date: Tue, 2 May 2017 12:03:52 -0500
217608
Subject: [PATCH] Prevent directory chmod race attack.
217608
MIME-Version: 1.0
217608
Content-Type: text/plain; charset=UTF-8
217608
Content-Transfer-Encoding: 8bit
217608
217608
CVE-2017-6512 is a race condition attack where the chmod() of directories
217608
that cannot be entered is misused to change the permissions on other
217608
files or directories on the system. This has been corrected by limiting
217608
the directory-permission loosening logic to systems where fchmod() is
217608
supported.
217608
217608
Petr Písař: Ported to 2.12.
217608
217608
Signed-off-by: Petr Písař <ppisar@redhat.com>
217608
---
217608
 lib/File/Path.pm | 39 +++++++++++++++++++++++++--------------
217608
 t/Path.t         | 40 ++++++++++++++++++++++++++--------------
217608
 2 files changed, 51 insertions(+), 28 deletions(-)
217608
217608
diff --git a/lib/File/Path.pm b/lib/File/Path.pm
217608
index 36f12cc..871f43a 100644
217608
--- a/lib/File/Path.pm
217608
+++ b/lib/File/Path.pm
217608
@@ -354,21 +354,32 @@ sub _rmtree {
217608
 
217608
                 # see if we can escalate privileges to get in
217608
                 # (e.g. funny protection mask such as -w- instead of rwx)
217608
-                $perm &= oct '7777';
217608
-                my $nperm = $perm | oct '700';
217608
-                if (
217608
-                    !(
217608
-                           $arg->{safe}
217608
-                        or $nperm == $perm
217608
-                        or chmod( $nperm, $root )
217608
-                    )
217608
-                  )
217608
-                {
217608
-                    _error( $arg,
217608
-                        "cannot make child directory read-write-exec", $canon );
217608
-                    next ROOT_DIR;
217608
+                # This uses fchmod to avoid traversing outside of the proper
217608
+                # location (CVE-2017-6512)
217608
+                my $root_fh;
217608
+                if (open($root_fh, '<', $root)) {
217608
+                    my ($fh_dev, $fh_inode) = (stat $root_fh )[0,1];
217608
+                    $perm &= oct '7777';
217608
+                    my $nperm = $perm | oct '700';
217608
+                    local $@;
217608
+                    if (
217608
+                        !(
217608
+                            $arg->{safe}
217608
+                           or $nperm == $perm
217608
+                           or !-d _
217608
+                           or $fh_dev ne $ldev
217608
+                           or $fh_inode ne $lino
217608
+                           or eval { chmod( $nperm, $root_fh ) }
217608
+                        )
217608
+                      )
217608
+                    {
217608
+                        _error( $arg,
217608
+                            "cannot make child directory read-write-exec", $canon );
217608
+                        next ROOT_DIR;
217608
+                    }
217608
+                    close $root_fh;
217608
                 }
217608
-                elsif ( !chdir($root) ) {
217608
+                if ( !chdir($root) ) {
217608
                     _error( $arg, "cannot chdir to child", $canon );
217608
                     next ROOT_DIR;
217608
                 }
217608
diff --git a/t/Path.t b/t/Path.t
217608
index 5644f57..fffc49c 100755
217608
--- a/t/Path.t
217608
+++ b/t/Path.t
217608
@@ -3,7 +3,7 @@
217608
 
217608
 use strict;
217608
 
217608
-use Test::More tests => 127;
217608
+use Test::More tests => 126;
217608
 use Config;
217608
 use Fcntl ':mode';
217608
 use lib 't/';
217608
@@ -17,6 +17,13 @@ BEGIN {
217608
 
217608
 my $Is_VMS = $^O eq 'VMS';
217608
 
217608
+my $fchmod_supported = 0;
217608
+if (open my $fh, curdir()) {
217608
+    my ($perm) = (stat($fh))[2];
217608
+    $perm &= 07777;
217608
+    eval { $fchmod_supported = chmod( $perm, $fh); };
217608
+}
217608
+
217608
 # first check for stupid permissions second for full, so we clean up
217608
 # behind ourselves
217608
 for my $perm (0111,0777) {
217608
@@ -298,16 +305,19 @@ is($created[0], $dir, "created directory (old style 3 mode undef) cross-check");
217608
 
217608
 is(rmtree($dir, 0, undef), 1, "removed directory 3 verbose undef");
217608
 
217608
-$dir = catdir($tmp_base,'G');
217608
-$dir = VMS::Filespec::unixify($dir) if $Is_VMS;
217608
+SKIP: {
217608
+    skip "fchmod of directories not supported on this platform", 3 unless $fchmod_supported;
217608
+    $dir = catdir($tmp_base,'G');
217608
+    $dir = VMS::Filespec::unixify($dir) if $Is_VMS;
217608
 
217608
-@created = mkpath($dir, undef, 0200);
217608
+    @created = mkpath($dir, undef, 0400);
217608
 
217608
-is(scalar(@created), 1, "created write-only dir");
217608
+    is(scalar(@created), 1, "created read-only dir");
217608
 
217608
-is($created[0], $dir, "created write-only directory cross-check");
217608
+    is($created[0], $dir, "created read-only directory cross-check");
217608
 
217608
-is(rmtree($dir), 1, "removed write-only dir");
217608
+    is(rmtree($dir), 1, "removed read-only dir");
217608
+}
217608
 
217608
 # borderline new-style heuristics
217608
 if (chdir $tmp_base) {
217608
@@ -449,26 +459,28 @@ SKIP: {
217608
 }
217608
 
217608
 SKIP : {
217608
-    my $skip_count = 19;
217608
+    my $skip_count = 18;
217608
     # this test will fail on Windows, as per:
217608
     #   http://perldoc.perl.org/perlport.html#chmod
217608
 
217608
     skip "Windows chmod test skipped", $skip_count
217608
         if $^O eq 'MSWin32';
217608
+    skip "fchmod() on directories is not supported on this platform", $skip_count
217608
+        unless $fchmod_supported;
217608
     my $mode;
217608
     my $octal_mode;
217608
     my @inputs = (
217608
-      0777, 0700, 0070, 0007,
217608
-      0333, 0300, 0030, 0003,
217608
-      0111, 0100, 0010, 0001,
217608
-      0731, 0713, 0317, 0371, 0173, 0137,
217608
-      00 );
217608
+      0777, 0700, 0470, 0407,
217608
+      0433, 0400, 0430, 0403,
217608
+      0111, 0100, 0110, 0101,
217608
+      0731, 0713, 0317, 0371,
217608
+      0173, 0137);
217608
     my $input;
217608
     my $octal_input;
217608
-    $dir = catdir($tmp_base, 'chmod_test');
217608
 
217608
     foreach (@inputs) {
217608
         $input = $_;
217608
+        $dir = catdir($tmp_base, sprintf("chmod_test%04o", $input));
217608
         # We can skip from here because 0 is last in the list.
217608
         skip "Mode of 0 means assume user defaults on VMS", 1
217608
           if ($input == 0 && $Is_VMS);
217608
-- 
217608
2.9.4
217608