--- 1675260_105.patch 2012-08-19 19:00:41.000000000 +0200 +++ ../modules/1675260-117.patch 2012-08-19 19:27:34.000000000 +0200 @@ -84,9 +84,91 @@ } $dir->close(); +diff --git a/core/lib/Drupal/Component/PhpStorage/FileReadOnlyStorage.php b/core/lib/Drupal/Component/PhpStorage/FileReadOnlyStorage.php +new file mode 100644 +index 0000000..e71f470 +--- /dev/null ++++ b/core/lib/Drupal/Component/PhpStorage/FileReadOnlyStorage.php +@@ -0,0 +1,76 @@ ++directory = $configuration['directory'] . '/' . $bin; ++ } ++ ++ /** ++ * Implements Drupal\Component\PhpStorage\PhpStorageInterface::exists(). ++ */ ++ public function exists($name) { ++ return file_exists($this->getFullPath($name)); ++ } ++ ++ /** ++ * Implements Drupal\Component\PhpStorage\PhpStorageInterface::load(). ++ */ ++ public function load($name) { ++ // The FALSE returned on failure is enough for the caller to handle this, ++ // we do not want a warning too. ++ return (@include_once $this->getFullPath($name)) !== FALSE; ++ } ++ ++ /** ++ * Implements Drupal\Component\PhpStorage\PhpStorageInterface::save(). ++ */ ++ public function save($name, $code) { ++ return FALSE; ++ } ++ ++ /** ++ * Implements Drupal\Component\PhpStorage\PhpStorageInterface::delete(). ++ */ ++ public function delete($name) { ++ return FALSE; ++ } ++ ++ /** ++ * Returns the full path where the file is or should be stored. ++ */ ++ protected function getFullPath($name) { ++ return $this->directory . '/' . $name; ++ } ++} diff --git a/core/lib/Drupal/Component/PhpStorage/FileStorage.php b/core/lib/Drupal/Component/PhpStorage/FileStorage.php new file mode 100644 -index 0000000..448ceec +index 0000000..0847d42 --- /dev/null +++ b/core/lib/Drupal/Component/PhpStorage/FileStorage.php @@ -0,0 +1,75 @@ @@ -115,7 +197,7 @@ + * Constructs this FileStorage object. + * + * @param $configuration -+ * An associated array, containing at least one key (the rest are ignored): ++ * An associative array, containing at least one key (the rest are ignored): + * - directory: The directory where the files should be stored. + * @param $bin + * The storage bin. Multiple storage objects can be instantiated with the @@ -167,10 +249,10 @@ +} diff --git a/core/lib/Drupal/Component/PhpStorage/MTimeProtectedFileStorage.php b/core/lib/Drupal/Component/PhpStorage/MTimeProtectedFileStorage.php new file mode 100644 -index 0000000..2401c60 +index 0000000..87d4abd --- /dev/null +++ b/core/lib/Drupal/Component/PhpStorage/MTimeProtectedFileStorage.php -@@ -0,0 +1,200 @@ +@@ -0,0 +1,204 @@ + 'Drupal\Component\PhpStorage\FileStorage', + 'directory' => DRUPAL_ROOT . '/' . variable_get('file_public_path', conf_path() . '/files') . '/php', + ); ++ $conf['php_storage']['readonly'] = array( ++ 'class' => 'Drupal\Component\PhpStorage\FileReadOnlyStorage', ++ 'directory' => DRUPAL_ROOT . '/' . variable_get('file_public_path', conf_path() . '/files') . '/php', ++ // Let this read from the bin where the other instance is writing. ++ 'bin' => 'simpletest', ++ ); + } + + /** @@ -532,13 +624,42 @@ + $this->assertIdentical(get_class($php), 'Drupal\Component\PhpStorage\FileStorage'); + $this->assertCRUD($php); + } ++ ++ /** ++ * Tests writing with one class and readon with another. ++ */ ++ function testReadOnly() { ++ $php = drupal_php_storage('simpletest'); ++ $name = $this->randomName() . '/' . $this->randomName() . '.php'; ++ ++ // Find a function name that doesn't exist. ++ do { ++ $random = mt_rand(10000, 100000); ++ $function = 'test' . $random; ++ } while (function_exists($function)); ++ ++ // Write out a PHP file and ensure it's successfully loaded. ++ $code = "save($name, $code); ++ $this->assertIdentical($success, TRUE); ++ $php_read = drupal_php_storage('readonly'); ++ $php_read->load($name); ++ $this->assertIdentical($function(), $random); ++ ++ // If the file was successfully loaded, it must also exist, but ensure the ++ // exists() method returns that correctly. ++ $this->assertIdentical($php_read->exists($name), TRUE); ++ // Saving and deleting should always fail. ++ $this->assertFalse($php_read->save($name, $code)); ++ $this->assertFalse($php_read->delete($name)); ++ } +} diff --git a/core/modules/system/lib/Drupal/system/Tests/PhpStorage/MTimeProtectedFileStorageTest.php b/core/modules/system/lib/Drupal/system/Tests/PhpStorage/MTimeProtectedFileStorageTest.php new file mode 100644 -index 0000000..12f0ce5 +index 0000000..f52b991 --- /dev/null +++ b/core/modules/system/lib/Drupal/system/Tests/PhpStorage/MTimeProtectedFileStorageTest.php -@@ -0,0 +1,87 @@ +@@ -0,0 +1,125 @@ +secret = $this->randomName(); + $conf['php_storage']['simpletest'] = array( + 'class' => 'Drupal\Component\PhpStorage\MTimeProtectedFileStorage', + 'directory' => DRUPAL_ROOT . '/' . variable_get('file_public_path', conf_path() . '/files') . '/php', -+ 'secret' => $GLOBALS['drupal_hash_salt'], ++ 'secret' => $this->secret, + ); + } + @@ -589,7 +711,7 @@ + $php->save($name, 'secret . filemtime($expected_directory)) . '.php'; + + // Ensure the file exists and that it and the containing directory have + // minimal permissions. fileperms() can return high bits unrelated to @@ -622,9 +744,46 @@ + chmod($expected_directory, 0100); + $this->assertIdentical(file_get_contents($expected_filename), $untrusted_code); + $php->load($name); ++ // If the untrusted code was included, $hacked would be re-assigned to FALSE. + $this->assertIdentical($hacked, FALSE); + $this->assertIdentical($php->exists($name), FALSE); + } ++ ++ /** ++ * Tests the vulnerability of the MTimeProtectedFileStorage implementation. It ++ * does not protet against overwritiing a file. ++ */ ++ function testVulnerability() { ++ $php = drupal_php_storage('simpletest'); ++ $name = 'simpletest.php'; ++ $php->save($name, 'secret . filemtime($expected_directory)) . '.php'; ++ ++ // Ensure the file exists and that it and the containing directory have ++ // minimal permissions. fileperms() can return high bits unrelated to ++ // permissions, so mask with 0777. ++ $this->assertTrue(file_exists($expected_filename)); ++ $this->assertIdentical(fileperms($expected_filename) & 0777, 0400); ++ $this->assertIdentical(fileperms($expected_directory) & 0777, 0100); ++ ++ // Ensure that if the file is replaced with an untrusted one (due to a ++ // direct replacedment), it does get loaded. ++ sleep(1); ++ $hacked = FALSE; ++ $untrusted_code = "assertIdentical(file_get_contents($expected_filename), $untrusted_code); ++ $php->load($name); ++ // If the untrusted code was included, $hacked would be re-assigned to FALSE. ++ $this->assertIdentical($hacked, FALSE); ++ $this->assertIdentical($php->exists($name), TRUE); ++ } +} diff --git a/core/modules/system/lib/Drupal/system/Tests/PhpStorage/PhpStorageTestBase.php b/core/modules/system/lib/Drupal/system/Tests/PhpStorage/PhpStorageTestBase.php new file mode 100644