blob: 2971545b598e5cf93f2dd002438ab532b24339e5 [file] [log] [blame]
#!/usr/bin/env python2
# Licensed under the same terms as Subversion: the Apache License, Version 2.0
#
# pre-commit hook script for Subversion CVE-2017-9800
#
# This prevents commits that set svn:externals containing suspicions
# svn+ssh:// URLs.
#
# With this script installed a commit like the one below should fail:
#
# svnmucc -mm propset svn:externals 'svn+ssh://-localhost/X X' REPOSITORY-URL
import sys, locale, urllib, urlparse, curses.ascii
from svn import wc, repos, fs
# A simple whitelist to ensure these are not suspicious:
# user@server
# [::1]:22
# server-name
# server_name
# 127.0.0.1
# with an extra restriction that a leading '-' is suspicious.
def suspicious_host(host):
if host[0] == '-':
return True
for char in host:
if not curses.ascii.isalnum(char) and not char in ':.-_[]@':
return True
return False
native = locale.getlocale()[1]
if not native: native = 'ascii'
repos_handle = repos.open(sys.argv[1].decode(native).encode('utf-8'))
fs_handle = repos.fs(repos_handle)
txn_handle = fs.open_txn(fs_handle, sys.argv[2].decode(native).encode('utf-8'))
txn_root = fs.txn_root(txn_handle)
rev_root = fs.revision_root(fs_handle, fs.txn_root_base_revision(txn_root))
for path, change in fs.paths_changed2(txn_root).iteritems():
if change.prop_mod:
# The new value, if any
txn_prop = fs.node_prop(txn_root, path, "svn:externals")
if not txn_prop:
continue
# The old value, if any
rev_prop = None
if change.change_kind == fs.path_change_modify:
rev_prop = fs.node_prop(rev_root, path, "svn:externals")
elif change.change_kind == fs.path_change_add and change.copyfrom_path:
copy_root = fs.revision_root(fs_handle, change.copyfrom_rev)
rev_prop = fs.node_prop(copy_root, change.copyfrom_path,
"svn:externals")
if txn_prop != rev_prop:
error_path = path.decode('utf-8').encode(native, 'replace')
externals = []
try:
externals = wc.parse_externals_description2(path, txn_prop)
except:
sys.stderr.write("Commit blocked due to parse failure "
"on svn:externals for %s\n" % error_path)
sys.exit(1)
for external in externals:
parsed = urlparse.urlparse(urllib.unquote(external.url))
if (parsed and parsed.scheme[:4] == "svn+"
and suspicious_host(parsed.netloc)):
sys.stderr.write("Commit blocked due to suspicious URL "
"containing %r in svn:externals "
"for %s\n" % (parsed.netloc, error_path))
sys.exit(1)