Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
* Fixed Bug #18056 [SECURITY]: Symlink attack in PEAR install [dufuz]
This feature is implemented as a result of the above fix
* Implemented Request #16648: Use TMPDIR for builds instead of /var/tmp [dufuz]

Conflicts:
	package.xml
	package2.xml
  • Loading branch information
helgi authored and cweiske committed May 27, 2014
1 parent 4893d4a commit 38de935
Show file tree
Hide file tree
Showing 11 changed files with 959 additions and 1,011 deletions.
25 changes: 16 additions & 9 deletions PEAR/Builder.php
Expand Up @@ -220,7 +220,7 @@ function _harvestInstDir($dest_prefix, $dirname, &$built_files)
/**
* Build an extension from source. Runs "phpize" in the source
* directory, but compiles in a temporary directory
* (/var/tmp/pear-build-USER/PACKAGE-VERSION).
* (TMPDIR/pear-build-USER/PACKAGE-VERSION).
*
* @param string|PEAR_PackageFile_v* $descfile path to XML package description file, or
* a PEAR_PackageFile object
Expand Down Expand Up @@ -250,6 +250,7 @@ function build($descfile, $callback = null)
' appears to have a prefix ' . $matches[2] . ', but' .
' config variable php_prefix does not match');
}

if (isset($matches[3]) && strlen($matches[3]) &&
trim($matches[3]) != trim($this->config->get('php_suffix'))) {
$this->log(0, 'WARNING: php_bin ' . $this->config->get('php_bin') .
Expand All @@ -258,14 +259,15 @@ function build($descfile, $callback = null)
}
}


$this->current_callback = $callback;
if (PEAR_OS == "Windows") {
return $this->_build_win32($descfile, $callback);
}

if (PEAR_OS != 'Unix') {
return $this->raiseError("building extensions not supported on this platform");
}

if (is_object($descfile)) {
$pkg = $descfile;
$descfile = $pkg->getPackageFile();
Expand All @@ -284,14 +286,17 @@ function build($descfile, $callback = null)
}
$dir = dirname($descfile);
}

$old_cwd = getcwd();
if (!file_exists($dir) || !is_dir($dir) || !chdir($dir)) {
return $this->raiseError("could not chdir to $dir");
}

$vdir = $pkg->getPackage() . '-' . $pkg->getVersion();
if (is_dir($vdir)) {
chdir($vdir);
}

$dir = getcwd();
$this->log(2, "building in $dir");
putenv('PATH=' . $this->config->get('bin_dir') . ':' . getenv('PATH'));
Expand All @@ -302,6 +307,7 @@ function build($descfile, $callback = null)
if (PEAR::isError($err)) {
return $err;
}

if (!$err) {
return $this->raiseError("`phpize' failed");
}
Expand All @@ -327,30 +333,31 @@ function build($descfile, $callback = null)
// }}} end of interactive part

// FIXME make configurable
if(!$user=getenv('USER')){
if (!$user=getenv('USER')) {
$user='defaultuser';
}
$build_basedir = "/var/tmp/pear-build-$user";

$tmpdir = $this->config->get('temp_dir');
$build_basedir = System::mktemp(" -t $tmpdir -d pear-build-$user");
$build_dir = "$build_basedir/$vdir";
$inst_dir = "$build_basedir/install-$vdir";
$this->log(1, "building in $build_dir");
if (is_dir($build_dir)) {
System::rm(array('-rf', $build_dir));
}

if (!System::mkDir(array('-p', $build_dir))) {
return $this->raiseError("could not create build dir: $build_dir");
}

$this->addTempFile($build_dir);
if (!System::mkDir(array('-p', $inst_dir))) {
return $this->raiseError("could not create temporary install dir: $inst_dir");
}
$this->addTempFile($inst_dir);

if (getenv('MAKE')) {
$make_command = getenv('MAKE');
} else {
$make_command = 'make';
}
$make_command = getenv('MAKE') ? getenv('MAKE') : 'make';

$to_run = array(
$configure_command,
$make_command,
Expand Down
6 changes: 4 additions & 2 deletions PEAR/Command/Install.php
Expand Up @@ -730,7 +730,8 @@ function doInstall($command, $options, $params)
if ($param->getPackageType() == 'extsrc' ||
$param->getPackageType() == 'extbin' ||
$param->getPackageType() == 'zendextsrc' ||
$param->getPackageType() == 'zendextbin') {
$param->getPackageType() == 'zendextbin'
) {
$pkg = &$param->getPackageFile();
if ($instbin = $pkg->getInstalledBinary()) {
$instpkg = &$instreg->getPackage($instbin, $pkg->getChannel());
Expand All @@ -741,7 +742,8 @@ function doInstall($command, $options, $params)
foreach ($instpkg->getFilelist() as $name => $atts) {
$pinfo = pathinfo($atts['installed_as']);
if (!isset($pinfo['extension']) ||
in_array($pinfo['extension'], array('c', 'h'))) {
in_array($pinfo['extension'], array('c', 'h'))
) {
continue; // make sure we don't match php_blah.h
}

Expand Down
8 changes: 5 additions & 3 deletions PEAR/Command/Package.php
Expand Up @@ -314,15 +314,15 @@ function &getPackager()
return $a;
}

function &getPackageFile($config, $debug = false, $tmpdir = null)
function &getPackageFile($config, $debug = false)
{
if (!class_exists('PEAR_Common')) {
require_once 'PEAR/Common.php';
}
if (!class_exists('PEAR_PackageFile')) {
require_once 'PEAR/PackageFile.php';
}
$a = &new PEAR_PackageFile($config, $debug, $tmpdir);
$a = &new PEAR_PackageFile($config, $debug);
$common = new PEAR_Common;
$common->ui = $this->ui;
$a->setLogger($common);
Expand Down Expand Up @@ -969,7 +969,9 @@ function doSign($command, $options, $params)
}

$tar = new Archive_Tar($params[0]);
$tmpdir = System::mktemp('-d pearsign');

$tmpdir = $this->config->get('temp_dir');
$tmpdir = System::mktemp(" -t $tmpdir -d pearsign");
if (!$tar->extractList('package2.xml package.xml package.sig', $tmpdir)) {
return $this->raiseError("failed to extract tar file");
}
Expand Down
4 changes: 2 additions & 2 deletions PEAR/Command/Pickle.php
Expand Up @@ -104,7 +104,7 @@ function &getPackager()
* @param string|null $tmpdir
* @return PEAR_PackageFile
*/
function &getPackageFile($config, $debug = false, $tmpdir = null)
function &getPackageFile($config, $debug = false)
{
if (!class_exists('PEAR_Common')) {
require_once 'PEAR/Common.php';
Expand All @@ -114,7 +114,7 @@ function &getPackageFile($config, $debug = false, $tmpdir = null)
require_once 'PEAR/PackageFile.php';
}

$a = &new PEAR_PackageFile($config, $debug, $tmpdir);
$a = &new PEAR_PackageFile($config, $debug);
$common = new PEAR_Common;
$common->ui = $this->ui;
$a->setLogger($common);
Expand Down
3 changes: 2 additions & 1 deletion PEAR/Command/Remote.php
Expand Up @@ -144,7 +144,7 @@ class PEAR_Command_Remote extends PEAR_Command_Common
'shortcut' => 'cc',
'options' => array(),
'doc' => '
Clear the XML-RPC/REST cache. See also the cache_ttl configuration
Clear the REST cache. See also the cache_ttl configuration
parameter.
',
),
Expand Down Expand Up @@ -776,6 +776,7 @@ function doClearCache($command, $options, $params)
if ($verbose >= 1) {
$output .= "reading directory $cache_dir\n";
}

