##// END OF EJS Templates
dirs: resolve fuzzer OOM situation by disallowing deep directory hierarchies...
Augie Fackler -
r44057:0796e266 default
parent child Browse files
Show More
@@ -1,313 +1,327
1 1 /*
2 2 dirs.c - dynamic directory diddling for dirstates
3 3
4 4 Copyright 2013 Facebook
5 5
6 6 This software may be used and distributed according to the terms of
7 7 the GNU General Public License, incorporated herein by reference.
8 8 */
9 9
10 10 #define PY_SSIZE_T_CLEAN
11 11 #include <Python.h>
12 #include <string.h>
12 13
13 14 #include "util.h"
14 15
15 16 #ifdef IS_PY3K
16 17 #define PYLONG_VALUE(o) ((PyLongObject *)o)->ob_digit[1]
17 18 #else
18 19 #define PYLONG_VALUE(o) PyInt_AS_LONG(o)
19 20 #endif
20 21
21 22 /*
22 23 * This is a multiset of directory names, built from the files that
23 24 * appear in a dirstate or manifest.
24 25 *
25 26 * A few implementation notes:
26 27 *
27 28 * We modify Python integers for refcounting, but those integers are
28 29 * never visible to Python code.
29 30 */
30 31 /* clang-format off */
31 32 typedef struct {
32 33 PyObject_HEAD
33 34 PyObject *dict;
34 35 } dirsObject;
35 36 /* clang-format on */
36 37
37 38 static inline Py_ssize_t _finddir(const char *path, Py_ssize_t pos)
38 39 {
39 40 while (pos != -1) {
40 41 if (path[pos] == '/')
41 42 break;
42 43 pos -= 1;
43 44 }
44 45 if (pos == -1) {
45 46 return 0;
46 47 }
47 48
48 49 return pos;
49 50 }
50 51
52 /* Mercurial will fail to run on directory hierarchies deeper than
53 * this constant, so we should try and keep this constant as big as
54 * possible.
55 */
56 #define MAX_DIRS_DEPTH 2048
57
51 58 static int _addpath(PyObject *dirs, PyObject *path)
52 59 {
53 60 const char *cpath = PyBytes_AS_STRING(path);
54 61 Py_ssize_t pos = PyBytes_GET_SIZE(path);
55 62 PyObject *key = NULL;
56 63 int ret = -1;
64 size_t num_slashes = 0;
57 65
58 66 /* This loop is super critical for performance. That's why we inline
59 67 * access to Python structs instead of going through a supported API.
60 68 * The implementation, therefore, is heavily dependent on CPython
61 69 * implementation details. We also commit violations of the Python
62 70 * "protocol" such as mutating immutable objects. But since we only
63 71 * mutate objects created in this function or in other well-defined
64 72 * locations, the references are known so these violations should go
65 73 * unnoticed. */
66 74 while ((pos = _finddir(cpath, pos - 1)) != -1) {
67 75 PyObject *val;
76 ++num_slashes;
77 if (num_slashes > MAX_DIRS_DEPTH) {
78 PyErr_SetString(PyExc_ValueError,
79 "Directory hierarchy too deep.");
80 goto bail;
81 }
68 82
69 83 /* Sniff for trailing slashes, a marker of an invalid input. */
70 84 if (pos > 0 && cpath[pos - 1] == '/') {
71 85 PyErr_SetString(
72 86 PyExc_ValueError,
73 87 "found invalid consecutive slashes in path");
74 88 goto bail;
75 89 }
76 90
77 91 key = PyBytes_FromStringAndSize(cpath, pos);
78 92 if (key == NULL)
79 93 goto bail;
80 94
81 95 val = PyDict_GetItem(dirs, key);
82 96 if (val != NULL) {
83 97 PYLONG_VALUE(val) += 1;
84 98 Py_CLEAR(key);
85 99 break;
86 100 }
87 101
88 102 /* Force Python to not reuse a small shared int. */
89 103 #ifdef IS_PY3K
90 104 val = PyLong_FromLong(0x1eadbeef);
91 105 #else
92 106 val = PyInt_FromLong(0x1eadbeef);
93 107 #endif
94 108
95 109 if (val == NULL)
96 110 goto bail;
97 111
98 112 PYLONG_VALUE(val) = 1;
99 113 ret = PyDict_SetItem(dirs, key, val);
100 114 Py_DECREF(val);
101 115 if (ret == -1)
102 116 goto bail;
103 117 Py_CLEAR(key);
104 118 }
105 119 ret = 0;
106 120
107 121 bail:
108 122 Py_XDECREF(key);
109 123
110 124 return ret;
111 125 }
112 126
113 127 static int _delpath(PyObject *dirs, PyObject *path)
114 128 {
115 129 char *cpath = PyBytes_AS_STRING(path);
116 130 Py_ssize_t pos = PyBytes_GET_SIZE(path);
117 131 PyObject *key = NULL;
118 132 int ret = -1;
119 133
120 134 while ((pos = _finddir(cpath, pos - 1)) != -1) {
121 135 PyObject *val;
122 136
123 137 key = PyBytes_FromStringAndSize(cpath, pos);
124 138
125 139 if (key == NULL)
126 140 goto bail;
127 141
128 142 val = PyDict_GetItem(dirs, key);
129 143 if (val == NULL) {
130 144 PyErr_SetString(PyExc_ValueError,
131 145 "expected a value, found none");
132 146 goto bail;
133 147 }
134 148
135 149 if (--PYLONG_VALUE(val) <= 0) {
136 150 if (PyDict_DelItem(dirs, key) == -1)
137 151 goto bail;
138 152 } else
139 153 break;
140 154 Py_CLEAR(key);
141 155 }
142 156 ret = 0;
143 157
144 158 bail:
145 159 Py_XDECREF(key);
146 160
147 161 return ret;
148 162 }
149 163
150 164 static int dirs_fromdict(PyObject *dirs, PyObject *source, char skipchar)
151 165 {
152 166 PyObject *key, *value;
153 167 Py_ssize_t pos = 0;
154 168
155 169 while (PyDict_Next(source, &pos, &key, &value)) {
156 170 if (!PyBytes_Check(key)) {
157 171 PyErr_SetString(PyExc_TypeError, "expected string key");
158 172 return -1;
159 173 }
160 174 if (skipchar) {
161 175 if (!dirstate_tuple_check(value)) {
162 176 PyErr_SetString(PyExc_TypeError,
163 177 "expected a dirstate tuple");
164 178 return -1;
165 179 }
166 180 if (((dirstateTupleObject *)value)->state == skipchar)
167 181 continue;
168 182 }
169 183
170 184 if (_addpath(dirs, key) == -1)
171 185 return -1;
172 186 }
173 187
174 188 return 0;
175 189 }
176 190
177 191 static int dirs_fromiter(PyObject *dirs, PyObject *source)
178 192 {
179 193 PyObject *iter, *item = NULL;
180 194 int ret;
181 195
182 196 iter = PyObject_GetIter(source);
183 197 if (iter == NULL)
184 198 return -1;
185 199
186 200 while ((item = PyIter_Next(iter)) != NULL) {
187 201 if (!PyBytes_Check(item)) {
188 202 PyErr_SetString(PyExc_TypeError, "expected string");
189 203 break;
190 204 }
191 205
192 206 if (_addpath(dirs, item) == -1)
193 207 break;
194 208 Py_CLEAR(item);
195 209 }
196 210
197 211 ret = PyErr_Occurred() ? -1 : 0;
198 212 Py_DECREF(iter);
199 213 Py_XDECREF(item);
200 214 return ret;
201 215 }
202 216
203 217 /*
204 218 * Calculate a refcounted set of directory names for the files in a
205 219 * dirstate.
206 220 */
207 221 static int dirs_init(dirsObject *self, PyObject *args)
208 222 {
209 223 PyObject *dirs = NULL, *source = NULL;
210 224 char skipchar = 0;
211 225 int ret = -1;
212 226
213 227 self->dict = NULL;
214 228
215 229 if (!PyArg_ParseTuple(args, "|Oc:__init__", &source, &skipchar))
216 230 return -1;
217 231
218 232 dirs = PyDict_New();
219 233
220 234 if (dirs == NULL)
221 235 return -1;
222 236
223 237 if (source == NULL)
224 238 ret = 0;
225 239 else if (PyDict_Check(source))
226 240 ret = dirs_fromdict(dirs, source, skipchar);
227 241 else if (skipchar)
228 242 PyErr_SetString(PyExc_ValueError,
229 243 "skip character is only supported "
230 244 "with a dict source");
231 245 else
232 246 ret = dirs_fromiter(dirs, source);
233 247
234 248 if (ret == -1)
235 249 Py_XDECREF(dirs);
236 250 else
237 251 self->dict = dirs;
238 252
239 253 return ret;
240 254 }
241 255
242 256 PyObject *dirs_addpath(dirsObject *self, PyObject *args)
243 257 {
244 258 PyObject *path;
245 259
246 260 if (!PyArg_ParseTuple(args, "O!:addpath", &PyBytes_Type, &path))
247 261 return NULL;
248 262
249 263 if (_addpath(self->dict, path) == -1)
250 264 return NULL;
251 265
252 266 Py_RETURN_NONE;
253 267 }
254 268
255 269 static PyObject *dirs_delpath(dirsObject *self, PyObject *args)
256 270 {
257 271 PyObject *path;
258 272
259 273 if (!PyArg_ParseTuple(args, "O!:delpath", &PyBytes_Type, &path))
260 274 return NULL;
261 275
262 276 if (_delpath(self->dict, path) == -1)
263 277 return NULL;
264 278
265 279 Py_RETURN_NONE;
266 280 }
267 281
268 282 static int dirs_contains(dirsObject *self, PyObject *value)
269 283 {
270 284 return PyBytes_Check(value) ? PyDict_Contains(self->dict, value) : 0;
271 285 }
272 286
273 287 static void dirs_dealloc(dirsObject *self)
274 288 {
275 289 Py_XDECREF(self->dict);
276 290 PyObject_Del(self);
277 291 }
278 292
279 293 static PyObject *dirs_iter(dirsObject *self)
280 294 {
281 295 return PyObject_GetIter(self->dict);
282 296 }
283 297
284 298 static PySequenceMethods dirs_sequence_methods;
285 299
286 300 static PyMethodDef dirs_methods[] = {
287 301 {"addpath", (PyCFunction)dirs_addpath, METH_VARARGS, "add a path"},
288 302 {"delpath", (PyCFunction)dirs_delpath, METH_VARARGS, "remove a path"},
289 303 {NULL} /* Sentinel */
290 304 };
291 305
292 306 static PyTypeObject dirsType = {PyVarObject_HEAD_INIT(NULL, 0)};
293 307
294 308 void dirs_module_init(PyObject *mod)
295 309 {
296 310 dirs_sequence_methods.sq_contains = (objobjproc)dirs_contains;
297 311 dirsType.tp_name = "parsers.dirs";
298 312 dirsType.tp_new = PyType_GenericNew;
299 313 dirsType.tp_basicsize = sizeof(dirsObject);
300 314 dirsType.tp_dealloc = (destructor)dirs_dealloc;
301 315 dirsType.tp_as_sequence = &dirs_sequence_methods;
302 316 dirsType.tp_flags = Py_TPFLAGS_DEFAULT;
303 317 dirsType.tp_doc = "dirs";
304 318 dirsType.tp_iter = (getiterfunc)dirs_iter;
305 319 dirsType.tp_methods = dirs_methods;
306 320 dirsType.tp_init = (initproc)dirs_init;
307 321
308 322 if (PyType_Ready(&dirsType) < 0)
309 323 return;
310 324 Py_INCREF(&dirsType);
311 325
312 326 PyModule_AddObject(mod, "dirs", (PyObject *)&dirsType);
313 327 }
General Comments 0
You need to be logged in to leave comments. Login now