comparison mercurial/hook.py @ 28938:ea1fec3e9aba

hook: report untrusted hooks as failure (issue5110) (BC) Before this patch, there was no way for a repository owner to ensure that validation hooks would be run by people with write access. If someone had write access but did not trust the user owning the repository, the config and its hook would simply be ignored. After this patch, hooks from untrusted configs are taken into account but never actually run. Instead they are reported as failures right away. This will ensure validation performed by a hook is not ignored. As a side effect writer can be forced to trust a repository hgrc by adding a 'pretxnopen.trust=true' hook to the file. This was discussed during the 3.8 sprint with Matt Mackall, Augie Fackler and Kevin Bullock.
author Pierre-Yves David <pierre-yves.david@ens-lyon.org>
date Thu, 14 Apr 2016 02:41:15 -0700
parents 3112c5e18835
children a1259e502bdf
comparison
equal deleted inserted replaced
28937:3112c5e18835 28938:ea1fec3e9aba
159 if throw: 159 if throw:
160 raise error.HookAbort(_('%s hook %s') % (name, desc)) 160 raise error.HookAbort(_('%s hook %s') % (name, desc))
161 ui.warn(_('warning: %s hook %s\n') % (name, desc)) 161 ui.warn(_('warning: %s hook %s\n') % (name, desc))
162 return r 162 return r
163 163
164 # represent an untrusted hook command
165 _fromuntrusted = object()
166
164 def _allhooks(ui): 167 def _allhooks(ui):
165 """return a list of (hook-id, cmd) pairs sorted by priority""" 168 """return a list of (hook-id, cmd) pairs sorted by priority"""
166 hooks = _hookitems(ui) 169 hooks = _hookitems(ui)
170 # Be careful in this section, propagating the real commands from untrusted
171 # sources would create a security vulnerability, make sure anything altered
172 # in that section uses "_fromuntrusted" as its command.
173 untrustedhooks = _hookitems(ui, _untrusted=True)
174 for name, value in untrustedhooks.items():
175 trustedvalue = hooks.get(name, (None, None, name, _fromuntrusted))
176 if value != trustedvalue:
177 (lp, lo, lk, lv) = trustedvalue
178 hooks[name] = (lp, lo, lk, _fromuntrusted)
179 # (end of the security sensitive section)
167 return [(k, v) for p, o, k, v in sorted(hooks.values())] 180 return [(k, v) for p, o, k, v in sorted(hooks.values())]
168 181
169 def _hookitems(ui): 182 def _hookitems(ui, _untrusted=False):
170 """return all hooks items ready to be sorted""" 183 """return all hooks items ready to be sorted"""
171 hooks = {} 184 hooks = {}
172 for name, cmd in ui.configitems('hooks'): 185 for name, cmd in ui.configitems('hooks', untrusted=_untrusted):
173 if not name.startswith('priority'): 186 if not name.startswith('priority'):
174 priority = ui.configint('hooks', 'priority.%s' % name, 0) 187 priority = ui.configint('hooks', 'priority.%s' % name, 0)
175 hooks[name] = (-priority, len(hooks), name, cmd) 188 hooks[name] = (-priority, len(hooks), name, cmd)
176 return hooks 189 return hooks
177 190
212 os.dup2(stderrno, stdoutno) 225 os.dup2(stderrno, stdoutno)
213 except (OSError, AttributeError): 226 except (OSError, AttributeError):
214 # files seem to be bogus, give up on redirecting (WSGI, etc) 227 # files seem to be bogus, give up on redirecting (WSGI, etc)
215 pass 228 pass
216 229
217 if callable(cmd): 230 if cmd is _fromuntrusted:
231 if throw:
232 raise error.HookAbort(
233 _('untrusted hook %s not executed') % name,
234 hint = _("see 'hg help config.trusted'"))
235 ui.warn(_('warning: untrusted hook %s not executed\n') % name)
236 r = 1
237 raised = False
238 elif callable(cmd):
218 r, raised = _pythonhook(ui, repo, name, hname, cmd, args, throw) 239 r, raised = _pythonhook(ui, repo, name, hname, cmd, args, throw)
219 elif cmd.startswith('python:'): 240 elif cmd.startswith('python:'):
220 if cmd.count(':') >= 2: 241 if cmd.count(':') >= 2:
221 path, cmd = cmd[7:].rsplit(':', 1) 242 path, cmd = cmd[7:].rsplit(':', 1)
222 path = util.expandpath(path) 243 path = util.expandpath(path)