# HG changeset patch
# User Augie Fackler <augie@google.com>
# 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__)

