merge with stable
authorMatt Mackall <mpm@selenic.com>
Tue, 29 Mar 2016 12:29:00 -0500
changeset 28670 ff0d3b6b287f
parent 28669 c4b727795d6a (current diff)
parent 28665 2d39f987f0ba (diff)
child 28671 96ed01f6514b
merge with stable
hgext/convert/common.py
hgext/convert/git.py
mercurial/subrepo.py
tests/test-subrepo-git.t
--- 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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
+ae279d4a19e9683214cbd1fe8298cf0b50571432 0 iQIVAwUAVvqzViBXgaxoKi1yAQKUCxAAtctMD3ydbe+li3iYjhY5qT0wyHwPr9fcLqsQUJ4ZtD4sK3oxCRZFWFxNBk5bIIyiwusSEJPiPddoQ7NljSZlYDI0HR3R4vns55fmDwPG07Ykf7aSyqr+c2ppCGzn2/2ID476FNtzKqjF+LkVyadgI9vgZk5S4BgdSlfSRBL+1KtB1BlF5etIZnc5U9qs1uqzZJc06xyyF8HlrmMZkAvRUbsx/JzA5LgzZ2WzueaxZgYzYjDk0nPLgyPPBj0DVyWXnW/kdRNmKHNbaZ9aZlWmdPCEoq5iBm71d7Xoa61shmeuVZWvxHNqXdjVMHVeT61cRxjdfxTIkJwvlRGwpy7V17vTgzWFxw6QJpmr7kupRo3idsDydLDPHGUsxP3uMZFsp6+4rEe6qbafjNajkRyiw7kVGCxboOFN0rLVJPZwZGksEIkw58IHcPhZNT1bHHocWOA/uHJTAynfKsAdv/LDdGKcZWUCFOzlokw54xbPvdrBtEOnYNp15OY01IAJd2FCUki5WHvhELUggTjfank1Tc3/Rt1KrGOFhg80CWq6eMiuiWkHGvYq3fjNLbgjl3JJatUFoB+cX1ulDOGsLJEXQ4v5DNHgel0o2H395owNlStksSeW1UBVk0hUK/ADtVUYKAPEIFiboh1iDpEOl40JVnYdsGz3w5FLj2w+16/1vWs=
--- 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
--- 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,))
--- 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
--- 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++;
 	}
 
--- 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
--- 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]
--- /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
--- 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]