Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Introduce a time-of-check-time-of-use (TOCTOU) save file handler, thi…
…s makes sure that from the time we check it is available until we get the file handler that no one attempted to put in a soft / hard link instead of what we checked for.

Further improves on #18056
  • Loading branch information
helgi authored and cweiske committed May 27, 2014
1 parent 5097885 commit cd31da7
Showing 1 changed file with 53 additions and 37 deletions.
90 changes: 53 additions & 37 deletions PEAR/REST.php
Expand Up @@ -228,59 +228,75 @@ function saveCache($url, $contents, $lastmodified, $nochange = false, $cacheid =
$cacheidfile = $d . 'rest.cacheid';
$cachefile = $d . 'rest.cachefile';

if (!is_dir($cache_dir)) {
if (System::mkdir(array('-p', $cache_dir) === false)) {
return PEAR::raiseError("The value of config option cache_dir ($cache_dir) is not a directory and attempts to create the directory failed.");
}
}

if ($cacheid === null && $nochange) {
$cacheid = unserialize(implode('', file($cacheidfile)));
}

if (is_link($cacheidfile)) {
return PEAR::raiseError('SECURITY ERROR: Will not write to ' . $cacheidfile . ' as it is symlinked to ' . readlink($cacheidfile) . ' - Possible symlink attack');
}
$idData = serialize(array(
'age' => time(),
'lastChange' => ($nochange ? $cacheid['lastChange'] : $lastmodified),
));

if (is_link($cachefile)) {
return PEAR::raiseError('SECURITY ERROR: Will not write to ' . $cacheidfile . ' as it is symlinked to ' . readlink($cacheidfile) . ' - Possible symlink attack');
$result = $this->saveCacheFile($cacheidfile, $idData);
if (PEAR::isError($result)) {
return $result;
} elseif ($nochange) {
return true;
}

$cacheidfile_fp = @fopen($cacheidfile, 'wb');
if (!$cacheidfile_fp) {
if (is_dir($cache_dir)) {
return PEAR::raiseError("The value of config option cache_dir ($cache_dir) is not a directory. ");
$result = $this->saveCacheFile($cachefile, serialize($contents));
if (PEAR::isError($result)) {
if (file_exists($cacheidfile)) {
@unlink($cacheidfile);
}

System::mkdir(array('-p', $cache_dir));
$cacheidfile_fp = @fopen($cacheidfile, 'wb');
if (!$cacheidfile_fp) {
return PEAR::raiseError("Could not open $cacheidfile for writing.");
}
return $result;
}

if ($nochange) {
fwrite($cacheidfile_fp, serialize(array(
'age' => time(),
'lastChange' => $cacheid['lastChange'],
))
);

fclose($cacheidfile_fp);
return true;
}
return true;
}

fwrite($cacheidfile_fp, serialize(array(
'age' => time(),
'lastChange' => $lastmodified,
))
);
fclose($cacheidfile_fp);
function saveCacheFile($file, $contents)
{
$len = strlen($contents);

$cachefile_fp = @fopen($cachefile, 'wb');
if (!$cachefile_fp) {
if (file_exists($cacheidfile)) {
@unlink($cacheidfile);
$cachefile_fp = @fopen($file, 'xb'); // x is the O_CREAT|O_EXCL mode
if ($cachefile_fp !== false) { // create file
if (fwrite($cachefile_fp, $contents, $len) < $len) {
fclose($cachefile_fp);
return PEAR::raiseError("Could not write $file.");
}
} else { // update file
$cachefile_lstat = lstat($file);
$cachefile_fp = @fopen($file, 'wb');
if (!$cachefile_fp) {
return PEAR::raiseError("Could not open $file for writing.");
}

return PEAR::raiseError("Could not open $cacheidfile for writing.");
$cachefile_fstat = fstat($cachefile_fp);
if (
$cachefile_lstat['mode'] == $cachefile_fstat['mode'] &&
$cachefile_lstat['ino'] == $cachefile_fstat['ino'] &&
$cachefile_lstat['dev'] == $cachefile_fstat['dev'] &&
$cachefile_fstat['nlink'] === 1
) {
if (fwrite($cachefile_fp, $contents, $len) < $len) {
fclose($cachefile_fp);
return PEAR::raiseError("Could not write $file.");
}
} else {
fclose($cachefile_fp);
$link = function_exists('readlink') ? readlink($file) : $file;
return PEAR::raiseError('SECURITY ERROR: Will not write to ' . $file . ' as it is symlinked to ' . $link . ' - Possible symlink attack');
}
}

fwrite($cachefile_fp, serialize($contents));
fclose($cachefile_fp);
return true;
}
Expand Down Expand Up @@ -464,4 +480,4 @@ function downloadHttp($url, $lastmodified = null, $accept = false, $channel = fa

return $data;
}
}
}

0 comments on commit cd31da7

Please sign in to comment.