# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1546951914 -32400
#      Tue Jan 08 21:51:54 2019 +0900
# Branch stable
# Node ID 31286c9282dfa734e9da085649b7ae5a8ba290ad
# Parent  8427fea0401708ba95a27810163c17cee54ed4b0
subrepo: extend path auditing test to include more weird patterns (SEC)

While reviewing patches for the issue 5739, "$foo in repository path
expanded", I realized that subrepo paths can also be cheated. This patch
includes various subrepo paths which are potentially unsafe.

Since an expanded subrepo path isn't audited, this bug allows symlink check
bypass. As a result, a malicious subrepository could be checked out to a
sub tree of e.g. $HOME directory. The good news is that the destination
directory must be empty or nonexistent, so the existing ~/.bashrc wouldn't
be overwritten. See the last part of the tests for details.

diff -r 8427fea04017 -r 31286c9282df tests/test-audit-subrepo.t
--- a/tests/test-audit-subrepo.t	Thu Jan 31 13:32:21 2019 +0800
+++ b/tests/test-audit-subrepo.t	Tue Jan 08 21:51:54 2019 +0900
@@ -36,6 +36,330 @@
   abort: path 'sub/.hg' is inside nested repo 'sub'
   [255]
 
+Test absolute path
+------------------
+
+on commit:
+
+  $ hg init absolutepath
+  $ cd absolutepath
+  $ hg init sub
+  $ echo '/sub = sub' >> .hgsub
+  $ hg ci -qAm 'add subrepo "/sub"'
+  abort: path contains illegal component: /sub
+  [255]
+
+prepare tampered repo (including the commit above):
+
+  $ hg import --bypass -qm 'add subrepo "/sub"' - <<'EOF'
+  > diff --git a/.hgsub b/.hgsub
+  > new file mode 100644
+  > --- /dev/null
+  > +++ b/.hgsub
+  > @@ -0,0 +1,1 @@
+  > +/sub = sub
+  > diff --git a/.hgsubstate b/.hgsubstate
+  > new file mode 100644
+  > --- /dev/null
+  > +++ b/.hgsubstate
+  > @@ -0,0 +1,1 @@
+  > +0000000000000000000000000000000000000000 /sub
+  > EOF
+  $ cd ..
+
+on clone (and update):
+
+  $ hg clone -q absolutepath absolutepath2
+  abort: path contains illegal component: /sub
+  [255]
+
+Test root path
+--------------
+
+on commit:
+
+  $ hg init rootpath
+  $ cd rootpath
+  $ hg init sub
+  $ echo '/ = sub' >> .hgsub
+  $ hg ci -qAm 'add subrepo "/"'
+  abort: path ends in directory separator: /
+  [255]
+
+prepare tampered repo (including the commit above):
+
+  $ hg import --bypass -qm 'add subrepo "/"' - <<'EOF'
+  > diff --git a/.hgsub b/.hgsub
+  > new file mode 100644
+  > --- /dev/null
+  > +++ b/.hgsub
+  > @@ -0,0 +1,1 @@
+  > +/ = sub
+  > diff --git a/.hgsubstate b/.hgsubstate
+  > new file mode 100644
+  > --- /dev/null
+  > +++ b/.hgsubstate
+  > @@ -0,0 +1,1 @@
+  > +0000000000000000000000000000000000000000 /
+  > EOF
+  $ cd ..
+
+on clone (and update):
+
+  $ hg clone -q rootpath rootpath2
+  abort: path ends in directory separator: /
+  [255]
+
+Test empty path
+---------------
+
+on commit:
+
+  $ hg init emptypath
+  $ cd emptypath
+  $ hg init sub
+  $ echo '= sub' >> .hgsub
+  $ hg ci -qAm 'add subrepo ""'
+  hg: parse error at .hgsub:1: = sub
+  [255]
+
+prepare tampered repo (including the commit above):
+
+  $ hg import --bypass -qm 'add subrepo ""' - <<'EOF'
+  > diff --git a/.hgsub b/.hgsub
+  > new file mode 100644
+  > --- /dev/null
+  > +++ b/.hgsub
+  > @@ -0,0 +1,1 @@
+  > += sub
+  > diff --git a/.hgsubstate b/.hgsubstate
+  > new file mode 100644
+  > --- /dev/null
+  > +++ b/.hgsubstate
+  > @@ -0,0 +1,1 @@
+  > +0000000000000000000000000000000000000000
+  > EOF
+  $ cd ..
+
+on clone (and update):
+
+  $ hg clone -q emptypath emptypath2
+  hg: parse error at .hgsub:1: = sub
+  [255]
+
+Test current path
+-----------------
+
+on commit:
+BROKEN: should fail
+
+  $ hg init currentpath
+  $ cd currentpath
+  $ hg init sub
+  $ echo '. = sub' >> .hgsub
+  $ hg ci -qAm 'add subrepo "."'
+  $ cd ..
+
+on clone (and update):
+
+  $ hg clone -q currentpath currentpath2 --config ui.timeout=1
+  waiting for lock on working directory of $TESTTMP/currentpath2/. * (glob)
+  abort: working directory of $TESTTMP/currentpath2/.: timed out waiting for lock held by '*' (glob)
+  [255]
+
+Test outer path
+---------------
+
+on commit:
+
+  $ mkdir outerpath
+  $ cd outerpath
+  $ hg init main
+  $ cd main
+  $ hg init ../sub
+  $ echo '../sub = ../sub' >> .hgsub
+  $ hg ci -qAm 'add subrepo "../sub"'
+  abort: path contains illegal component: ../sub
+  [255]
+
+prepare tampered repo (including the commit above):
+
+  $ hg import --bypass -qm 'add subrepo "../sub"' - <<'EOF'
+  > diff --git a/.hgsub b/.hgsub
+  > new file mode 100644
+  > --- /dev/null
+  > +++ b/.hgsub
+  > @@ -0,0 +1,1 @@
+  > +../sub = ../sub
+  > diff --git a/.hgsubstate b/.hgsubstate
+  > new file mode 100644
+  > --- /dev/null
+  > +++ b/.hgsubstate
+  > @@ -0,0 +1,1 @@
+  > +0000000000000000000000000000000000000000 ../sub
+  > EOF
+  $ cd ..
+
+on clone (and update):
+
+  $ hg clone -q main main2
+  abort: path contains illegal component: ../sub
+  [255]
+  $ cd ..
+
+Test variable expansion
+-----------------------
+
+Subrepository paths shouldn't be expanded, but we fail to handle them
+properly. Any local repository paths are expanded.
+
+on commit:
+BROKEN: wrong error message
+
+  $ mkdir envvar
+  $ cd envvar
+  $ hg init main
+  $ cd main
+  $ hg init sub1
+  $ cat <<'EOF' > sub1/hgrc
+  > [hooks]
+  > log = echo pwned
+  > EOF
+  $ hg -R sub1 ci -qAm 'add sub1 files'
+  $ hg -R sub1 log -r. -T '{node}\n'
+  39eb4b4d3e096527668784893a9280578a8f38b8
+  $ echo '$SUB = sub1' >> .hgsub
+  $ SUB=sub1 hg ci -qAm 'add subrepo "$SUB"'
+  abort: repository $TESTTMP/envvar/main/$SUB already exists!
+  [255]
+
+prepare tampered repo (including the changes above as two commits):
+
+  $ hg import --bypass -qm 'add subrepo "$SUB"' - <<'EOF'
+  > diff --git a/.hgsub b/.hgsub
+  > new file mode 100644
+  > --- /dev/null
+  > +++ b/.hgsub
+  > @@ -0,0 +1,1 @@
+  > +$SUB = sub1
+  > diff --git a/.hgsubstate b/.hgsubstate
+  > new file mode 100644
+  > --- /dev/null
+  > +++ b/.hgsubstate
+  > @@ -0,0 +1,1 @@
+  > +0000000000000000000000000000000000000000 $SUB
+  > EOF
+  $ hg debugsetparents 0
+  $ hg import --bypass -qm 'update subrepo "$SUB"' - <<'EOF'
+  > diff --git a/.hgsubstate b/.hgsubstate
+  > --- a/.hgsubstate
+  > +++ b/.hgsubstate
+  > @@ -1,1 +1,1 @@
+  > -0000000000000000000000000000000000000000 $SUB
+  > +39eb4b4d3e096527668784893a9280578a8f38b8 $SUB
+  > EOF
+  $ cd ..
+
+on clone (and update) with various substitutions:
+
+  $ hg clone -q main main2
+  $ ls main2
+  $SUB
+
+  $ SUB=sub1 hg clone -q main main3
+  $ ls main3
+  sub1
+
+  $ SUB=sub2 hg clone -q main main4
+  $ ls main4
+  sub2
+
+on clone empty subrepo into .hg, then pull (and update), which at least fails:
+BROKEN: the first clone should fail
+
+  $ SUB=.hg hg clone -qr0 main main5
+  $ ls main5
+  $ ls -d main5/.hg/.hg
+  main5/.hg/.hg
+  $ SUB=.hg hg -R main5 pull -u
+  pulling from $TESTTMP/envvar/main
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 1 changes to 1 files
+  new changesets 7a2f0e59146f
+  abort: repository $TESTTMP/envvar/main5/$SUB already exists!
+  [255]
+  $ cat main5/.hg/hgrc | grep pwned
+  [1]
+
+on clone (and update) into .hg, which at least fails:
+
+  $ SUB=.hg hg clone -q main main6
+  abort: destination '$TESTTMP/envvar/main6/.hg' is not empty (in subrepository ".hg")
+  [255]
+  $ ls main6
+  $ cat main6/.hg/hgrc | grep pwned
+  [1]
+
+on clone (and update) into .hg/* subdir:
+BROKEN: should fail
+
+  $ SUB=.hg/foo hg clone -q main main7
+  $ ls main7
+  $ ls main7/.hg/foo
+  hgrc
+
+on clone (and update) into outer tree:
+BROKEN: should fail
+
+  $ SUB=../out-of-tree-write hg clone -q main main8
+  $ ls main8
+
+on clone (and update) into e.g. $HOME, which doesn't work since subrepo paths
+are concatenated prior to variable expansion:
+
+  $ SUB="$TESTTMP/envvar/fakehome" hg clone -q main main9
+  $ ls main9 | wc -l
+  \s*1 (re)
+
+  $ ls
+  main
+  main2
+  main3
+  main4
+  main5
+  main6
+  main7
+  main8
+  main9
+  out-of-tree-write
+  $ cd ..
+
+Test tilde
+----------
+
+The leading tilde may be expanded to $HOME, but it's a valid subrepo path.
+However, we might want to prohibit it as it seems potentially unsafe.
+
+on commit:
+
+  $ hg init tilde
+  $ cd tilde
+  $ hg init './~'
+  $ echo '~ = ~' >> .hgsub
+  $ hg ci -qAm 'add subrepo "~"'
+  $ ls
+  ~
+  $ cd ..
+
+on clone (and update):
+
+  $ hg clone -q tilde tilde2
+  $ ls tilde2
+  ~
+
 Test direct symlink traversal
 -----------------------------
 
@@ -130,3 +454,166 @@
   root
 
 #endif
+
+Test symlink traversal by variable expansion
+--------------------------------------------
+
+#if symlink
+
+  $ FAKEHOME="$TESTTMP/envvarsym/fakehome"
+
+on commit:
+BROKEN: wrong error message
+
+  $ mkdir envvarsym
+  $ cd envvarsym
+  $ hg init main
+  $ cd main
+  $ ln -s "`echo "$FAKEHOME" | sed 's|\(.\)/.*|\1|'`"
+  $ hg ci -qAm 'add symlink to top-level system directory'
+
+  $ hg init sub1
+  $ echo pwned > sub1/pwned
+  $ hg -R sub1 ci -qAm 'add sub1 files'
+  $ hg -R sub1 log -r. -T '{node}\n'
+  f40c9134ba1b6961e12f250868823f0092fb68a8
+  $ echo '$SUB = sub1' >> .hgsub
+  $ SUB="$FAKEHOME" hg ci -qAm 'add subrepo "$SUB"'
+  abort: repository $TESTTMP/envvarsym/main/$SUB already exists!
+  [255]
+
+prepare tampered repo (including the changes above as two commits):
+
+  $ hg import --bypass -qm 'add subrepo "$SUB"' - <<'EOF'
+  > diff --git a/.hgsub b/.hgsub
+  > new file mode 100644
+  > --- /dev/null
+  > +++ b/.hgsub
+  > @@ -0,0 +1,1 @@
+  > +$SUB = sub1
+  > diff --git a/.hgsubstate b/.hgsubstate
+  > new file mode 100644
+  > --- /dev/null
+  > +++ b/.hgsubstate
+  > @@ -0,0 +1,1 @@
+  > +0000000000000000000000000000000000000000 $SUB
+  > EOF
+  $ hg debugsetparents 1
+  $ hg import --bypass -qm 'update subrepo "$SUB"' - <<'EOF'
+  > diff --git a/.hgsubstate b/.hgsubstate
+  > --- a/.hgsubstate
+  > +++ b/.hgsubstate
+  > @@ -1,1 +1,1 @@
+  > -0000000000000000000000000000000000000000 $SUB
+  > +f40c9134ba1b6961e12f250868823f0092fb68a8 $SUB
+  > EOF
+  $ cd ..
+
+on clone (and update) without fakehome directory:
+BROKEN: should fail
+
+  $ rm -fR "$FAKEHOME"
+  $ SUB="$FAKEHOME" hg clone -q main main2
+  $ ls "$FAKEHOME"
+  pwned
+
+on clone (and update) with empty fakehome directory:
+BROKEN: should fail
+
+  $ rm -fR "$FAKEHOME"
+  $ mkdir "$FAKEHOME"
+  $ SUB="$FAKEHOME" hg clone -q main main3
+  $ ls "$FAKEHOME"
+  pwned
+
+on clone (and update) with non-empty fakehome directory:
+BROKEN: wrong error message
+
+  $ rm -fR "$FAKEHOME"
+  $ mkdir "$FAKEHOME"
+  $ touch "$FAKEHOME/a"
+  $ SUB="$FAKEHOME" hg clone -q main main4
+  abort: destination '$TESTTMP/envvarsym/fakehome' is not empty (in subrepository "*") (glob)
+  [255]
+  $ ls "$FAKEHOME"
+  a
+
+on clone empty subrepo with non-empty fakehome directory,
+then pull (and update):
+BROKEN: the first clone should fail
+
+  $ rm -fR "$FAKEHOME"
+  $ mkdir "$FAKEHOME"
+  $ touch "$FAKEHOME/a"
+  $ SUB="$FAKEHOME" hg clone -qr1 main main5
+  $ ls "$FAKEHOME"
+  a
+  $ ls -d "$FAKEHOME/.hg"
+  $TESTTMP/envvarsym/fakehome/.hg
+  $ SUB="$FAKEHOME" hg -R main5 pull -u
+  pulling from $TESTTMP/envvarsym/main
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 1 changes to 1 files
+  new changesets * (glob)
+  abort: repository $TESTTMP/envvarsym/main5/$SUB already exists!
+  [255]
+  $ ls "$FAKEHOME"
+  a
+
+on clone empty subrepo with hg-managed fakehome directory,
+then pull (and update):
+BROKEN: wrong error message
+
+  $ rm -fR "$FAKEHOME"
+  $ hg init "$FAKEHOME"
+  $ touch "$FAKEHOME/a"
+  $ hg -R "$FAKEHOME" ci -qAm 'add fakehome file'
+  $ SUB="$FAKEHOME" hg clone -qr1 main main6
+  abort: repository $TESTTMP/envvarsym/main6/$SUB already exists!
+  [255]
+  $ ls "$FAKEHOME"
+  a
+  $ SUB="$FAKEHOME" hg -R main6 pull -u
+  pulling from $TESTTMP/envvarsym/main
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 1 changes to 1 files
+  new changesets * (glob)
+  .hgsubstate: untracked file differs
+  abort: untracked files in working directory differ from files in requested revision
+  [255]
+  $ ls "$FAKEHOME"
+  a
+
+on clone only symlink with hg-managed fakehome directory,
+then pull (and update):
+BROKEN: wrong error message
+
+  $ rm -fR "$FAKEHOME"
+  $ hg init "$FAKEHOME"
+  $ touch "$FAKEHOME/a"
+  $ hg -R "$FAKEHOME" ci -qAm 'add fakehome file'
+  $ SUB="$FAKEHOME" hg clone -qr0 main main7
+  $ ls "$FAKEHOME"
+  a
+  $ SUB="$FAKEHOME" hg -R main7 pull -uf
+  pulling from $TESTTMP/envvarsym/main
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 2 changesets with 3 changes to 2 files
+  new changesets * (glob)
+  abort: repository $TESTTMP/envvarsym/main7/$SUB already exists!
+  [255]
+  $ ls "$FAKEHOME"
+  a
+
+  $ cd ..
+
+#endif
