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