Merge pull request #15834 from owncloud/make-temporary-file-really-unique
Fix collision on temporary files + adjust permissions
This commit is contained in:
commit
4dfdaf741c
|
@ -2,6 +2,7 @@
|
||||||
/**
|
/**
|
||||||
* @author Morris Jobke <hey@morrisjobke.de>
|
* @author Morris Jobke <hey@morrisjobke.de>
|
||||||
* @author Robin Appelman <icewind@owncloud.com>
|
* @author Robin Appelman <icewind@owncloud.com>
|
||||||
|
* @author Lukas Reschke <lukas@owncloud.com>
|
||||||
*
|
*
|
||||||
* @copyright Copyright (c) 2015, ownCloud, Inc.
|
* @copyright Copyright (c) 2015, ownCloud, Inc.
|
||||||
* @license AGPL-3.0
|
* @license AGPL-3.0
|
||||||
|
@ -26,24 +27,14 @@ use OCP\ILogger;
|
||||||
use OCP\ITempManager;
|
use OCP\ITempManager;
|
||||||
|
|
||||||
class TempManager implements ITempManager {
|
class TempManager implements ITempManager {
|
||||||
/**
|
/** @var string[] Current temporary files and folders, used for cleanup */
|
||||||
* Current temporary files and folders
|
protected $current = [];
|
||||||
*
|
/** @var string i.e. /tmp on linux systems */
|
||||||
* @var string[]
|
|
||||||
*/
|
|
||||||
protected $current = array();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* i.e. /tmp on linux systems
|
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
protected $tmpBaseDir;
|
protected $tmpBaseDir;
|
||||||
|
/** @var ILogger */
|
||||||
/**
|
|
||||||
* @var \OCP\ILogger
|
|
||||||
*/
|
|
||||||
protected $log;
|
protected $log;
|
||||||
|
/** Prefix */
|
||||||
|
const TMP_PREFIX = 'oc_tmp_';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $baseDir
|
* @param string $baseDir
|
||||||
|
@ -55,35 +46,55 @@ class TempManager implements ITempManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $postFix
|
* Builds the filename with suffix and removes potential dangerous characters
|
||||||
|
* such as directory separators.
|
||||||
|
*
|
||||||
|
* @param string $absolutePath Absolute path to the file / folder
|
||||||
|
* @param string $postFix Postfix appended to the temporary file name, may be user controlled
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
protected function generatePath($postFix) {
|
private function buildFileNameWithSuffix($absolutePath, $postFix = '') {
|
||||||
if ($postFix) {
|
if($postFix !== '') {
|
||||||
$postFix = '.' . ltrim($postFix, '.');
|
$postFix = '.' . ltrim($postFix, '.');
|
||||||
|
$postFix = str_replace(['\\', '/'], '', $postFix);
|
||||||
|
$absolutePath .= '-';
|
||||||
}
|
}
|
||||||
$postFix = str_replace(['\\', '/'], '', $postFix);
|
|
||||||
return $this->tmpBaseDir . '/oc_tmp_' . md5(time() . rand()) . $postFix;
|
return $absolutePath . $postFix;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a temporary file and return the path
|
* Create a temporary file and return the path
|
||||||
*
|
*
|
||||||
* @param string $postFix
|
* @param string $postFix Postfix appended to the temporary file name
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getTemporaryFile($postFix = '') {
|
public function getTemporaryFile($postFix = '') {
|
||||||
$file = $this->generatePath($postFix);
|
|
||||||
if (is_writable($this->tmpBaseDir)) {
|
if (is_writable($this->tmpBaseDir)) {
|
||||||
touch($file);
|
// To create an unique file and prevent the risk of race conditions
|
||||||
|
// or duplicated temporary files by other means such as collisions
|
||||||
|
// we need to create the file using `tempnam` and append a possible
|
||||||
|
// postfix to it later
|
||||||
|
$file = tempnam($this->tmpBaseDir, self::TMP_PREFIX);
|
||||||
$this->current[] = $file;
|
$this->current[] = $file;
|
||||||
|
|
||||||
|
// If a postfix got specified sanitize it and create a postfixed
|
||||||
|
// temporary file
|
||||||
|
if($postFix !== '') {
|
||||||
|
$fileNameWithPostfix = $this->buildFileNameWithSuffix($file, $postFix);
|
||||||
|
touch($fileNameWithPostfix);
|
||||||
|
chmod($fileNameWithPostfix, 0600);
|
||||||
|
$this->current[] = $fileNameWithPostfix;
|
||||||
|
return $fileNameWithPostfix;
|
||||||
|
}
|
||||||
|
|
||||||
return $file;
|
return $file;
|
||||||
} else {
|
} else {
|
||||||
$this->log->warning(
|
$this->log->warning(
|
||||||
'Can not create a temporary file in directory {dir}. Check it exists and has correct permissions',
|
'Can not create a temporary file in directory {dir}. Check it exists and has correct permissions',
|
||||||
array(
|
[
|
||||||
'dir' => $this->tmpBaseDir
|
'dir' => $this->tmpBaseDir,
|
||||||
)
|
]
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -92,21 +103,30 @@ class TempManager implements ITempManager {
|
||||||
/**
|
/**
|
||||||
* Create a temporary folder and return the path
|
* Create a temporary folder and return the path
|
||||||
*
|
*
|
||||||
* @param string $postFix
|
* @param string $postFix Postfix appended to the temporary folder name
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getTemporaryFolder($postFix = '') {
|
public function getTemporaryFolder($postFix = '') {
|
||||||
$path = $this->generatePath($postFix);
|
|
||||||
if (is_writable($this->tmpBaseDir)) {
|
if (is_writable($this->tmpBaseDir)) {
|
||||||
mkdir($path);
|
// To create an unique directory and prevent the risk of race conditions
|
||||||
|
// or duplicated temporary files by other means such as collisions
|
||||||
|
// we need to create the file using `tempnam` and append a possible
|
||||||
|
// postfix to it later
|
||||||
|
$uniqueFileName = tempnam($this->tmpBaseDir, self::TMP_PREFIX);
|
||||||
|
$this->current[] = $uniqueFileName;
|
||||||
|
|
||||||
|
// Build a name without postfix
|
||||||
|
$path = $this->buildFileNameWithSuffix($uniqueFileName . '-folder', $postFix);
|
||||||
|
mkdir($path, 0700);
|
||||||
$this->current[] = $path;
|
$this->current[] = $path;
|
||||||
|
|
||||||
return $path . '/';
|
return $path . '/';
|
||||||
} else {
|
} else {
|
||||||
$this->log->warning(
|
$this->log->warning(
|
||||||
'Can not create a temporary folder in directory {dir}. Check it exists and has correct permissions',
|
'Can not create a temporary folder in directory {dir}. Check it exists and has correct permissions',
|
||||||
array(
|
[
|
||||||
'dir' => $this->tmpBaseDir
|
'dir' => $this->tmpBaseDir,
|
||||||
)
|
]
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -119,6 +139,9 @@ class TempManager implements ITempManager {
|
||||||
$this->cleanFiles($this->current);
|
$this->cleanFiles($this->current);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string[] $files
|
||||||
|
*/
|
||||||
protected function cleanFiles($files) {
|
protected function cleanFiles($files) {
|
||||||
foreach ($files as $file) {
|
foreach ($files as $file) {
|
||||||
if (file_exists($file)) {
|
if (file_exists($file)) {
|
||||||
|
@ -127,10 +150,10 @@ class TempManager implements ITempManager {
|
||||||
} catch (\UnexpectedValueException $ex) {
|
} catch (\UnexpectedValueException $ex) {
|
||||||
$this->log->warning(
|
$this->log->warning(
|
||||||
"Error deleting temporary file/folder: {file} - Reason: {error}",
|
"Error deleting temporary file/folder: {file} - Reason: {error}",
|
||||||
array(
|
[
|
||||||
'file' => $file,
|
'file' => $file,
|
||||||
'error' => $ex->getMessage()
|
'error' => $ex->getMessage(),
|
||||||
)
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -151,11 +174,11 @@ class TempManager implements ITempManager {
|
||||||
*/
|
*/
|
||||||
protected function getOldFiles() {
|
protected function getOldFiles() {
|
||||||
$cutOfTime = time() - 3600;
|
$cutOfTime = time() - 3600;
|
||||||
$files = array();
|
$files = [];
|
||||||
$dh = opendir($this->tmpBaseDir);
|
$dh = opendir($this->tmpBaseDir);
|
||||||
if ($dh) {
|
if ($dh) {
|
||||||
while (($file = readdir($dh)) !== false) {
|
while (($file = readdir($dh)) !== false) {
|
||||||
if (substr($file, 0, 7) === 'oc_tmp_') {
|
if (substr($file, 0, 7) === self::TMP_PREFIX) {
|
||||||
$path = $this->tmpBaseDir . '/' . $file;
|
$path = $this->tmpBaseDir . '/' . $file;
|
||||||
$mtime = filemtime($path);
|
$mtime = filemtime($path);
|
||||||
if ($mtime < $cutOfTime) {
|
if ($mtime < $cutOfTime) {
|
||||||
|
|
|
@ -152,16 +152,37 @@ class TempManager extends \Test\TestCase {
|
||||||
$this->assertFalse($manager->getTemporaryFolder());
|
$this->assertFalse($manager->getTemporaryFolder());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGeneratePathTraversal() {
|
public function testBuildFileNameWithPostfix() {
|
||||||
$logger = $this->getMock('\Test\NullLogger');
|
$logger = $this->getMock('\Test\NullLogger');
|
||||||
$tmpManager = \Test_Helper::invokePrivate(
|
$tmpManager = \Test_Helper::invokePrivate(
|
||||||
$this->getManager($logger),
|
$this->getManager($logger),
|
||||||
'generatePath',
|
'buildFileNameWithSuffix',
|
||||||
['../Traversal\\../FileName']
|
['/tmp/myTemporaryFile', 'postfix']
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals('/tmp/myTemporaryFile-.postfix', $tmpManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testBuildFileNameWithoutPostfix() {
|
||||||
|
$logger = $this->getMock('\Test\NullLogger');
|
||||||
|
$tmpManager = \Test_Helper::invokePrivate(
|
||||||
|
$this->getManager($logger),
|
||||||
|
'buildFileNameWithSuffix',
|
||||||
|
['/tmp/myTemporaryFile', '']
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals('/tmp/myTemporaryFile', $tmpManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testBuildFileNameWithSuffixPathTraversal() {
|
||||||
|
$logger = $this->getMock('\Test\NullLogger');
|
||||||
|
$tmpManager = \Test_Helper::invokePrivate(
|
||||||
|
$this->getManager($logger),
|
||||||
|
'buildFileNameWithSuffix',
|
||||||
|
['foo', '../Traversal\\../FileName']
|
||||||
);
|
);
|
||||||
|
|
||||||
$this->assertStringEndsNotWith('./Traversal\\../FileName', $tmpManager);
|
$this->assertStringEndsNotWith('./Traversal\\../FileName', $tmpManager);
|
||||||
$this->assertStringEndsWith('.Traversal..FileName', $tmpManager);
|
$this->assertStringEndsWith('.Traversal..FileName', $tmpManager);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue