SECURITY update. No CVE. Changes backported from 4.4.1.

"It is possible that a specially malformed repository can cause Git
subrepositories to run arbitrary code in the form of a .git/hooks/post-update
script checked in to the repository in Mercurial 4.4 and earlier. Typical use
of Mercurial prevents construction of such repositories, but they can be
created programmatically."
This commit is contained in:
juanfra 2017-11-09 19:28:46 +00:00
parent 10beb87663
commit f89871d86a
11 changed files with 482 additions and 1 deletions

View File

@ -1,10 +1,11 @@
# $OpenBSD: Makefile,v 1.80 2017/10/06 20:43:52 juanfra Exp $
# $OpenBSD: Makefile,v 1.81 2017/11/09 19:28:46 juanfra Exp $
COMMENT-main = fast, lightweight source control management
COMMENT-x11 = graphical tooling for mercurial
# Keep in sync with devel/tortoisehg and devel/py-hg-git
MODPY_EGG_VERSION = 4.3.3
REVISION = 0
DISTNAME = mercurial-${MODPY_EGG_VERSION}
CATEGORIES = devel

View File

@ -0,0 +1,24 @@
$OpenBSD: patch-mercurial_configitems_py,v 1.2 2017/11/09 19:28:46 juanfra Exp $
Index: mercurial/configitems.py
--- mercurial/configitems.py.orig
+++ mercurial/configitems.py
@@ -403,6 +403,18 @@ coreconfigitem('smtp', 'username',
coreconfigitem('sparse', 'missingwarning',
default=True,
)
+coreconfigitem('subrepos', 'allowed',
+ default=dynamicdefault, # to make backporting simpler
+)
+coreconfigitem('subrepos', 'hg:allowed',
+ default=dynamicdefault,
+)
+coreconfigitem('subrepos', 'git:allowed',
+ default=dynamicdefault,
+)
+coreconfigitem('subrepos', 'svn:allowed',
+ default=dynamicdefault,
+)
coreconfigitem('trusted', 'groups',
default=list,
)

View File

@ -0,0 +1,53 @@
$OpenBSD: patch-mercurial_help_config_txt,v 1.2 2017/11/09 19:28:46 juanfra Exp $
Index: mercurial/help/config.txt
--- mercurial/help/config.txt.orig
+++ mercurial/help/config.txt
@@ -1824,6 +1824,47 @@ rewrite rules are then applied on the full (absolute)
doesn't match the full path, an attempt is made to apply it on the
relative path alone. The rules are applied in definition order.
+``subrepos``
+------------
+
+This section contains options that control the behavior of the
+subrepositories feature. See also :hg:`help subrepos`.
+
+Security note: auditing in Mercurial is known to be insufficient to
+prevent clone-time code execution with carefully constructed Git
+subrepos. It is unknown if a similar detect is present in Subversion
+subrepos. Both Git and Subversion subrepos are disabled by default
+out of security concerns. These subrepo types can be enabled using
+the respective options below.
+
+``allowed``
+ Whether subrepositories are allowed in the working directory.
+
+ When false, commands involving subrepositories (like :hg:`update`)
+ will fail for all subrepository types.
+ (default: true)
+
+``hg:allowed``
+ Whether Mercurial subrepositories are allowed in the working
+ directory. This option only has an effect if ``subrepos.allowed``
+ is true.
+ (default: true)
+
+``git:allowed``
+ Whether Git subrepositories are allowed in the working directory.
+ This option only has an effect if ``subrepos.allowed`` is true.
+
+ See the security note above before enabling Git subrepos.
+ (default: false)
+
+``svn:allowed``
+ Whether Subversion subrepositories are allowed in the working
+ directory. This option only has an effect if ``subrepos.allowed``
+ is true.
+
+ See the security note above before enabling Subversion subrepos.
+ (default: false)
+
``templatealias``
-----------------

View File

@ -0,0 +1,67 @@
$OpenBSD: patch-mercurial_subrepo_py,v 1.2 2017/11/09 19:28:46 juanfra Exp $
Index: mercurial/subrepo.py
--- mercurial/subrepo.py.orig
+++ mercurial/subrepo.py
@@ -359,6 +359,33 @@ def _sanitize(ui, vfs, ignore):
"in '%s'\n") % vfs.join(dirname))
vfs.unlink(vfs.reljoin(dirname, f))
+def _auditsubrepopath(repo, path):
+ # auditor doesn't check if the path itself is a symlink
+ pathutil.pathauditor(repo.root)(path)
+ if repo.wvfs.islink(path):
+ raise error.Abort(_("subrepo '%s' traverses symbolic link") % path)
+
+SUBREPO_ALLOWED_DEFAULTS = {
+ 'hg': True,
+ 'git': False,
+ 'svn': False,
+}
+
+def _checktype(ui, kind):
+ # subrepos.allowed is a master kill switch. If disabled, subrepos are
+ # disabled period.
+ if not ui.configbool('subrepos', 'allowed', True):
+ raise error.Abort(_('subrepos not enabled'),
+ hint=_("see 'hg help config.subrepos' for details"))
+
+ default = SUBREPO_ALLOWED_DEFAULTS.get(kind, False)
+ if not ui.configbool('subrepos', '%s:allowed' % kind, default):
+ raise error.Abort(_('%s subrepos not allowed') % kind,
+ hint=_("see 'hg help config.subrepos' for details"))
+
+ if kind not in types:
+ raise error.Abort(_('unknown subrepo type %s') % kind)
+
def subrepo(ctx, path, allowwdir=False, allowcreate=True):
"""return instance of the right subrepo class for subrepo in path"""
# subrepo inherently violates our import layering rules
@@ -369,10 +396,10 @@ def subrepo(ctx, path, allowwdir=False, allowcreate=Tr
from . import hg as h
hg = h
- pathutil.pathauditor(ctx.repo().root)(path)
+ repo = ctx.repo()
+ _auditsubrepopath(repo, path)
state = ctx.substate[path]
- if state[2] not in types:
- raise error.Abort(_('unknown subrepo type %s') % state[2])
+ _checktype(repo.ui, state[2])
if allowwdir:
state = (state[0], ctx.subrev(path), state[2])
return types[state[2]](ctx, path, state[:2], allowcreate)
@@ -387,10 +414,10 @@ def nullsubrepo(ctx, path, pctx):
from . import hg as h
hg = h
- pathutil.pathauditor(ctx.repo().root)(path)
+ repo = ctx.repo()
+ _auditsubrepopath(repo, path)
state = ctx.substate[path]
- if state[2] not in types:
- raise error.Abort(_('unknown subrepo type %s') % state[2])
+ _checktype(repo.ui, state[2])
subrev = ''
if state[2] == 'hg':
subrev = "0" * 40

View File

@ -0,0 +1,138 @@
$OpenBSD: patch-tests_test-audit-subrepo_t,v 1.2 2017/11/09 19:28:46 juanfra Exp $
Index: tests/test-audit-subrepo.t
--- tests/test-audit-subrepo.t.orig
+++ tests/test-audit-subrepo.t
@@ -0,0 +1,132 @@
+Test illegal name
+-----------------
+
+on commit:
+
+ $ hg init hgname
+ $ cd hgname
+ $ mkdir sub
+ $ hg init sub/.hg
+ $ echo 'sub/.hg = sub/.hg' >> .hgsub
+ $ hg ci -qAm 'add subrepo "sub/.hg"'
+ abort: path 'sub/.hg' is inside nested repo 'sub'
+ [255]
+
+prepare tampered repo (including the commit above):
+
+ $ hg import --bypass -qm 'add subrepo "sub/.hg"' - <<'EOF'
+ > diff --git a/.hgsub b/.hgsub
+ > new file mode 100644
+ > --- /dev/null
+ > +++ b/.hgsub
+ > @@ -0,0 +1,1 @@
+ > +sub/.hg = sub/.hg
+ > diff --git a/.hgsubstate b/.hgsubstate
+ > new file mode 100644
+ > --- /dev/null
+ > +++ b/.hgsubstate
+ > @@ -0,0 +1,1 @@
+ > +0000000000000000000000000000000000000000 sub/.hg
+ > EOF
+ $ cd ..
+
+on clone (and update):
+
+ $ hg clone -q hgname hgname2
+ abort: path 'sub/.hg' is inside nested repo 'sub'
+ [255]
+
+Test direct symlink traversal
+-----------------------------
+
+#if symlink
+
+on commit:
+
+ $ mkdir hgsymdir
+ $ hg init hgsymdir/root
+ $ cd hgsymdir/root
+ $ ln -s ../out
+ $ hg ci -qAm 'add symlink "out"'
+ $ hg init ../out
+ $ echo 'out = out' >> .hgsub
+ $ hg ci -qAm 'add subrepo "out"'
+ abort: subrepo 'out' traverses symbolic link
+ [255]
+
+prepare tampered repo (including the commit above):
+
+ $ hg import --bypass -qm 'add subrepo "out"' - <<'EOF'
+ > diff --git a/.hgsub b/.hgsub
+ > new file mode 100644
+ > --- /dev/null
+ > +++ b/.hgsub
+ > @@ -0,0 +1,1 @@
+ > +out = out
+ > diff --git a/.hgsubstate b/.hgsubstate
+ > new file mode 100644
+ > --- /dev/null
+ > +++ b/.hgsubstate
+ > @@ -0,0 +1,1 @@
+ > +0000000000000000000000000000000000000000 out
+ > EOF
+ $ cd ../..
+
+on clone (and update):
+
+ $ mkdir hgsymdir2
+ $ hg clone -q hgsymdir/root hgsymdir2/root
+ abort: subrepo 'out' traverses symbolic link
+ [255]
+ $ ls hgsymdir2
+ root
+
+#endif
+
+Test indirect symlink traversal
+-------------------------------
+
+#if symlink
+
+on commit:
+
+ $ mkdir hgsymin
+ $ hg init hgsymin/root
+ $ cd hgsymin/root
+ $ ln -s ../out
+ $ hg ci -qAm 'add symlink "out"'
+ $ mkdir ../out
+ $ hg init ../out/sub
+ $ echo 'out/sub = out/sub' >> .hgsub
+ $ hg ci -qAm 'add subrepo "out/sub"'
+ abort: path 'out/sub' traverses symbolic link 'out'
+ [255]
+
+prepare tampered repo (including the commit above):
+
+ $ hg import --bypass -qm 'add subrepo "out/sub"' - <<'EOF'
+ > diff --git a/.hgsub b/.hgsub
+ > new file mode 100644
+ > --- /dev/null
+ > +++ b/.hgsub
+ > @@ -0,0 +1,1 @@
+ > +out/sub = out/sub
+ > diff --git a/.hgsubstate b/.hgsubstate
+ > new file mode 100644
+ > --- /dev/null
+ > +++ b/.hgsubstate
+ > @@ -0,0 +1,1 @@
+ > +0000000000000000000000000000000000000000 out/sub
+ > EOF
+ $ cd ../..
+
+on clone (and update):
+
+ $ mkdir hgsymin2
+ $ hg clone -q hgsymin/root hgsymin2/root
+ abort: path 'out/sub' traverses symbolic link 'out'
+ [255]
+ $ ls hgsymin2
+ root
+
+#endif

View File

@ -0,0 +1,12 @@
$OpenBSD: patch-tests_test-clonebundles_t.orig,v 1.1 2017/11/09 19:28:46 juanfra Exp $
--- tests/test-clonebundles.t.orig Wed May 3 15:20:10 2017
+++ tests/test-clonebundles.t Wed May 3 15:20:33 2017
@@ -51,7 +51,7 @@ Manifest file with invalid URL aborts
$ echo 'http://does.not.exist/bundle.hg' > server/.hg/clonebundles.manifest
$ hg clone http://localhost:$HGPORT 404-url
applying clone bundle from http://does.not.exist/bundle.hg
- error fetching bundle: (.* not known|getaddrinfo failed|No address associated with hostname) (re)
+ error fetching bundle: (.* not known|getaddrinfo failed|No address associated with hostname|no address associated with name) (re)
abort: error applying bundle
(if this error persists, consider contacting the server operator or disable clone bundles via "--config ui.clonebundles=false")
[255]

View File

@ -0,0 +1,16 @@
$OpenBSD: patch-tests_test-convert-git_t,v 1.2 2017/11/09 19:28:46 juanfra Exp $
Index: tests/test-convert-git.t
--- tests/test-convert-git.t.orig
+++ tests/test-convert-git.t
@@ -6,6 +6,10 @@
$ echo "autocrlf = false" >> $HOME/.gitconfig
$ echo "[extensions]" >> $HGRCPATH
$ echo "convert=" >> $HGRCPATH
+ $ cat >> $HGRCPATH <<EOF
+ > [subrepos]
+ > git:allowed = true
+ > EOF
$ GIT_AUTHOR_NAME='test'; export GIT_AUTHOR_NAME
$ GIT_AUTHOR_EMAIL='test@example.org'; export GIT_AUTHOR_EMAIL
$ GIT_AUTHOR_DATE="2007-01-01 00:00:00 +0000"; export GIT_AUTHOR_DATE

View File

@ -0,0 +1,15 @@
$OpenBSD: patch-tests_test-mq-subrepo-svn_t,v 1.2 2017/11/09 19:28:46 juanfra Exp $
Index: tests/test-mq-subrepo-svn.t
--- tests/test-mq-subrepo-svn.t.orig
+++ tests/test-mq-subrepo-svn.t
@@ -5,6 +5,9 @@
> mq =
> [diff]
> nodates = 1
+ > [subrepos]
+ > allowed = true
+ > svn:allowed = true
> EOF
fn to create new repository, and cd into it

View File

@ -0,0 +1,75 @@
$OpenBSD: patch-tests_test-subrepo-git_t,v 1.2 2017/11/09 19:28:46 juanfra Exp $
Index: tests/test-subrepo-git.t
--- tests/test-subrepo-git.t.orig
+++ tests/test-subrepo-git.t
@@ -41,7 +41,23 @@ add subrepo clone
$ echo 's = [git]../gitroot' > .hgsub
$ git clone -q ../gitroot s
$ hg add .hgsub
+
+git subrepo is disabled by default
+
$ hg commit -m 'new git subrepo'
+ abort: git subrepos not allowed
+ (see 'hg help config.subrepos' for details)
+ [255]
+
+so enable it
+
+ $ cat >> $HGRCPATH <<EOF
+ > [subrepos]
+ > git:allowed = true
+ > EOF
+
+ $ hg commit -m 'new git subrepo'
+
$ hg debugsub
path s
source ../gitroot
@@ -86,9 +102,29 @@ clone root
path s
source ../gitroot
revision 126f2a14290cd5ce061fdedc430170e8d39e1c5a
+ $ cd ..
+clone with subrepo disabled (update should fail)
+
+ $ hg clone t -U tc2 --config subrepos.allowed=false
+ $ hg update -R tc2 --config subrepos.allowed=false
+ abort: subrepos not enabled
+ (see 'hg help config.subrepos' for details)
+ [255]
+ $ ls tc2
+ a
+
+ $ hg clone t tc3 --config subrepos.allowed=false
+ updating to branch default
+ abort: subrepos not enabled
+ (see 'hg help config.subrepos' for details)
+ [255]
+ $ ls tc3
+ a
+
update to previous substate
+ $ cd tc
$ hg update 1 -q
$ cat s/g
g
@@ -399,11 +435,13 @@ Don't crash if the subrepo is missing
Don't crash if subrepo is a broken symlink
$ ln -s broken s
$ hg status -S
+ abort: subrepo 's' traverses symbolic link
+ [255]
$ hg push -q
- abort: subrepo s is missing (in subrepository "s")
+ abort: subrepo 's' traverses symbolic link
[255]
$ hg commit --subrepos -qm missing
- abort: subrepo s is missing (in subrepository "s")
+ abort: subrepo 's' traverses symbolic link
[255]
$ rm s
#endif

View File

@ -0,0 +1,27 @@
$OpenBSD: patch-tests_test-subrepo-svn_t,v 1.2 2017/11/09 19:28:46 juanfra Exp $
Index: tests/test-subrepo-svn.t
--- tests/test-subrepo-svn.t.orig
+++ tests/test-subrepo-svn.t
@@ -57,6 +57,21 @@ add first svn sub with leading whitespaces
$ mkdir subdir
$ svn co --quiet "$SVNREPOURL"/src subdir/s
$ hg add .hgsub
+
+svn subrepo is disabled by default
+
+ $ hg ci -m1
+ abort: svn subrepos not allowed
+ (see 'hg help config.subrepos' for details)
+ [255]
+
+so enable it
+
+ $ cat >> $HGRCPATH <<EOF
+ > [subrepos]
+ > svn:allowed = true
+ > EOF
+
$ hg ci -m1
make sure we avoid empty commits (issue2445)

View File

@ -0,0 +1,53 @@
$OpenBSD: patch-tests_test-subrepo_t,v 1.2 2017/11/09 19:28:46 juanfra Exp $
Index: tests/test-subrepo.t
--- tests/test-subrepo.t.orig
+++ tests/test-subrepo.t
@@ -484,9 +484,47 @@ clone
path t
source t
revision 20a0db6fbf6c3d2836e6519a642ae929bfc67c0e
+ $ cd ..
+clone with subrepo disabled (update should fail)
+
+ $ hg clone t -U tc2 --config subrepos.allowed=false
+ $ hg update -R tc2 --config subrepos.allowed=false
+ abort: subrepos not enabled
+ (see 'hg help config.subrepos' for details)
+ [255]
+ $ ls tc2
+ a
+
+ $ hg clone t tc3 --config subrepos.allowed=false
+ updating to branch default
+ abort: subrepos not enabled
+ (see 'hg help config.subrepos' for details)
+ [255]
+ $ ls tc3
+ a
+
+And again with just the hg type disabled
+
+ $ hg clone t -U tc4 --config subrepos.hg:allowed=false
+ $ hg update -R tc4 --config subrepos.hg:allowed=false
+ abort: hg subrepos not allowed
+ (see 'hg help config.subrepos' for details)
+ [255]
+ $ ls tc4
+ a
+
+ $ hg clone t tc5 --config subrepos.hg:allowed=false
+ updating to branch default
+ abort: hg subrepos not allowed
+ (see 'hg help config.subrepos' for details)
+ [255]
+ $ ls tc5
+ a
+
push
+ $ cd tc
$ echo bah > t/t
$ hg ci -m11
committing subrepository t