$num = 0;
while ($ent = readdir($dp)) {
if (preg_match('/rest.cache(file|id)\\z/', $ent)) {
Expand Down
50 changes: 27 additions & 23 deletions PEAR/Downloader.php
Expand Up @@ -189,7 +189,8 @@ function discover($channel)
require_once 'System.php';
}

$tmp = System::mktemp(array('-d'));
$tmpdir = $this->config->get('temp_dir');
$tmp = System::mktemp("-d -t $tmpdir");
$a = $this->downloadHttp('http://' . $channel . '/channel.xml', $this->ui, $tmp, $callback, false);
PEAR::popErrorHandling();
if (PEAR::isError($a)) {
Expand Down Expand Up @@ -493,14 +494,13 @@ function &download($params)
*/
function analyzeDependencies(&$params, $force = false)
{
$hasfailed = $failed = false;
if (isset($this->_options['downloadonly'])) {
return;
}

PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
$redo = true;
$reset = false;
$reset = $hasfailed = $failed = false;
while ($redo) {
$redo = false;
foreach ($params as $i => $param) {
Expand Down Expand Up @@ -698,6 +698,7 @@ function analyzeDependencies(&$params, $force = false)
}
}
}

PEAR::staticPopErrorHandling();
if ($hasfailed && (isset($this->_options['ignore-errors']) ||
isset($this->_options['nodeps']))) {
Expand All @@ -718,21 +719,25 @@ function getDownloadDir()
if (isset($this->_downloadDir)) {
return $this->_downloadDir;
}

$downloaddir = $this->config->get('download_dir');
if (empty($downloaddir) || (is_dir($downloaddir) && !is_writable($downloaddir))) {
if (is_dir($downloaddir) && !is_writable($downloaddir)) {
$this->log(0, 'WARNING: configuration download directory "' . $downloaddir .
'" is not writeable. Change download_dir config variable to ' .
'a writeable dir to avoid this warning');
}

if (!class_exists('System')) {
require_once 'System.php';
}

if (PEAR::isError($downloaddir = System::mktemp('-d'))) {
return $downloaddir;
}
$this->log(3, '+ tmp dir created at ' . $downloaddir);
}

if (!is_writable($downloaddir)) {
if (PEAR::isError(System::mkdir(array('-p', $downloaddir))) ||
!is_writable($downloaddir)) {
Expand All @@ -741,6 +746,7 @@ function getDownloadDir()
'a writeable dir');
}
}

return $this->_downloadDir = $downloaddir;
}

Expand Down Expand Up @@ -771,27 +777,11 @@ function setOptions($options)
$this->_options = $options;
}

