Browse code

Python2 : Fix CVE-2017-1000030.

Change-Id: I6c6ac38ca4bfd0d571236489d447b28f2b0886d6
Reviewed-on: http://photon-jenkins.eng.vmware.com:8082/4977
Tested-by: gerrit-photon <photon-checkins@vmware.com>
Reviewed-by: Anish Swaminathan <anishs@vmware.com>

Xiaolin Li authored on 2018/04/06 07:46:06
Showing 3 changed files
1 1
new file mode 100644
... ...
@@ -0,0 +1,113 @@
0
+From 6401e5671781eb217ee1afb4603cc0d1b0367ae6 Mon Sep 17 00:00:00 2001
1
+From: Serhiy Storchaka <storchaka@gmail.com>
2
+Date: Fri, 10 Nov 2017 12:58:55 +0200
3
+Subject: [PATCH] [2.7] bpo-31530: Stop crashes when iterating over a file on
4
+ multiple threads. (#3672)
5
+
6
+---
7
+ Lib/test/test_file2k.py                            | 32 ++++++++++++++++++++++
8
+ .../2017-09-20-18-28-09.bpo-31530.CdLOM7.rst       |  4 +++
9
+ Objects/fileobject.c                               | 19 +++++++++++--
10
+ 3 files changed, 52 insertions(+), 3 deletions(-)
11
+ create mode 100644 Misc/NEWS.d/next/Core and Builtins/2017-09-20-18-28-09.bpo-31530.CdLOM7.rst
12
+
13
+diff --git a/Lib/test/test_file2k.py b/Lib/test/test_file2k.py
14
+index e39ef7042eae..d8966e034e08 100644
15
+--- a/Lib/test/test_file2k.py
16
+@@ -652,6 +652,38 @@ def io_func():
17
+             self.f.writelines('')
18
+         self._test_close_open_io(io_func)
19
+ 
20
++    def test_iteration_torture(self):
21
++        # bpo-31530: Crash when concurrently iterate over a file.
22
++        with open(self.filename, "wb") as fp:
23
++            for i in xrange(2**20):
24
++                fp.write(b"0"*50 + b"\n")
25
++        with open(self.filename, "rb") as f:
26
++            def iterate():
27
++                try:
28
++                    for l in f:
29
++                        pass
30
++                except IOError:
31
++                    pass
32
++            self._run_workers(iterate, 10)
33
++
34
++    def test_iteration_seek(self):
35
++        # bpo-31530: Crash when concurrently seek and iterate over a file.
36
++        with open(self.filename, "wb") as fp:
37
++            for i in xrange(10000):
38
++                fp.write(b"0"*50 + b"\n")
39
++        with open(self.filename, "rb") as f:
40
++            it = iter([1] + [0]*10)  # one thread reads, others seek
41
++            def iterate():
42
++                try:
43
++                    if next(it):
44
++                        for l in f:
45
++                            pass
46
++                    else:
47
++                        for i in range(100):
48
++                            f.seek(i*100, 0)
49
++                except IOError:
50
++                    pass
51
++            self._run_workers(iterate, 10)
52
+ 
53
+ @unittest.skipUnless(os.name == 'posix', 'test requires a posix system.')
54
+ class TestFileSignalEINTR(unittest.TestCase):
55
+diff --git a/Misc/NEWS.d/next/Core and Builtins/2017-09-20-18-28-09.bpo-31530.CdLOM7.rst b/Misc/NEWS.d/next/Core and Builtins/2017-09-20-18-28-09.bpo-31530.CdLOM7.rst
56
+new file mode 100644
57
+index 000000000000..a6cb6c9e9ba9
58
+--- /dev/null
59
+@@ -0,0 +1,4 @@
60
++Fixed crashes when iterating over a file on multiple threads.
61
++seek() and next() methods of file objects now raise an exception during
62
++concurrent operation on the same file object.
63
++A lock can be used to prevent the error.
64
+diff --git a/Objects/fileobject.c b/Objects/fileobject.c
65
+index 7e07a5376f88..2f63c374d1e2 100644
66
+--- a/Objects/fileobject.c
67
+@@ -762,6 +762,12 @@ file_seek(PyFileObject *f, PyObject *args)
68
+ 
69
+     if (f->f_fp == NULL)
70
+         return err_closed();
71
++    if (f->unlocked_count > 0) {
72
++        PyErr_SetString(PyExc_IOError,
73
++            "seek() called during concurrent "
74
++            "operation on the same file object");
75
++        return NULL;
76
++    }
77
+     drop_readahead(f);
78
+     whence = 0;
79
+     if (!PyArg_ParseTuple(args, "O|i:seek", &offobj, &whence))
80
+@@ -2238,6 +2244,7 @@ readahead(PyFileObject *f, Py_ssize_t bufsize)
81
+ {
82
+     Py_ssize_t chunksize;
83
+ 
84
++    assert(f->unlocked_count == 0);
85
+     if (f->f_buf != NULL) {
86
+         if( (f->f_bufend - f->f_bufptr) >= 1)
87
+             return 0;
88
+@@ -2279,6 +2286,12 @@ readahead_get_line_skip(PyFileObject *f, Py_ssize_t skip, Py_ssize_t bufsize)
89
+     char *buf;
90
+     Py_ssize_t len;
91
+ 
92
++    if (f->unlocked_count > 0) {
93
++        PyErr_SetString(PyExc_IOError,
94
++            "next() called during concurrent "
95
++            "operation on the same file object");
96
++        return NULL;
97
++    }
98
+     if (f->f_buf == NULL)
99
+         if (readahead(f, bufsize) < 0)
100
+             return NULL;
101
+@@ -2692,7 +2705,7 @@ int PyObject_AsFileDescriptor(PyObject *o)
102
+     }
103
+     else {
104
+         PyErr_SetString(PyExc_TypeError,
105
+-                        "argument must be an int, or have a fileno() method.");
106
++                        "argument must be an int, or have a fileno() method");
107
+         return -1;
108
+     }
109
+ 
0 110
new file mode 100644
... ...
@@ -0,0 +1,297 @@
0
+From dbf52e02f18dac6f5f0a64f78932f3dc6efc056b Mon Sep 17 00:00:00 2001
1
+From: Benjamin Peterson <benjamin@python.org>
2
+Date: Tue, 2 Jan 2018 09:25:41 -0800
3
+Subject: [PATCH] bpo-31530: fix crash when multiple threads iterate over a
4
+ file, round 2 (#5060)
5
+
6
+Multiple threads iterating over a file can corrupt the file's internal readahead
7
+buffer resulting in crashes. To fix this, cache buffer state thread-locally for
8
+the duration of a file_iternext call and only update the file's internal state
9
+after reading completes.
10
+
11
+No attempt is made to define or provide "reasonable" semantics for iterating
12
+over a file on multiple threads. (Non-crashing) races are still
13
+present. Duplicated, corrupt, and missing data will happen.
14
+
15
+This was originally fixed by 6401e5671781eb217ee1afb4603cc0d1b0367ae6, which
16
+raised an exception from seek() and next() when concurrent operations were
17
+detected. Alas, this simpler solution breaks legitimate use cases such as
18
+capturing the standard streams when multiple threads are logging.
19
+---
20
+ Lib/test/test_file2k.py                            |  27 ++---
21
+ .../2017-09-20-18-28-09.bpo-31530.CdLOM7.rst       |   3 -
22
+ Objects/fileobject.c                               | 118 ++++++++++++---------
23
+ 3 files changed, 78 insertions(+), 70 deletions(-)
24
+
25
+diff --git a/Lib/test/test_file2k.py b/Lib/test/test_file2k.py
26
+index d8966e034e08..c73e8d8dc450 100644
27
+--- a/Lib/test/test_file2k.py
28
+@@ -653,18 +653,15 @@ def io_func():
29
+         self._test_close_open_io(io_func)
30
+ 
31
+     def test_iteration_torture(self):
32
+-        # bpo-31530: Crash when concurrently iterate over a file.
33
++        # bpo-31530
34
+         with open(self.filename, "wb") as fp:
35
+             for i in xrange(2**20):
36
+                 fp.write(b"0"*50 + b"\n")
37
+         with open(self.filename, "rb") as f:
38
+-            def iterate():
39
+-                try:
40
+-                    for l in f:
41
+-                        pass
42
+-                except IOError:
43
++            def it():
44
++                for l in f:
45
+                     pass
46
+-            self._run_workers(iterate, 10)
47
++            self._run_workers(it, 10)
48
+ 
49
+     def test_iteration_seek(self):
50
+         # bpo-31530: Crash when concurrently seek and iterate over a file.
51
+@@ -674,17 +671,15 @@ def test_iteration_seek(self):
52
+         with open(self.filename, "rb") as f:
53
+             it = iter([1] + [0]*10)  # one thread reads, others seek
54
+             def iterate():
55
+-                try:
56
+-                    if next(it):
57
+-                        for l in f:
58
+-                            pass
59
+-                    else:
60
+-                        for i in range(100):
61
+-                            f.seek(i*100, 0)
62
+-                except IOError:
63
+-                    pass
64
++                if next(it):
65
++                    for l in f:
66
++                        pass
67
++                else:
68
++                    for i in xrange(100):
69
++                        f.seek(i*100, 0)
70
+             self._run_workers(iterate, 10)
71
+ 
72
++
73
+ @unittest.skipUnless(os.name == 'posix', 'test requires a posix system.')
74
+ class TestFileSignalEINTR(unittest.TestCase):
75
+     def _test_reading(self, data_to_write, read_and_verify_code, method_name,
76
+diff --git a/Misc/NEWS.d/next/Core and Builtins/2017-09-20-18-28-09.bpo-31530.CdLOM7.rst b/Misc/NEWS.d/next/Core and Builtins/2017-09-20-18-28-09.bpo-31530.CdLOM7.rst
77
+index a6cb6c9e9ba9..beb09b5ae650 100644
78
+--- a/Misc/NEWS.d/next/Core and Builtins/2017-09-20-18-28-09.bpo-31530.CdLOM7.rst	
79
+@@ -1,4 +1 @@
80
+ Fixed crashes when iterating over a file on multiple threads.
81
+-seek() and next() methods of file objects now raise an exception during
82
+-concurrent operation on the same file object.
83
+-A lock can be used to prevent the error.
84
+diff --git a/Objects/fileobject.c b/Objects/fileobject.c
85
+index 8d1c5812f0d2..270b28264a8f 100644
86
+--- a/Objects/fileobject.c
87
+@@ -609,7 +609,12 @@ err_iterbuffered(void)
88
+     return NULL;
89
+ }
90
+ 
91
+-static void drop_readahead(PyFileObject *);
92
++static void
93
++drop_file_readahead(PyFileObject *f)
94
++{
95
++    PyMem_FREE(f->f_buf);
96
++    f->f_buf = NULL;
97
++}
98
+ 
99
+ /* Methods */
100
+ 
101
+@@ -632,7 +637,7 @@ file_dealloc(PyFileObject *f)
102
+     Py_XDECREF(f->f_mode);
103
+     Py_XDECREF(f->f_encoding);
104
+     Py_XDECREF(f->f_errors);
105
+-    drop_readahead(f);
106
++    drop_file_readahead(f);
107
+     Py_TYPE(f)->tp_free((PyObject *)f);
108
+ }
109
+ 
110
+@@ -767,13 +772,7 @@ file_seek(PyFileObject *f, PyObject *args)
111
+ 
112
+     if (f->f_fp == NULL)
113
+         return err_closed();
114
+-    if (f->unlocked_count > 0) {
115
+-        PyErr_SetString(PyExc_IOError,
116
+-            "seek() called during concurrent "
117
+-            "operation on the same file object");
118
+-        return NULL;
119
+-    }
120
+-    drop_readahead(f);
121
++    drop_file_readahead(f);
122
+     whence = 0;
123
+     if (!PyArg_ParseTuple(args, "O|i:seek", &offobj, &whence))
124
+         return NULL;
125
+@@ -2242,12 +2241,16 @@ static PyGetSetDef file_getsetlist[] = {
126
+     {0},
127
+ };
128
+ 
129
++typedef struct {
130
++    char *buf, *bufptr, *bufend;
131
++} readaheadbuffer;
132
++
133
+ static void
134
+-drop_readahead(PyFileObject *f)
135
++drop_readaheadbuffer(readaheadbuffer *rab)
136
+ {
137
+-    if (f->f_buf != NULL) {
138
+-        PyMem_Free(f->f_buf);
139
+-        f->f_buf = NULL;
140
++    if (rab->buf != NULL) {
141
++        PyMem_FREE(rab->buf);
142
++        rab->buf = NULL;
143
+     }
144
+ }
145
+ 
146
+@@ -2255,36 +2258,34 @@ drop_readahead(PyFileObject *f)
147
+    (unless at EOF) and no more than bufsize.  Returns negative value on
148
+    error, will set MemoryError if bufsize bytes cannot be allocated. */
149
+ static int
150
+-readahead(PyFileObject *f, Py_ssize_t bufsize)
151
++readahead(PyFileObject *f, readaheadbuffer *rab, Py_ssize_t bufsize)
152
+ {
153
+     Py_ssize_t chunksize;
154
+ 
155
+-    assert(f->unlocked_count == 0);
156
+-    if (f->f_buf != NULL) {
157
+-        if( (f->f_bufend - f->f_bufptr) >= 1)
158
++    if (rab->buf != NULL) {
159
++        if ((rab->bufend - rab->bufptr) >= 1)
160
+             return 0;
161
+         else
162
+-            drop_readahead(f);
163
++            drop_readaheadbuffer(rab);
164
+     }
165
+-    if ((f->f_buf = (char *)PyMem_Malloc(bufsize)) == NULL) {
166
++    if ((rab->buf = PyMem_MALLOC(bufsize)) == NULL) {
167
+         PyErr_NoMemory();
168
+         return -1;
169
+     }
170
+     FILE_BEGIN_ALLOW_THREADS(f)
171
+     errno = 0;
172
+-    chunksize = Py_UniversalNewlineFread(
173
+-        f->f_buf, bufsize, f->f_fp, (PyObject *)f);
174
++    chunksize = Py_UniversalNewlineFread(rab->buf, bufsize, f->f_fp, (PyObject *)f);
175
+     FILE_END_ALLOW_THREADS(f)
176
+     if (chunksize == 0) {
177
+         if (ferror(f->f_fp)) {
178
+             PyErr_SetFromErrno(PyExc_IOError);
179
+             clearerr(f->f_fp);
180
+-            drop_readahead(f);
181
++            drop_readaheadbuffer(rab);
182
+             return -1;
183
+         }
184
+     }
185
+-    f->f_bufptr = f->f_buf;
186
+-    f->f_bufend = f->f_buf + chunksize;
187
++    rab->bufptr = rab->buf;
188
++    rab->bufend = rab->buf + chunksize;
189
+     return 0;
190
+ }
191
+ 
192
+@@ -2294,51 +2295,43 @@ readahead(PyFileObject *f, Py_ssize_t bufsize)
193
+    logarithmic buffer growth to about 50 even when reading a 1gb line. */
194
+ 
195
+ static PyStringObject *
196
+-readahead_get_line_skip(PyFileObject *f, Py_ssize_t skip, Py_ssize_t bufsize)
197
++readahead_get_line_skip(PyFileObject *f, readaheadbuffer *rab, Py_ssize_t skip, Py_ssize_t bufsize)
198
+ {
199
+     PyStringObject* s;
200
+     char *bufptr;
201
+     char *buf;
202
+     Py_ssize_t len;
203
+ 
204
+-    if (f->unlocked_count > 0) {
205
+-        PyErr_SetString(PyExc_IOError,
206
+-            "next() called during concurrent "
207
+-            "operation on the same file object");
208
+-        return NULL;
209
+-    }
210
+-    if (f->f_buf == NULL)
211
+-        if (readahead(f, bufsize) < 0)
212
++    if (rab->buf == NULL)
213
++        if (readahead(f, rab, bufsize) < 0)
214
+             return NULL;
215
+ 
216
+-    len = f->f_bufend - f->f_bufptr;
217
++    len = rab->bufend - rab->bufptr;
218
+     if (len == 0)
219
+-        return (PyStringObject *)
220
+-            PyString_FromStringAndSize(NULL, skip);
221
+-    bufptr = (char *)memchr(f->f_bufptr, '\n', len);
222
++        return (PyStringObject *)PyString_FromStringAndSize(NULL, skip);
223
++    bufptr = (char *)memchr(rab->bufptr, '\n', len);
224
+     if (bufptr != NULL) {
225
+         bufptr++;                               /* Count the '\n' */
226
+-        len = bufptr - f->f_bufptr;
227
+-        s = (PyStringObject *)
228
+-            PyString_FromStringAndSize(NULL, skip + len);
229
++        len = bufptr - rab->bufptr;
230
++        s = (PyStringObject *)PyString_FromStringAndSize(NULL, skip + len);
231
+         if (s == NULL)
232
+             return NULL;
233
+-        memcpy(PyString_AS_STRING(s) + skip, f->f_bufptr, len);
234
+-        f->f_bufptr = bufptr;
235
+-        if (bufptr == f->f_bufend)
236
+-            drop_readahead(f);
237
++        memcpy(PyString_AS_STRING(s) + skip, rab->bufptr, len);
238
++        rab->bufptr = bufptr;
239
++        if (bufptr == rab->bufend)
240
++            drop_readaheadbuffer(rab);
241
+     } else {
242
+-        bufptr = f->f_bufptr;
243
+-        buf = f->f_buf;
244
+-        f->f_buf = NULL;                /* Force new readahead buffer */
245
++        bufptr = rab->bufptr;
246
++        buf = rab->buf;
247
++        rab->buf = NULL;                /* Force new readahead buffer */
248
+         assert(len <= PY_SSIZE_T_MAX - skip);
249
+-        s = readahead_get_line_skip(f, skip + len, bufsize + (bufsize>>2));
250
++        s = readahead_get_line_skip(f, rab, skip + len, bufsize + (bufsize>>2));
251
+         if (s == NULL) {
252
+-            PyMem_Free(buf);
253
++            PyMem_FREE(buf);
254
+             return NULL;
255
+         }
256
+         memcpy(PyString_AS_STRING(s) + skip, bufptr, len);
257
+-        PyMem_Free(buf);
258
++        PyMem_FREE(buf);
259
+     }
260
+     return s;
261
+ }
262
+@@ -2356,7 +2349,30 @@ file_iternext(PyFileObject *f)
263
+     if (!f->readable)
264
+         return err_mode("reading");
265
+ 
266
+-    l = readahead_get_line_skip(f, 0, READAHEAD_BUFSIZE);
267
++    {
268
++        /*
269
++          Multiple threads can enter this method while the GIL is released
270
++          during file read and wreak havoc on the file object's readahead
271
++          buffer. To avoid dealing with cross-thread coordination issues, we
272
++          cache the file buffer state locally and only set it back on the file
273
++          object when we're done.
274
++        */
275
++        readaheadbuffer rab = {f->f_buf, f->f_bufptr, f->f_bufend};
276
++        f->f_buf = NULL;
277
++        l = readahead_get_line_skip(f, &rab, 0, READAHEAD_BUFSIZE);
278
++        /*
279
++          Make sure the file's internal read buffer is cleared out. This will
280
++          only do anything if some other thread interleaved with us during
281
++          readahead. We want to drop any changeling buffer, so we don't leak
282
++          memory. We may lose data, but that's what you get for reading the same
283
++          file object in multiple threads.
284
++        */
285
++        drop_file_readahead(f);
286
++        f->f_buf = rab.buf;
287
++        f->f_bufptr = rab.bufptr;
288
++        f->f_bufend = rab.bufend;
289
++    }
290
++
291
+     if (l == NULL || PyString_GET_SIZE(l) == 0) {
292
+         Py_XDECREF(l);
293
+         return NULL;
... ...
@@ -1,7 +1,7 @@
1 1
 Summary:        A high-level scripting language
2 2
 Name:           python2
3 3
 Version:        2.7.13
4
-Release:        11%{?dist}
4
+Release:        12%{?dist}
5 5
 License:        PSF
6 6
 URL:            http://www.python.org/
7 7
 Group:          System Environment/Programming
... ...
@@ -15,6 +15,9 @@ Patch2:         added-pyopenssl-ipaddress-certificate-validation.patch
15 15
 Patch3:         python2-support-photon-platform.patch
16 16
 Patch4:         back-port-random-dot-c.patch
17 17
 Patch5:         python2-CVE-2017-1000158.patch
18
+Patch6:         python2-CVE-2018-1000030-1.patch
19
+Patch7:         python2-CVE-2018-1000030-2.patch
20
+
18 21
 BuildRequires:  pkg-config >= 0.28
19 22
 BuildRequires:  bzip2-devel
20 23
 BuildRequires:  openssl-devel
... ...
@@ -117,6 +120,8 @@ The test package contains all regression tests for Python as well as the modules
117 117
 %patch3 -p1
118 118
 %patch4 -p1
119 119
 %patch5 -p1
120
+%patch6 -p1
121
+%patch7 -p1
120 122
 
121 123
 %build
122 124
 export OPT="${CFLAGS}"
... ...
@@ -238,6 +243,8 @@ make test
238 238
 %{_libdir}/python2.7/test/*
239 239
 
240 240
 %changelog
241
+*   Mon Dec 04 2017 Xiaolin Li <xiaolinl@vmware.com> 2.7.13-12
242
+-   Fix CVE-2017-1000030
241 243
 *   Mon Dec 04 2017 Xiaolin Li <xiaolinl@vmware.com> 2.7.13-11
242 244
 -   Fix CVE-2017-1000158
243 245
 *   Mon Sep 18 2017 Alexey Makhalov <amakhalov@vmware.com> 2.7.13-10