From 3709d2e9e89e455638fefb94a792935931bf81d4 Mon Sep 17 00:00:00 2001
From: Bob Vincent <bobvin@pillars.net>
Date: Thu, 16 Jun 2011 22:19:07 -0400
Subject: [PATCH 1/2] Issue #1008402 by tekante, pillarsdotnet: Add test for symlink support within the files directory.

---
 modules/simpletest/tests/file.test |   36 ++++++++++++++++++++++++++++++++++++
 1 files changed, 36 insertions(+), 0 deletions(-)

diff --git a/modules/simpletest/tests/file.test b/modules/simpletest/tests/file.test
index dc12b1b7395371b423abf9e74d1b3352f61100ad..b1e992718c9658b9565dc3766c711dd58dcd4088 100644
--- a/modules/simpletest/tests/file.test
+++ b/modules/simpletest/tests/file.test
@@ -983,6 +983,42 @@ class FileDirectoryTest extends FileTestCase {
     $setting = variable_get('file_temporary_path', '');
     $this->assertEqual($setting, $tmp_directory, t("The 'file_temporary_path' variable has the same value that file_directory_temp() returned."));
   }
+
+  /**
+   * Tests that symlinks are supported within the files directory.
+   */
+  function testFileDirectorySymlinks() {
+    // Temporarily unset the file_temporary_path variable.
+    $file_temporary_path = variable_get('file_temporary_path', NULL);
+    variable_del('file_temporary_path');
+    // Now the return value of file_directory_temp() should be outside
+    // the public files directory.
+    $temp = file_directory_temp();
+    // Restore the file_temporary_path variable.
+    variable_set('file_temporary_path', $file_temporary_path);
+    $public = drupal_realpath('public://');
+    $dirname = $this->randomname(20);
+    $filename = $this->randomname(20);
+    // Create a randomly-named directory in the temp folder.
+    $temp_dir = drupal_realpath($temp) . DIRECTORY_SEPARATOR . $dirname;
+    drupal_mkdir($temp_dir);
+    // Create a symlink in the public files folder pointing to the
+    // newly-created directory.
+    $symlink = $public . DIRECTORY_SEPARATOR . $dirname;
+    symlink($temp_dir, $symlink);
+    // Copy a test file to the symlinked directory.
+    $source = current($this->drupalGetTestFiles('text'))->uri;
+    $destination = "public://$dirname/$filename";
+    file_unmanaged_copy($source, $destination, FILE_EXISTS_ERROR);
+    // Test that the real path of the copied file lies in the temp folder.
+    $realpath = drupal_realpath($destination);
+    $compare = $temp_dir . DIRECTORY_SEPARATOR . $filename;
+    $this->assertEqual($realpath, $compare, "drupal_realpath('$destination') returned '$realpath'; expected '$compare'");
+    // Clean up the mess.
+    file_unmanaged_delete($destination);
+    drupal_unlink($symlink);
+    drupal_rmdir($temp_dir);
+  }
 }
 
 /**
-- 
1.7.4.1


From d3bb34f2a58605f5b00648969384698b57ae0ddc Mon Sep 17 00:00:00 2001
From: Bob Vincent <bobvin@pillars.net>
Date: Thu, 16 Jun 2011 22:20:34 -0400
Subject: [PATCH 2/2] Issue #1008402 by tekante, pillarsdotnet: Add support for symlinks within the files directory.

---
 includes/stream_wrappers.inc |   30 +++++++++++++++++++++---------
 1 files changed, 21 insertions(+), 9 deletions(-)

diff --git a/includes/stream_wrappers.inc b/includes/stream_wrappers.inc
index 7df1f9dc6b23a8e4bc3daac7f7ddb626174be5ee..0c1c875ae14b09241ce20ada59448d4f8a5c7237 100644
--- a/includes/stream_wrappers.inc
+++ b/includes/stream_wrappers.inc
@@ -361,17 +361,29 @@ abstract class DrupalLocalStreamWrapper implements DrupalStreamWrapperInterface
     if (!isset($uri)) {
       $uri = $this->uri;
     }
-    $path = $this->getDirectoryPath() . '/' . $this->getTarget($uri);
-    $realpath = realpath($path);
-    if (!$realpath) {
-      // This file does not yet exist.
-      $realpath = realpath(dirname($path)) . '/' . basename($path);
-    }
-    $directory = realpath($this->getDirectoryPath());
-    if (!$realpath || !$directory || strpos($realpath, $directory) !== 0) {
+    // This is the target path relative to the files repository.
+    $target = DIRECTORY_SEPARATOR . $this->getTarget($uri);
+    // This is the files repository directory.
+    $repository = realpath($this->getDirectoryPath());
+    // This is the target directory.
+    $target_dir = realpath(dirname($repository . $target)) . DIRECTORY_SEPARATOR;
+    // This is the target name, without any directory components.
+    $target_name = basename($repository . $target);
+    // A directory component can point outside its parent directory if the path
+    // separator ('/' or '\') is followed either by '..' to reference the parent
+    // directory, or '~' to reference the user home directory.
+    $pattern = '@(/|\\\\)(\.\.|~)@';
+    // This checks whether the target can possibly point outside its parent.
+    $traversal = preg_match($pattern, $target);
+    // This checks whether the target dir exists within the files repository.
+    $subdirectory = strpos($target_dir, $repository . DIRECTORY_SEPARATOR) === 0;
+    if ($traversal && !$subdirectory) {
+      // If the target path contains directory-traversal parts such as
+      // '/..' or '/~', and does not resolve to a subdirectory of the
+      // repository, then return FALSE to avoid a possible exploit.
       return FALSE;
     }
-    return $realpath;
+    return $target_dir . $target_name;
   }
 
   /**
-- 
1.7.4.1

