Skip to content
Browse files

mercurial: reject malicious command argument (#27516)

We've got a security report from the Phabricator team, which basically says
--config and --debugger arguments can be injected anywhere to lead to an
arbitrary command execution.

This is a fundamental issue of the argument parsing rules in Mercurial, which
allows extensions to populate their parsing rules and such extensions can be
loaded by "--config extensions.<name>=". There's a chicken and egg problem.
We're working on hardening the parsing rules, but which won't come in by
default as it would be a behavior change.

This patch adds a verification to reject malicious command arguments as a
last ditch. The subsequent patches will fix the problem in more appropriate

Contributed by Yuya Nishihara.

git-svn-id: e93f8b46-1217-0410-a6f0-8f06a7374b81
  • Loading branch information...
marutosi committed Dec 7, 2017
1 parent d6d2d23 commit ca87bf766cdc70179cb2dce03015d78ec9c13ebd
@@ -32,6 +32,8 @@ class MercurialAdapter < AbstractAdapter

# raised if hg command exited with error, e.g. unknown revision.
class HgCommandAborted < CommandFailed; end
# raised if bad command argument detected before executing hg.
class HgCommandArgumentError < CommandFailed; end

class << self
def client_command
@@ -286,8 +288,21 @@ def format_identifier

# command options which may be processed earlier, by faulty parser in hg
HG_EARLY_BOOL_ARG = /^--(debugger|profile|traceback)$/
HG_EARLY_LIST_ARG = /^(--(config|cwd|repo(sitory)?)\b|-R)/

# Runs 'hg' command with the given args
def hg(*args, &block)
# as of hg 4.4.1, early parsing of bool options is not terminated at '--'
if args.any? { |s| s =~ HG_EARLY_BOOL_ARG }
raise HgCommandArgumentError, "malicious command argument detected"
if args.take_while { |s| s != '--' }.any? { |s| s =~ HG_EARLY_LIST_ARG }
raise HgCommandArgumentError, "malicious command argument detected"

repo_path = root_url || url
full_args = ['-R', repo_path, '--encoding', 'utf-8']
full_args << '--config' << "extensions.redminehelper=#{HG_HELPER_EXT}"
@@ -21,6 +21,7 @@ class MercurialAdapterTest < ActiveSupport::TestCase
HELPERS_DIR = Redmine::Scm::Adapters::MercurialAdapter::HELPERS_DIR
TEMPLATE_NAME = Redmine::Scm::Adapters::MercurialAdapter::TEMPLATE_NAME
TEMPLATE_EXTENSION = Redmine::Scm::Adapters::MercurialAdapter::TEMPLATE_EXTENSION
HgCommandArgumentError = Redmine::Scm::Adapters::MercurialAdapter::HgCommandArgumentError

REPOSITORY_PATH = repository_path('mercurial')
CHAR_1_HEX = "\xc3\x9c"
@@ -443,6 +444,24 @@ def test_path_encoding_default_utf8
assert_equal "UTF-8", adpt2.path_encoding

def test_bad_early_options
assert_raise HgCommandArgumentError do
@adapter.diff('sources/welcome_controller.rb', '--config=alias.rhdiff=!xterm')
assert_raise HgCommandArgumentError do
assert_raise HgCommandArgumentError do
@adapter.revisions(nil, nil, nil, limit: '--repo=otherrepo')
assert_raise HgCommandArgumentError do
@adapter.nodes_in_branch('default', limit: '--repository=otherrepo')
assert_raise HgCommandArgumentError do


def test_hgversion_for(hgversion, version)

0 comments on commit ca87bf7

Please sign in to comment.
You can’t perform that action at this time.