@@ -161,15 +161,28 @@
ui.warn(_('warning: %s hook %s\n') % (name, desc))
return r
+# represent an untrusted hook command
+_fromuntrusted = object()
+
def _allhooks(ui):
"""return a list of (hook-id, cmd) pair sorted by priority"""
hooks = _hookitems(ui)
+ # Be careful in this section, propagating the real commands from untrusted
+ # sources would create a security vulnerability, make sure anything altered
+ # in that section uses "_fromuntrusted" as its command.
+ untrustedhooks = _hookitems(ui, _untrusted=True)
+ for name, value in untrustedhooks.items():
+ trustedvalue = hooks.get(name, (None, None, name, _fromuntrusted))
+ if value != trustedvalue:
+ (lp, lo, lk, lv) = trustedvalue
+ hooks[name] = (lp, lo, lk, _fromuntrusted)
+ # (end of the security sensitive section)
return [(k, v) for p, o, k, v in sorted(hooks.values())]
-def _hookitems(ui):
+def _hookitems(ui, _untrusted=False):
"""return all hooks items ready to be sorted"""
hooks = {}
- for name, cmd in ui.configitems('hooks'):
+ for name, cmd in ui.configitems('hooks', untrusted=_untrusted):
if not name.startswith('priority'):
priority = ui.configint('hooks', 'priority.%s' % name, 0)
hooks[name] = (-priority, len(hooks), name, cmd)
@@ -214,7 +227,16 @@
# files seem to be bogus, give up on redirecting (WSGI, etc)
pass
- if callable(cmd):
+ if cmd is _fromuntrusted:
+ abortmsg = _('%s hook forbidden (from untrusted config)')
+ warnmsg = _('warning: %s hook forbidden '
+ '(from untrusted config)\n')
+ if throw:
+ raise error.HookAbort(abortmsg % name)
+ ui.warn(warnmsg % name)
+ r = 1
+ raised = False
+ elif callable(cmd):
r, raised = _pythonhook(ui, repo, name, hname, cmd, args, throw)
elif cmd.startswith('python:'):
if cmd.count(':') >= 2:
@@ -794,7 +794,6 @@
date: Thu Jan 01 00:00:00 1970 +0000
summary: b
- $ cd ..
pretxnclose hook failure should abort the transaction
@@ -816,3 +815,61 @@
$ hg recover
no interrupted transaction available
[1]
+ $ cd ..
+
+Hook from untrusted hgrc are reported as failure
+================================================
+
+ $ cat << EOF > $TESTTMP/untrusted.py
+ > from mercurial import scmutil
+ > def uisetup(ui):
+ > class untrustedui(ui.__class__):
+ > def _trusted(self, fp, f):
+ > if fp.name.endswith('untrusted/.hg/hgrc'):
+ > return False
+ > return super(untrustedui, self)._trusted(fp, f)
+ > ui.__class__ = untrustedui
+ > EOF
+ $ cat << EOF >> $HGRCPATH
+ > [extensions]
+ > untrusted=$TESTTMP/untrusted.py
+ > EOF
+ $ hg init untrusted
+ $ cd untrusted
+
+Non-blocking hook
+-----------------
+
+ $ cat << EOF >> .hg/hgrc
+ > [hooks]
+ > txnclose.testing=echo txnclose hook called
+ > EOF
+ $ touch a && hg commit -Aqm a
+ warning: txnclose hook forbidden (from untrusted config)
+ $ hg log
+ changeset: 0:3903775176ed
+ tag: tip
+ user: test
+ date: Thu Jan 01 00:00:00 1970 +0000
+ summary: a
+
+
+Non-blocking hook
+-----------------
+
+ $ cat << EOF >> .hg/hgrc
+ > [hooks]
+ > pretxnclose.testing=echo pre-txnclose hook called
+ > EOF
+ $ touch b && hg commit -Aqm a
+ transaction abort!
+ rollback completed
+ abort: pretxnclose hook forbidden (from untrusted config)
+ [255]
+ $ hg log
+ changeset: 0:3903775176ed
+ tag: tip
+ user: test
+ date: Thu Jan 01 00:00:00 1970 +0000
+ summary: a
+