dissalow symlinks in local storages that point outside the datadir

This commit is contained in:
Robin Appelman 2016-05-30 15:44:19 +02:00 committed by Arthur Schiwon
parent a6134a6d2f
commit bee918693a
No known key found for this signature in database
GPG Key ID: 7424F1874854DF23
3 changed files with 63 additions and 2 deletions

View File

@ -33,20 +33,31 @@
*/ */
namespace OC\Files\Storage; namespace OC\Files\Storage;
use OCP\Files\ForbiddenException;
/** /**
* for local filestore, we only have to map the paths * for local filestore, we only have to map the paths
*/ */
class Local extends \OC\Files\Storage\Common { class Local extends \OC\Files\Storage\Common {
protected $datadir; protected $datadir;
protected $dataDirLength;
protected $allowSymlinks = false;
protected $realDataDir;
public function __construct($arguments) { public function __construct($arguments) {
if (!isset($arguments['datadir']) || !is_string($arguments['datadir'])) { if (!isset($arguments['datadir']) || !is_string($arguments['datadir'])) {
throw new \InvalidArgumentException('No data directory set for local storage'); throw new \InvalidArgumentException('No data directory set for local storage');
} }
$this->datadir = $arguments['datadir']; $this->datadir = $arguments['datadir'];
$this->realDataDir = rtrim(realpath($this->datadir), '/') . '/';
if (substr($this->datadir, -1) !== '/') { if (substr($this->datadir, -1) !== '/') {
$this->datadir .= '/'; $this->datadir .= '/';
} }
$this->dataDirLength = strlen($this->realDataDir);
} }
public function __destruct() { public function __destruct() {
@ -337,10 +348,27 @@ class Local extends \OC\Files\Storage\Common {
* *
* @param string $path * @param string $path
* @return string * @return string
* @throws ForbiddenException
*/ */
public function getSourcePath($path) { public function getSourcePath($path) {
$fullPath = $this->datadir . $path; $fullPath = $this->datadir . $path;
return $fullPath; if ($this->allowSymlinks || $path === '') {
return $fullPath;
}
$pathToResolve = $fullPath;
$realPath = realpath($pathToResolve);
while ($realPath === false) { // for non existing files check the parent directory
$pathToResolve = dirname($pathToResolve);
$realPath = realpath($pathToResolve);
}
if ($realPath) {
$realPath = $realPath . '/';
}
if (substr($realPath, 0, $this->dataDirLength) === $this->realDataDir) {
return $fullPath;
} else {
throw new ForbiddenException("Following symlinks is not allowed ('$fullPath' -> '$realPath' not inside '{$this->realDataDir}')", false);
}
} }
/** /**

View File

@ -206,7 +206,9 @@ class OC_Helper {
foreach ($files as $fileInfo) { foreach ($files as $fileInfo) {
/** @var SplFileInfo $fileInfo */ /** @var SplFileInfo $fileInfo */
if ($fileInfo->isDir()) { if ($fileInfo->isLink()) {
unlink($fileInfo->getPathname());
} else if ($fileInfo->isDir()) {
rmdir($fileInfo->getRealPath()); rmdir($fileInfo->getRealPath());
} else { } else {
unlink($fileInfo->getRealPath()); unlink($fileInfo->getRealPath());

View File

@ -84,5 +84,36 @@ class LocalTest extends Storage {
public function testInvalidArgumentsNoArray() { public function testInvalidArgumentsNoArray() {
new \OC\Files\Storage\Local(null); new \OC\Files\Storage\Local(null);
} }
/**
* @expectedException \OCP\Files\ForbiddenException
*/
public function testDisallowSymlinksOutsideDatadir() {
$subDir1 = $this->tmpDir . 'sub1';
$subDir2 = $this->tmpDir . 'sub2';
$sym = $this->tmpDir . 'sub1/sym';
mkdir($subDir1);
mkdir($subDir2);
symlink($subDir2, $sym);
$storage = new \OC\Files\Storage\Local(['datadir' => $subDir1]);
$storage->file_put_contents('sym/foo', 'bar');
}
public function testDisallowSymlinksInsideDatadir() {
$subDir1 = $this->tmpDir . 'sub1';
$subDir2 = $this->tmpDir . 'sub1/sub2';
$sym = $this->tmpDir . 'sub1/sym';
mkdir($subDir1);
mkdir($subDir2);
symlink($subDir2, $sym);
$storage = new \OC\Files\Storage\Local(['datadir' => $subDir1]);
$storage->file_put_contents('sym/foo', 'bar');
}
} }