// }}}
// {{{ setOptions()
function getOptions()
{
return $this->_options;
}

/**
* For simpler unit-testing
* @param PEAR_Config
* @param int
* @param string
*/
function &getPackagefileObject(&$c, $d, $t = false)
{
if (!class_exists('PEAR_PackageFile')) {
require_once 'PEAR/PackageFile.php';
}
$a = &new PEAR_PackageFile($c, $d, $t);
return $a;
}

/**
* @param array output of {@link parsePackageName()}
Expand Down Expand Up @@ -1000,9 +990,20 @@ function _getDepPackageDownloadUrl($dep, $parr)
}
return $info;
} elseif ($chan->supportsREST($this->config->get('preferred_mirror'))
&& $base = $chan->getBaseURL('REST1.0', $this->config->get('preferred_mirror'))
&&
(
($base2 = $chan->getBaseURL('REST1.3', $this->config->get('preferred_mirror')))
||
($base = $chan->getBaseURL('REST1.0', $this->config->get('preferred_mirror')))
)
) {
$rest = &$this->config->getREST('1.0', $this->_options);
if ($base2) {
$base = $base2;
$rest = &$this->config->getREST('1.3', $this->_options);
} else {
$rest = &$this->config->getREST('1.0', $this->_options);
}

$url = $rest->getDepDownloadURL($base, $xsdversion, $dep, $parr,
$state, $version, $chan->getName());
if (PEAR::isError($url)) {
Expand Down Expand Up @@ -1707,6 +1708,10 @@ function downloadHttp($url, &$ui, $save_dir = '.', $callback = null, $lastmodifi
}

$dest_file = $save_dir . DIRECTORY_SEPARATOR . $save_as;
if (is_link($dest_file)) {
return PEAR::raiseError('SECURITY ERROR: Will not write to ' . $dest_file . ' as it is symlinked to ' . readlink($dest_file) . ' - Possible symlink attack');
}

if (!$wp = @fopen($dest_file, 'wb')) {
fclose($fp);
if ($callback) {
Expand Down Expand Up @@ -1758,5 +1763,4 @@ function downloadHttp($url, &$ui, $save_dir = '.', $callback = null, $lastmodifi
}
return $dest_file;
}
}
// }}}
}
28 changes: 6 additions & 22 deletions PEAR/Downloader/Package.php
Expand Up @@ -1376,6 +1376,8 @@ function mergeDependencies(&$params)
continue;
}

// FIXME do symlink check

fwrite($fp, $filecontents, strlen($filecontents));
fclose($fp);
if ($s = $params[$i]->explicitState()) {
Expand Down Expand Up @@ -1497,13 +1499,12 @@ function willDownload($param, $params)
* @param int
* @param string
*/
function &getPackagefileObject(&$c, $d, $t = false)
function &getPackagefileObject(&$c, $d)
{
$a = &new PEAR_PackageFile($c, $d, $t);
$a = &new PEAR_PackageFile($c, $d);
return $a;
}


/**
* This will retrieve from a local file if possible, and parse out
* a group name as well. The original parameter will be modified to reflect this.
Expand All @@ -1527,16 +1528,7 @@ function _fromFile(&$param)
if (@is_file($param)) {
$this->_type = 'local';
$options = $this->_downloader->getOptions();
if (isset($options['downloadonly'])) {
$pkg = &$this->getPackagefileObject($this->_config,
$this->_downloader->_debug);
} else {
if (PEAR::isError($dir = $this->_downloader->getDownloadDir())) {
return $dir;
}
$pkg = &$this->getPackagefileObject($this->_config,
$this->_downloader->_debug, $dir);
}
$pkg = &$this->getPackagefileObject($this->_config, $this->_downloader->_debug);
PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
$pf = &$pkg->fromAnyFile($param, PEAR_VALIDATE_INSTALLING);
PEAR::popErrorHandling();
Expand Down Expand Up @@ -1608,15 +1600,7 @@ function _fromUrl($param, $saveparam = '')
}

// whew, download worked!
if (isset($options['downloadonly'])) {
$pkg = &$this->getPackagefileObject($this->_config, $this->_downloader->debug);
} else {
$dir = $this->_downloader->getDownloadDir();
if (PEAR::isError($dir)) {
return $dir;
}
$pkg = &$this->getPackagefileObject($this->_config, $this->_downloader->debug, $dir);
}
$pkg = &$this->getPackagefileObject($this->_config, $this->_downloader->debug);

PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
$pf = &$pkg->fromAnyFile($file, PEAR_VALIDATE_INSTALLING);
Expand Down

0 comments on commit 38de935

Please sign in to comment.