Mercurial > public > mercurial-scm > hg-stable
diff mercurial/wireproto.py @ 36768:7bf80d9d9543
merge with stable
There were a handful of merge conflicts in the wire protocol code due
to significant refactoring in default. When resolving the conflicts,
I tried to produce the minimal number of changes to make the incoming
security patches work with the new code.
I will send some follow-up commits to get the security patches better
integrated into default.
author | Gregory Szorc <gregory.szorc@gmail.com> |
---|---|
date | Tue, 06 Mar 2018 14:32:14 -0800 |
parents | 390d16ea7c76 ff4bc0ab6740 |
children | 0b18604db95e |
line wrap: on
line diff
--- a/mercurial/wireproto.py Sun Mar 04 21:16:36 2018 -0500 +++ b/mercurial/wireproto.py Tue Mar 06 14:32:14 2018 -0800 @@ -672,6 +672,11 @@ commands = commanddict() +# Maps wire protocol name to operation type. This is used for permissions +# checking. All defined @wireiprotocommand should have an entry in this +# dict. +permissions = {} + def wireprotocommand(name, args='', transportpolicy=POLICY_ALL): """Decorator to declare a wire protocol command. @@ -701,6 +706,8 @@ return func return register +# TODO define a more appropriate permissions type to use for this. +permissions['batch'] = 'pull' @wireprotocommand('batch', 'cmds *') def batch(repo, proto, cmds, others): repo = repo.filtered("served") @@ -713,6 +720,17 @@ n, v = a.split('=') vals[unescapearg(n)] = unescapearg(v) func, spec = commands[op] + + # If the protocol supports permissions checking, perform that + # checking on each batched command. + # TODO formalize permission checking as part of protocol interface. + if util.safehasattr(proto, 'checkperm'): + # Assume commands with no defined permissions are writes / for + # pushes. This is the safest from a security perspective because + # it doesn't allow commands with undefined semantics from + # bypassing permissions checks. + proto.checkperm(permissions.get(op, 'push')) + if spec: keys = spec.split() data = {} @@ -740,6 +758,7 @@ return bytesresponse(';'.join(res)) +permissions['between'] = 'pull' @wireprotocommand('between', 'pairs', transportpolicy=POLICY_V1_ONLY) def between(repo, proto, pairs): pairs = [decodelist(p, '-') for p in pairs.split(" ")] @@ -749,6 +768,7 @@ return bytesresponse(''.join(r)) +permissions['branchmap'] = 'pull' @wireprotocommand('branchmap') def branchmap(repo, proto): branchmap = repo.branchmap() @@ -760,6 +780,7 @@ return bytesresponse('\n'.join(heads)) +permissions['branches'] = 'pull' @wireprotocommand('branches', 'nodes', transportpolicy=POLICY_V1_ONLY) def branches(repo, proto, nodes): nodes = decodelist(nodes) @@ -769,6 +790,7 @@ return bytesresponse(''.join(r)) +permissions['clonebundles'] = 'pull' @wireprotocommand('clonebundles', '') def clonebundles(repo, proto): """Server command for returning info for available bundles to seed clones. @@ -821,10 +843,12 @@ # If you are writing an extension and consider wrapping this function. Wrap # `_capabilities` instead. +permissions['capabilities'] = 'pull' @wireprotocommand('capabilities') def capabilities(repo, proto): return bytesresponse(' '.join(_capabilities(repo, proto))) +permissions['changegroup'] = 'pull' @wireprotocommand('changegroup', 'roots', transportpolicy=POLICY_V1_ONLY) def changegroup(repo, proto, roots): nodes = decodelist(roots) @@ -834,6 +858,7 @@ gen = iter(lambda: cg.read(32768), '') return streamres(gen=gen) +permissions['changegroupsubset'] = 'pull' @wireprotocommand('changegroupsubset', 'bases heads', transportpolicy=POLICY_V1_ONLY) def changegroupsubset(repo, proto, bases, heads): @@ -845,6 +870,7 @@ gen = iter(lambda: cg.read(32768), '') return streamres(gen=gen) +permissions['debugwireargs'] = 'pull' @wireprotocommand('debugwireargs', 'one two *') def debugwireargs(repo, proto, one, two, others): # only accept optional args from the known set @@ -852,6 +878,7 @@ return bytesresponse(repo.debugwireargs(one, two, **pycompat.strkwargs(opts))) +permissions['getbundle'] = 'pull' @wireprotocommand('getbundle', '*') def getbundle(repo, proto, others): opts = options('getbundle', gboptsmap.keys(), others) @@ -918,11 +945,13 @@ return streamres(gen=chunks, prefer_uncompressed=not prefercompressed) +permissions['heads'] = 'pull' @wireprotocommand('heads') def heads(repo, proto): h = repo.heads() return bytesresponse(encodelist(h) + '\n') +permissions['hello'] = 'pull' @wireprotocommand('hello') def hello(repo, proto): """Called as part of SSH handshake to obtain server info. @@ -938,11 +967,13 @@ caps = capabilities(repo, proto).data return bytesresponse('capabilities: %s\n' % caps) +permissions['listkeys'] = 'pull' @wireprotocommand('listkeys', 'namespace') def listkeys(repo, proto, namespace): d = sorted(repo.listkeys(encoding.tolocal(namespace)).items()) return bytesresponse(pushkeymod.encodekeys(d)) +permissions['lookup'] = 'pull' @wireprotocommand('lookup', 'key') def lookup(repo, proto, key): try: @@ -955,11 +986,13 @@ success = 0 return bytesresponse('%d %s\n' % (success, r)) +permissions['known'] = 'pull' @wireprotocommand('known', 'nodes *') def known(repo, proto, nodes, others): v = ''.join(b and '1' or '0' for b in repo.known(decodelist(nodes))) return bytesresponse(v) +permissions['pushkey'] = 'push' @wireprotocommand('pushkey', 'namespace key old new') def pushkey(repo, proto, namespace, key, old, new): # compatibility with pre-1.8 clients which were accidentally @@ -981,6 +1014,7 @@ output = output.getvalue() if output else '' return bytesresponse('%d\n%s' % (int(r), output)) +permissions['stream_out'] = 'pull' @wireprotocommand('stream_out') def stream(repo, proto): '''If the server supports streaming clone, it advertises the "stream" @@ -989,6 +1023,7 @@ ''' return streamres_legacy(streamclone.generatev1wireproto(repo)) +permissions['unbundle'] = 'push' @wireprotocommand('unbundle', 'heads') def unbundle(repo, proto, heads): their_heads = decodelist(heads)