# HG changeset patch # User Augie Fackler # Date 1571354962 14400 # Node ID 5d40317d42b7083b49467502549e25f144888cb3 # Parent 3a463e5e470b40c275091a38b1a4464e36c0c5a4 dirs: reject consecutive slashes in paths We shouldn't ever see those, and the fuzzer go really excited that if it gives us a 65k string with 55k slashes in it we use a lot of RAM. This is a better fix than what I tried in D7105. It was suggested by Yuya, and I verified it does in fact cause the fuzzer to not OOM. This is a revision of D7234, but with the missing set of an error added. I added a unit test of the dirs behavior because I needed to reason more carefully about the failure modes around consecutive slashes. Differential Revision: https://phab.mercurial-scm.org/D7252 diff -r 3a463e5e470b -r 5d40317d42b7 mercurial/cext/dirs.c --- a/mercurial/cext/dirs.c Tue Nov 05 22:56:12 2019 -0500 +++ b/mercurial/cext/dirs.c Thu Oct 17 19:29:22 2019 -0400 @@ -66,6 +66,14 @@ while ((pos = _finddir(cpath, pos - 1)) != -1) { PyObject *val; + /* Sniff for trailing slashes, a marker of an invalid input. */ + if (pos > 0 && cpath[pos - 1] == '/') { + PyErr_SetString( + PyExc_ValueError, + "found invalid consecutive slashes in path"); + goto bail; + } + key = PyBytes_FromStringAndSize(cpath, pos); if (key == NULL) goto bail; diff -r 3a463e5e470b -r 5d40317d42b7 mercurial/util.py --- a/mercurial/util.py Tue Nov 05 22:56:12 2019 -0500 +++ b/mercurial/util.py Thu Oct 17 19:29:22 2019 -0400 @@ -3515,6 +3515,10 @@ def addpath(self, path): dirs = self._dirs for base in finddirs(path): + if base.endswith(b'/'): + raise ValueError( + "found invalid consecutive slashes in path: %r" % base + ) if base in dirs: dirs[base] += 1 return diff -r 3a463e5e470b -r 5d40317d42b7 tests/test-dirs.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-dirs.py Thu Oct 17 19:29:22 2019 -0400 @@ -0,0 +1,27 @@ +from __future__ import absolute_import + +import unittest + +import silenttestrunner + +from mercurial import util + + +class dirstests(unittest.TestCase): + def testdirs(self): + for case, want in [ + (b'a/a/a', [b'a', b'a/a', b'']), + (b'alpha/beta/gamma', [b'', b'alpha', b'alpha/beta']), + ]: + d = util.dirs({}) + d.addpath(case) + self.assertEqual(sorted(d), sorted(want)) + + def testinvalid(self): + with self.assertRaises(ValueError): + d = util.dirs({}) + d.addpath(b'a//b') + + +if __name__ == '__main__': + silenttestrunner.main(__name__)