diff --git a/src/applications/diffusion/protocol/DiffusionGitCommandEngine.php b/src/applications/diffusion/protocol/DiffusionGitCommandEngine.php index a16416ae1d..168b18caa5 100644 --- a/src/applications/diffusion/protocol/DiffusionGitCommandEngine.php +++ b/src/applications/diffusion/protocol/DiffusionGitCommandEngine.php @@ -1,51 +1,49 @@ isGit(); } protected function newFormattedCommand($pattern, array $argv) { $pattern = "git {$pattern}"; return array($pattern, $argv); } protected function newCustomEnvironment() { $env = array(); // NOTE: See T2965. Some time after Git 1.7.5.4, Git started fataling if // it can not read $HOME. For many users, $HOME points at /root (this // seems to be a default result of Apache setup). Instead, explicitly // point $HOME at a readable, empty directory so that Git looks for the // config file it's after, fails to locate it, and moves on. This is // really silly, but seems like the least damaging approach to // mitigating the issue. $env['HOME'] = PhabricatorEnv::getEmptyCWD(); - if ($this->isAnySSHProtocol()) { - $env['GIT_SSH'] = $this->getSSHWrapper(); - } + $env['GIT_SSH'] = $this->getSSHWrapper(); if ($this->isAnyHTTPProtocol()) { $uri = $this->getURI(); if ($uri) { $proxy = PhutilHTTPEngineExtension::buildHTTPProxyURI($uri); if ($proxy) { if ($this->isHTTPSProtocol()) { $env_key = 'https_proxy'; } else { $env_key = 'http_proxy'; } $env[$env_key] = (string)$proxy; } } } return $env; } } diff --git a/src/applications/diffusion/protocol/DiffusionMercurialCommandEngine.php b/src/applications/diffusion/protocol/DiffusionMercurialCommandEngine.php index 49c618b085..9499b8f5f1 100644 --- a/src/applications/diffusion/protocol/DiffusionMercurialCommandEngine.php +++ b/src/applications/diffusion/protocol/DiffusionMercurialCommandEngine.php @@ -1,70 +1,72 @@ isHg(); } protected function newFormattedCommand($pattern, array $argv) { $args = array(); - if ($this->isAnySSHProtocol()) { - $pattern = "hg --config ui.ssh=%s {$pattern}"; - $args[] = $this->getSSHWrapper(); - } else { - $pattern = "hg {$pattern}"; - } + // NOTE: Here, and in Git and Subversion, we override the SSH command even + // if the repository does not use an SSH remote, since our SSH wrapper + // defuses an attack against older versions of Mercurial, Git and + // Subversion (see T12961) and it's possible to execute this attack + // in indirect ways, like by using an SSH subrepo inside an HTTP repo. + + $pattern = "hg --config ui.ssh=%s {$pattern}"; + $args[] = $this->getSSHWrapper(); return array($pattern, array_merge($args, $argv)); } protected function newCustomEnvironment() { $env = array(); // NOTE: This overrides certain configuration, extensions, and settings // which make Mercurial commands do random unusual things. $env['HGPLAIN'] = 1; return $env; } /** * Sanitize output of an `hg` command invoked with the `--debug` flag to make * it usable. * * @param string Output from `hg --debug ...` * @return string Usable output. */ public static function filterMercurialDebugOutput($stdout) { // When hg commands are run with `--debug` and some config file isn't // trusted, Mercurial prints out a warning to stdout, twice, after Feb 2011. // // http://selenic.com/pipermail/mercurial-devel/2011-February/028541.html // // After Jan 2015, it may also fail to write to a revision branch cache. $ignore = array( 'ignoring untrusted configuration option', "couldn't write revision branch cache:", ); foreach ($ignore as $key => $pattern) { $ignore[$key] = preg_quote($pattern, '/'); } $ignore = '('.implode('|', $ignore).')'; $lines = preg_split('/(?<=\n)/', $stdout); $regex = '/'.$ignore.'.*\n$/'; foreach ($lines as $key => $line) { $lines[$key] = preg_replace($regex, '', $line); } return implode('', $lines); } } diff --git a/src/applications/diffusion/protocol/DiffusionSubversionCommandEngine.php b/src/applications/diffusion/protocol/DiffusionSubversionCommandEngine.php index d8e9e6ff17..75b8785a6e 100644 --- a/src/applications/diffusion/protocol/DiffusionSubversionCommandEngine.php +++ b/src/applications/diffusion/protocol/DiffusionSubversionCommandEngine.php @@ -1,54 +1,52 @@ isSVN(); } protected function newFormattedCommand($pattern, array $argv) { $flags = array(); $args = array(); $flags[] = '--non-interactive'; if ($this->isAnyHTTPProtocol() || $this->isSVNProtocol()) { $flags[] = '--no-auth-cache'; if ($this->isAnyHTTPProtocol()) { $flags[] = '--trust-server-cert'; } $credential_phid = $this->getCredentialPHID(); if ($credential_phid) { $key = PassphrasePasswordKey::loadFromPHID( $credential_phid, PhabricatorUser::getOmnipotentUser()); $flags[] = '--username %P'; $args[] = $key->getUsernameEnvelope(); $flags[] = '--password %P'; $args[] = $key->getPasswordEnvelope(); } } $flags = implode(' ', $flags); $pattern = "svn {$flags} {$pattern}"; return array($pattern, array_merge($args, $argv)); } protected function newCustomEnvironment() { $env = array(); - if ($this->isAnySSHProtocol()) { - $env['SVN_SSH'] = $this->getSSHWrapper(); - } + $env['SVN_SSH'] = $this->getSSHWrapper(); return $env; } } diff --git a/src/applications/diffusion/protocol/__tests__/DiffusionCommandEngineTestCase.php b/src/applications/diffusion/protocol/__tests__/DiffusionCommandEngineTestCase.php index 76d9ad9170..1df3ea3522 100644 --- a/src/applications/diffusion/protocol/__tests__/DiffusionCommandEngineTestCase.php +++ b/src/applications/diffusion/protocol/__tests__/DiffusionCommandEngineTestCase.php @@ -1,155 +1,155 @@ assertCommandEngineFormat( 'git xyz', array( 'LANG' => 'en_US.UTF-8', 'HOME' => $home, ), array( 'vcs' => $type_git, 'argv' => 'xyz', )); $this->assertCommandEngineFormat( - 'hg xyz', + (string)csprintf('hg --config ui.ssh=%s xyz', $ssh_wrapper), array( 'LANG' => 'en_US.UTF-8', 'HGPLAIN' => '1', ), array( 'vcs' => $type_hg, 'argv' => 'xyz', )); $this->assertCommandEngineFormat( 'svn --non-interactive xyz', array( 'LANG' => 'en_US.UTF-8', ), array( 'vcs' => $type_svn, 'argv' => 'xyz', )); // Commands with SSH. $this->assertCommandEngineFormat( 'git xyz', array( 'LANG' => 'en_US.UTF-8', 'HOME' => $home, 'GIT_SSH' => $ssh_wrapper, ), array( 'vcs' => $type_git, 'argv' => 'xyz', 'protocol' => 'ssh', )); $this->assertCommandEngineFormat( (string)csprintf('hg --config ui.ssh=%s xyz', $ssh_wrapper), array( 'LANG' => 'en_US.UTF-8', 'HGPLAIN' => '1', ), array( 'vcs' => $type_hg, 'argv' => 'xyz', 'protocol' => 'ssh', )); $this->assertCommandEngineFormat( 'svn --non-interactive xyz', array( 'LANG' => 'en_US.UTF-8', 'SVN_SSH' => $ssh_wrapper, ), array( 'vcs' => $type_svn, 'argv' => 'xyz', 'protocol' => 'ssh', )); // Commands with HTTP. $this->assertCommandEngineFormat( 'git xyz', array( 'LANG' => 'en_US.UTF-8', 'HOME' => $home, ), array( 'vcs' => $type_git, 'argv' => 'xyz', 'protocol' => 'https', )); $this->assertCommandEngineFormat( - 'hg xyz', + (string)csprintf('hg --config ui.ssh=%s xyz', $ssh_wrapper), array( 'LANG' => 'en_US.UTF-8', 'HGPLAIN' => '1', ), array( 'vcs' => $type_hg, 'argv' => 'xyz', 'protocol' => 'https', )); $this->assertCommandEngineFormat( 'svn --non-interactive --no-auth-cache --trust-server-cert xyz', array( 'LANG' => 'en_US.UTF-8', ), array( 'vcs' => $type_svn, 'argv' => 'xyz', 'protocol' => 'https', )); } private function assertCommandEngineFormat( $command, array $env, array $inputs) { $repository = id(new PhabricatorRepository()) ->setVersionControlSystem($inputs['vcs']); $future = DiffusionCommandEngine::newCommandEngine($repository) ->setArgv((array)$inputs['argv']) ->setProtocol(idx($inputs, 'protocol')) ->newFuture(); $command_string = $future->getCommand(); $actual_command = $command_string->getUnmaskedString(); $this->assertEqual($command, $actual_command); $actual_environment = $future->getEnv(); $compare_environment = array_select_keys( $actual_environment, array_keys($env)); $this->assertEqual($env, $compare_environment); } }