# HG changeset patch # User Matt Mackall # Date 1459272540 18000 # Node ID ff0d3b6b287f89594bd8d0308fe2810d2a18ea01 # Parent c4b727795d6a5770ec523c1965b464baed81097f# Parent 2d39f987f0bacc39a8319cb6c84b2d38c1991028 merge with stable diff -r c4b727795d6a -r ff0d3b6b287f .hgsigs --- a/.hgsigs Fri Mar 25 23:05:32 2016 -0700 +++ b/.hgsigs Tue Mar 29 12:29:00 2016 -0500 @@ -122,3 +122,4 @@ 2408645de650d8a29a6ce9e7dce601d8dd0d1474 0 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 diff -r c4b727795d6a -r ff0d3b6b287f .hgtags --- a/.hgtags Fri Mar 25 23:05:32 2016 -0700 +++ b/.hgtags Tue Mar 29 12:29:00 2016 -0500 @@ -135,3 +135,4 @@ 2408645de650d8a29a6ce9e7dce601d8dd0d1474 3.7 b698abf971e7377d9b7ec7fc8c52df45255b0329 3.7.1 d493d64757eb45ada99fcb3693e479a51b7782da 3.7.2 +ae279d4a19e9683214cbd1fe8298cf0b50571432 3.7.3 diff -r c4b727795d6a -r ff0d3b6b287f hgext/convert/common.py --- a/hgext/convert/common.py Fri Mar 25 23:05:32 2016 -0700 +++ b/hgext/convert/common.py Tue Mar 29 12:29:00 2016 -0500 @@ -352,6 +352,9 @@ def _run2(self, cmd, *args, **kwargs): return self._dorun(util.popen2, cmd, *args, **kwargs) + def _run3(self, cmd, *args, **kwargs): + return self._dorun(util.popen3, cmd, *args, **kwargs) + def _dorun(self, openfunc, cmd, *args, **kwargs): cmdline = self._cmdline(cmd, *args, **kwargs) self.ui.debug('running: %s\n' % (cmdline,)) diff -r c4b727795d6a -r ff0d3b6b287f hgext/convert/git.py --- a/hgext/convert/git.py Fri Mar 25 23:05:32 2016 -0700 +++ b/hgext/convert/git.py Tue Mar 29 12:29:00 2016 -0500 @@ -32,61 +32,28 @@ def hgsubstate(self): return "%s %s" % (self.node, self.path) -class convert_git(common.converter_source): +class convert_git(common.converter_source, common.commandline): # Windows does not support GIT_DIR= construct while other systems # cannot remove environment variable. Just assume none have # both issues. - if util.safehasattr(os, 'unsetenv'): - def gitopen(self, s, err=None): - prevgitdir = os.environ.get('GIT_DIR') - os.environ['GIT_DIR'] = self.path - try: - if err == subprocess.PIPE: - (stdin, stdout, stderr) = util.popen3(s) - return stdout - elif err == subprocess.STDOUT: - return self.popen_with_stderr(s) - else: - return util.popen(s, 'rb') - finally: - if prevgitdir is None: - del os.environ['GIT_DIR'] - else: - os.environ['GIT_DIR'] = prevgitdir + + def _gitcmd(self, cmd, *args, **kwargs): + return cmd('--git-dir=%s' % self.path, *args, **kwargs) + + def gitrun0(self, *args, **kwargs): + return self._gitcmd(self.run0, *args, **kwargs) - def gitpipe(self, s): - prevgitdir = os.environ.get('GIT_DIR') - os.environ['GIT_DIR'] = self.path - try: - return util.popen3(s) - finally: - if prevgitdir is None: - del os.environ['GIT_DIR'] - else: - os.environ['GIT_DIR'] = prevgitdir + def gitrun(self, *args, **kwargs): + return self._gitcmd(self.run, *args, **kwargs) + + def gitrunlines0(self, *args, **kwargs): + return self._gitcmd(self.runlines0, *args, **kwargs) - else: - def gitopen(self, s, err=None): - if err == subprocess.PIPE: - (sin, so, se) = util.popen3('GIT_DIR=%s %s' % (self.path, s)) - return so - elif err == subprocess.STDOUT: - return self.popen_with_stderr(s) - else: - return util.popen('GIT_DIR=%s %s' % (self.path, s), 'rb') + def gitrunlines(self, *args, **kwargs): + return self._gitcmd(self.runlines, *args, **kwargs) - def gitpipe(self, s): - return util.popen3('GIT_DIR=%s %s' % (self.path, s)) - - def popen_with_stderr(self, s): - p = subprocess.Popen(s, shell=True, bufsize=-1, - close_fds=util.closefds, - stdin=subprocess.PIPE, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - universal_newlines=False, - env=None) - return p.stdout + def gitpipe(self, *args, **kwargs): + return self._gitcmd(self._run3, *args, **kwargs) def gitread(self, s): fh = self.gitopen(s) @@ -95,6 +62,7 @@ def __init__(self, ui, path, revs=None): super(convert_git, self).__init__(ui, path, revs=revs) + common.commandline.__init__(self, ui, 'git') if os.path.isdir(path + "/.git"): path += "/.git" @@ -107,20 +75,20 @@ if similarity < 0 or similarity > 100: raise error.Abort(_('similarity must be between 0 and 100')) if similarity > 0: - self.simopt = '-C%d%%' % similarity + self.simopt = ['-C%d%%' % similarity] findcopiesharder = ui.configbool('convert', 'git.findcopiesharder', False) if findcopiesharder: - self.simopt += ' --find-copies-harder' + self.simopt.append('--find-copies-harder') else: - self.simopt = '' + self.simopt = [] common.checktool('git', 'git') self.path = path self.submodules = [] - self.catfilepipe = self.gitpipe('git cat-file --batch') + self.catfilepipe = self.gitpipe('cat-file', '--batch') def after(self): for f in self.catfilepipe: @@ -128,14 +96,14 @@ def getheads(self): if not self.revs: - heads, ret = self.gitread('git rev-parse --branches --remotes') - heads = heads.splitlines() - if ret: + output, status = self.gitrun('rev-parse', '--branches', '--remotes') + heads = output.splitlines() + if status: raise error.Abort(_('cannot retrieve git heads')) else: heads = [] for rev in self.revs: - rawhead, ret = self.gitread("git rev-parse --verify %s" % rev) + rawhead, ret = self.gitrun('rev-parse', '--verify', rev) heads.append(rawhead[:-1]) if ret: raise error.Abort(_('cannot retrieve git head "%s"') % rev) @@ -195,7 +163,7 @@ self.submodules.append(submodule(s['path'], '', s['url'])) def retrievegitmodules(self, version): - modules, ret = self.gitread("git show %s:%s" % (version, '.gitmodules')) + modules, ret = self.gitrun('show', '%s:%s' % (version, '.gitmodules')) if ret: # This can happen if a file is in the repo that has permissions # 160000, but there is no .gitmodules file. @@ -211,7 +179,7 @@ return for m in self.submodules: - node, ret = self.gitread("git rev-parse %s:%s" % (version, m.path)) + node, ret = self.gitrun('rev-parse', '%s:%s' % (version, m.path)) if ret: continue m.node = node.strip() @@ -220,15 +188,17 @@ if full: raise error.Abort(_("convert from git does not support --full")) self.modecache = {} - fh = self.gitopen("git diff-tree -z --root -m -r %s %s" % ( - self.simopt, version)) + cmd = ['diff-tree','-z', '--root', '-m', '-r'] + self.simopt + [version] + output, status = self.gitrun(*cmd) + if status: + raise error.Abort(_('cannot read changes in %s') % version) changes = [] copies = {} seen = set() entry = None subexists = [False] subdeleted = [False] - difftree = fh.read().split('\x00') + difftree = output.split('\x00') lcount = len(difftree) i = 0 @@ -290,8 +260,6 @@ if f != '.gitmodules' and fdest != '.gitmodules': copies[fdest] = f entry = None - if fh.close(): - raise error.Abort(_('cannot read changes in %s') % version) if subexists[0]: if subdeleted[0]: @@ -338,17 +306,23 @@ return c def numcommits(self): - return len([None for _ in self.gitopen('git rev-list --all')]) + output, ret = self.gitrunlines('rev-list', '--all') + if ret: + raise error.Abort(_('cannot retrieve number of commits in %s') \ + % self.path) + return len(output) def gettags(self): tags = {} alltags = {} - fh = self.gitopen('git ls-remote --tags "%s"' % self.path, - err=subprocess.STDOUT) + output, status = self.gitrunlines('ls-remote', '--tags', self.path) + + if status: + raise error.Abort(_('cannot read tags from %s') % self.path) prefix = 'refs/tags/' # Build complete list of tags, both annotated and bare ones - for line in fh: + for line in output: line = line.strip() if line.startswith("error:") or line.startswith("fatal:"): raise error.Abort(_('cannot read tags from %s') % self.path) @@ -356,8 +330,6 @@ if not tag.startswith(prefix): continue alltags[tag[len(prefix):]] = node - if fh.close(): - raise error.Abort(_('cannot read tags from %s') % self.path) # Filter out tag objects for annotated tag refs for tag in alltags: @@ -374,18 +346,20 @@ def getchangedfiles(self, version, i): changes = [] if i is None: - fh = self.gitopen("git diff-tree --root -m -r %s" % version) - for l in fh: + output, status = self.gitrunlines('diff-tree', '--root', '-m', + '-r', version) + if status: + raise error.Abort(_('cannot read changes in %s') % version) + for l in output: if "\t" not in l: continue m, f = l[:-1].split("\t") changes.append(f) else: - fh = self.gitopen('git diff-tree --name-only --root -r %s ' - '"%s^%s" --' % (version, version, i + 1)) - changes = [f.rstrip('\n') for f in fh] - if fh.close(): - raise error.Abort(_('cannot read changes in %s') % version) + output, status = self.gitrunlines('diff-tree', '--name-only', + '--root', '-r', version, + '%s^%s' % (version, i + 1), '--') + changes = [f.rstrip('\n') for f in output] return changes @@ -405,8 +379,8 @@ ]) try: - fh = self.gitopen('git show-ref', err=subprocess.PIPE) - for line in fh: + output, status = self.gitrunlines('show-ref') + for line in output: line = line.strip() rev, name = line.split(None, 1) # Process each type of branch diff -r c4b727795d6a -r ff0d3b6b287f mercurial/mpatch.c --- a/mercurial/mpatch.c Fri Mar 25 23:05:32 2016 -0700 +++ b/mercurial/mpatch.c Tue Mar 29 12:29:00 2016 -0500 @@ -205,7 +205,7 @@ int pos = 0; /* assume worst case size, we won't have many of these lists */ - l = lalloc(len / 12); + l = lalloc(len / 12 + 1); if (!l) return NULL; @@ -215,10 +215,10 @@ lt->start = getbe32(bin + pos); lt->end = getbe32(bin + pos + 4); lt->len = getbe32(bin + pos + 8); - if (lt->start > lt->end) - break; /* sanity check */ lt->data = bin + pos + 12; pos += 12 + lt->len; + if (lt->start > lt->end || lt->len < 0) + break; /* sanity check */ lt++; } diff -r c4b727795d6a -r ff0d3b6b287f mercurial/subrepo.py --- a/mercurial/subrepo.py Fri Mar 25 23:05:32 2016 -0700 +++ b/mercurial/subrepo.py Tue Mar 29 12:29:00 2016 -0500 @@ -1385,6 +1385,11 @@ are not supported and very probably fail. """ self.ui.debug('%s: git %s\n' % (self._relpath, ' '.join(commands))) + if env is None: + env = os.environ.copy() + # fix for Git CVE-2015-7545 + if 'GIT_ALLOW_PROTOCOL' not in env: + env['GIT_ALLOW_PROTOCOL'] = 'file:git:http:https:ssh' # unless ui.quiet is set, print git's stderr, # which is mostly progress and useful info errpipe = None diff -r c4b727795d6a -r ff0d3b6b287f tests/test-convert-git.t --- a/tests/test-convert-git.t Fri Mar 25 23:05:32 2016 -0700 +++ b/tests/test-convert-git.t Tue Mar 29 12:29:00 2016 -0500 @@ -714,7 +714,7 @@ $ COMMIT_OBJ=1c/0ce3c5886f83a1d78a7b517cdff5cf9ca17bdd $ mv git-repo4/.git/objects/$COMMIT_OBJ git-repo4/.git/objects/$COMMIT_OBJ.tmp $ hg convert git-repo4 git-repo4-broken-hg 2>&1 | grep 'abort:' - abort: cannot read tags from git-repo4/.git + abort: cannot retrieve number of commits in git-repo4/.git $ mv git-repo4/.git/objects/$COMMIT_OBJ.tmp git-repo4/.git/objects/$COMMIT_OBJ damage git repository by renaming a blob object @@ -729,3 +729,20 @@ $ mv git-repo4/.git/objects/$TREE_OBJ git-repo4/.git/objects/$TREE_OBJ.tmp $ hg convert git-repo4 git-repo4-broken-hg 2>&1 | grep 'abort:' abort: cannot read changes in 1c0ce3c5886f83a1d78a7b517cdff5cf9ca17bdd + +test for escaping the repo name (CVE-2016-3069) + + $ git init '`echo pwned >COMMAND-INJECTION`' + Initialized empty Git repository in $TESTTMP/`echo pwned >COMMAND-INJECTION`/.git/ + $ cd '`echo pwned >COMMAND-INJECTION`' + $ git commit -q --allow-empty -m 'empty' + $ cd .. + $ hg convert '`echo pwned >COMMAND-INJECTION`' 'converted' + initializing destination converted repository + scanning source... + sorting... + converting... + 0 empty + updating bookmarks + $ test -f COMMAND-INJECTION + [1] diff -r c4b727795d6a -r ff0d3b6b287f tests/test-revlog.t --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-revlog.t Tue Mar 29 12:29:00 2016 -0500 @@ -0,0 +1,15 @@ +Test for CVE-2016-3630 + + $ hg init + + >>> open("a.i", "w").write( + ... """eJxjYGZgZIAAYQYGxhgom+k/FMx8YKx9ZUaKSOyqo4cnuKb8mbqHV5cBCVTMWb1Cwqkhe4Gsg9AD + ... Joa3dYtcYYYBAQ8Qr4OqZAYRICPTSr5WKd/42rV36d+8/VmrNpv7NP1jQAXrQE4BqQUARngwVA==""" + ... .decode("base64").decode("zlib")) + + $ hg debugindex a.i + rev offset length delta linkrev nodeid p1 p2 + 0 0 19 -1 2 99e0332bd498 000000000000 000000000000 + 1 19 12 0 3 6674f57a23d8 99e0332bd498 000000000000 + $ hg debugdata a.i 1 2>&1 | grep decoded + mpatch.mpatchError: patch cannot be decoded diff -r c4b727795d6a -r ff0d3b6b287f tests/test-subrepo-git.t --- a/tests/test-subrepo-git.t Fri Mar 25 23:05:32 2016 -0700 +++ b/tests/test-subrepo-git.t Tue Mar 29 12:29:00 2016 -0500 @@ -1132,4 +1132,36 @@ ? s/foobar.orig ? s/snake.python.orig +test for Git CVE-2016-3068 + $ hg init malicious-subrepository + $ cd malicious-subrepository + $ echo "s = [git]ext::sh -c echo% pwned% >&2" > .hgsub + $ git init s + Initialized empty Git repository in $TESTTMP/tc/malicious-subrepository/s/.git/ + $ cd s + $ git commit --allow-empty -m 'empty' + [master (root-commit) 153f934] empty $ cd .. + $ hg add .hgsub + $ hg commit -m "add subrepo" + $ cd .. + $ env -u GIT_ALLOW_PROTOCOL hg clone malicious-subrepository malicious-subrepository-protected + Cloning into '$TESTTMP/tc/malicious-subrepository-protected/s'... + fatal: transport 'ext' not allowed + updating to branch default + cloning subrepo s from ext::sh -c echo% pwned% >&2 + abort: git clone error 128 in s (in subrepo s) + [255] + +whitelisting of ext should be respected (that's the git submodule behaviour) + $ env GIT_ALLOW_PROTOCOL=ext hg clone malicious-subrepository malicious-subrepository-clone-allowed + Cloning into '$TESTTMP/tc/malicious-subrepository-clone-allowed/s'... + pwned + fatal: Could not read from remote repository. + + Please make sure you have the correct access rights + and the repository exists. + updating to branch default + cloning subrepo s from ext::sh -c echo% pwned% >&2 + abort: git clone error 128 in s (in subrepo s) + [255]