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