diff options
Diffstat (limited to 'meta/recipes-devtools/python')
-rw-r--r-- | meta/recipes-devtools/python/python3/CVE-2021-23336.patch | 530 | ||||
-rw-r--r-- | meta/recipes-devtools/python/python3_3.8.2.bb | 1 |
2 files changed, 0 insertions, 531 deletions
diff --git a/meta/recipes-devtools/python/python3/CVE-2021-23336.patch b/meta/recipes-devtools/python/python3/CVE-2021-23336.patch deleted file mode 100644 index 2a885b9d37..0000000000 --- a/meta/recipes-devtools/python/python3/CVE-2021-23336.patch +++ /dev/null | |||
@@ -1,530 +0,0 @@ | |||
1 | From 3ab6f812653e79d008d5eba31dc25d34f3ca7170 Mon Sep 17 00:00:00 2001 | ||
2 | From: Senthil Kumaran <senthil@uthcode.com> | ||
3 | Date: Mon, 15 Feb 2021 10:15:02 -0800 | ||
4 | Subject: [PATCH] bpo-42967: only use '&' as a query string separator | ||
5 | MIME-Version: 1.0 | ||
6 | Content-Type: text/plain; charset=UTF-8 | ||
7 | Content-Transfer-Encoding: 8bit | ||
8 | |||
9 | (GH-24297) (#24529) | ||
10 | MIME-Version: 1.0 | ||
11 | Content-Type: text/plain; charset=UTF-8 | ||
12 | Content-Transfer-Encoding: 8bit | ||
13 | |||
14 | * bpo-42967: only use '&' as a query string separator (#24297) | ||
15 | |||
16 | bpo-42967: [security] Address a web cache-poisoning issue reported in | ||
17 | urllib.parse.parse_qsl(). | ||
18 | |||
19 | urllib.parse will only us "&" as query string separator by default | ||
20 | instead of both ";" and "&" as allowed in earlier versions. An optional | ||
21 | argument seperator with default value "&" is added to specify the | ||
22 | separator. | ||
23 | |||
24 | Co-authored-by: Éric Araujo <merwok@netwok.org> | ||
25 | Co-authored-by: blurb-it[bot] <43283697+blurb-it[bot]@users.noreply.github.com> | ||
26 | Co-authored-by: Ken Jin <28750310+Fidget-Spinner@users.noreply.github.com> | ||
27 | Co-authored-by: Éric Araujo <merwok@netwok.org> | ||
28 | (cherry picked from commit fcbe0cb04d35189401c0c880ebfb4311e952d776) | ||
29 | |||
30 | * [3.8] bpo-42967: only use '&' as a query string separator (GH-24297) | ||
31 | |||
32 | bpo-42967: [security] Address a web cache-poisoning issue reported in urllib.parse.parse_qsl(). | ||
33 | |||
34 | urllib.parse will only us "&" as query string separator by default instead of both ";" and "&" as allowed in earlier versions. An optional argument seperator with default value "&" is added to specify the separator. | ||
35 | |||
36 | Co-authored-by: Éric Araujo <merwok@netwok.org> | ||
37 | Co-authored-by: blurb-it[bot] <43283697+blurb-it[bot]@users.noreply.github.com> | ||
38 | Co-authored-by: Ken Jin <28750310+Fidget-Spinner@users.noreply.github.com> | ||
39 | Co-authored-by: Éric Araujo <merwok@netwok.org>. | ||
40 | (cherry picked from commit fcbe0cb04d35189401c0c880ebfb4311e952d776) | ||
41 | |||
42 | Co-authored-by: Adam Goldschmidt <adamgold7@gmail.com> | ||
43 | |||
44 | * Update correct version information. | ||
45 | |||
46 | * fix docs and make logic clearer | ||
47 | |||
48 | Co-authored-by: Adam Goldschmidt <adamgold7@gmail.com> | ||
49 | Co-authored-by: Fidget-Spinner <28750310+Fidget-Spinner@users.noreply.github.com> | ||
50 | |||
51 | Upstream-Status: Backport [https://github.com/python/cpython/commit/e3110c3cfbb7daa690d54d0eff6c264c870a71bf] | ||
52 | CVE: CVE-2020-23336 | ||
53 | Signed-off-by: Chee Yang Lee <chee.yang.lee@intel.com> | ||
54 | |||
55 | --- | ||
56 | Doc/library/cgi.rst | 11 ++- | ||
57 | Doc/library/urllib.parse.rst | 22 +++++- | ||
58 | Doc/whatsnew/3.6.rst | 13 ++++ | ||
59 | Doc/whatsnew/3.7.rst | 13 ++++ | ||
60 | Lib/cgi.py | 23 ++++--- | ||
61 | Lib/test/test_cgi.py | 29 ++++++-- | ||
62 | Lib/test/test_urlparse.py | 68 +++++++++++++------ | ||
63 | Lib/urllib/parse.py | 19 ++++-- | ||
64 | .../2021-02-14-15-59-16.bpo-42967.YApqDS.rst | 1 + | ||
65 | 9 files changed, 153 insertions(+), 46 deletions(-) | ||
66 | create mode 100644 Misc/NEWS.d/next/Security/2021-02-14-15-59-16.bpo-42967.YApqDS.rst | ||
67 | |||
68 | diff --git a/Doc/library/cgi.rst b/Doc/library/cgi.rst | ||
69 | index 4048592..880074b 100644 | ||
70 | --- a/Doc/library/cgi.rst | ||
71 | +++ b/Doc/library/cgi.rst | ||
72 | @@ -277,14 +277,16 @@ These are useful if you want more control, or if you want to employ some of the | ||
73 | algorithms implemented in this module in other circumstances. | ||
74 | |||
75 | |||
76 | -.. function:: parse(fp=None, environ=os.environ, keep_blank_values=False, strict_parsing=False) | ||
77 | +.. function:: parse(fp=None, environ=os.environ, keep_blank_values=False, strict_parsing=False, separator="&") | ||
78 | |||
79 | Parse a query in the environment or from a file (the file defaults to | ||
80 | - ``sys.stdin``). The *keep_blank_values* and *strict_parsing* parameters are | ||
81 | + ``sys.stdin``). The *keep_blank_values*, *strict_parsing* and *separator* parameters are | ||
82 | passed to :func:`urllib.parse.parse_qs` unchanged. | ||
83 | |||
84 | + .. versionchanged:: 3.8.8 | ||
85 | + Added the *separator* parameter. | ||
86 | |||
87 | -.. function:: parse_multipart(fp, pdict, encoding="utf-8", errors="replace") | ||
88 | +.. function:: parse_multipart(fp, pdict, encoding="utf-8", errors="replace", separator="&") | ||
89 | |||
90 | Parse input of type :mimetype:`multipart/form-data` (for file uploads). | ||
91 | Arguments are *fp* for the input file, *pdict* for a dictionary containing | ||
92 | @@ -303,6 +305,9 @@ algorithms implemented in this module in other circumstances. | ||
93 | Added the *encoding* and *errors* parameters. For non-file fields, the | ||
94 | value is now a list of strings, not bytes. | ||
95 | |||
96 | + .. versionchanged:: 3.8.8 | ||
97 | + Added the *separator* parameter. | ||
98 | + | ||
99 | |||
100 | .. function:: parse_header(string) | ||
101 | |||
102 | diff --git a/Doc/library/urllib.parse.rst b/Doc/library/urllib.parse.rst | ||
103 | index 52f98ef..45ca03a 100644 | ||
104 | --- a/Doc/library/urllib.parse.rst | ||
105 | +++ b/Doc/library/urllib.parse.rst | ||
106 | @@ -165,7 +165,7 @@ or on combining URL components into a URL string. | ||
107 | now raise :exc:`ValueError`. | ||
108 | |||
109 | |||
110 | -.. function:: parse_qs(qs, keep_blank_values=False, strict_parsing=False, encoding='utf-8', errors='replace', max_num_fields=None) | ||
111 | +.. function:: parse_qs(qs, keep_blank_values=False, strict_parsing=False, encoding='utf-8', errors='replace', max_num_fields=None, separator='&') | ||
112 | |||
113 | Parse a query string given as a string argument (data of type | ||
114 | :mimetype:`application/x-www-form-urlencoded`). Data are returned as a | ||
115 | @@ -190,6 +190,9 @@ or on combining URL components into a URL string. | ||
116 | read. If set, then throws a :exc:`ValueError` if there are more than | ||
117 | *max_num_fields* fields read. | ||
118 | |||
119 | + The optional argument *separator* is the symbol to use for separating the | ||
120 | + query arguments. It defaults to ``&``. | ||
121 | + | ||
122 | Use the :func:`urllib.parse.urlencode` function (with the ``doseq`` | ||
123 | parameter set to ``True``) to convert such dictionaries into query | ||
124 | strings. | ||
125 | @@ -201,8 +204,14 @@ or on combining URL components into a URL string. | ||
126 | .. versionchanged:: 3.8 | ||
127 | Added *max_num_fields* parameter. | ||
128 | |||
129 | + .. versionchanged:: 3.8.8 | ||
130 | + Added *separator* parameter with the default value of ``&``. Python | ||
131 | + versions earlier than Python 3.8.8 allowed using both ``;`` and ``&`` as | ||
132 | + query parameter separator. This has been changed to allow only a single | ||
133 | + separator key, with ``&`` as the default separator. | ||
134 | + | ||
135 | |||
136 | -.. function:: parse_qsl(qs, keep_blank_values=False, strict_parsing=False, encoding='utf-8', errors='replace', max_num_fields=None) | ||
137 | +.. function:: parse_qsl(qs, keep_blank_values=False, strict_parsing=False, encoding='utf-8', errors='replace', max_num_fields=None, separator='&') | ||
138 | |||
139 | Parse a query string given as a string argument (data of type | ||
140 | :mimetype:`application/x-www-form-urlencoded`). Data are returned as a list of | ||
141 | @@ -226,6 +235,9 @@ or on combining URL components into a URL string. | ||
142 | read. If set, then throws a :exc:`ValueError` if there are more than | ||
143 | *max_num_fields* fields read. | ||
144 | |||
145 | + The optional argument *separator* is the symbol to use for separating the | ||
146 | + query arguments. It defaults to ``&``. | ||
147 | + | ||
148 | Use the :func:`urllib.parse.urlencode` function to convert such lists of pairs into | ||
149 | query strings. | ||
150 | |||
151 | @@ -235,6 +247,12 @@ or on combining URL components into a URL string. | ||
152 | .. versionchanged:: 3.8 | ||
153 | Added *max_num_fields* parameter. | ||
154 | |||
155 | + .. versionchanged:: 3.8.8 | ||
156 | + Added *separator* parameter with the default value of ``&``. Python | ||
157 | + versions earlier than Python 3.8.8 allowed using both ``;`` and ``&`` as | ||
158 | + query parameter separator. This has been changed to allow only a single | ||
159 | + separator key, with ``&`` as the default separator. | ||
160 | + | ||
161 | |||
162 | .. function:: urlunparse(parts) | ||
163 | |||
164 | diff --git a/Doc/whatsnew/3.6.rst b/Doc/whatsnew/3.6.rst | ||
165 | index 04c1f7e..4409a3a 100644 | ||
166 | --- a/Doc/whatsnew/3.6.rst | ||
167 | +++ b/Doc/whatsnew/3.6.rst | ||
168 | @@ -2443,3 +2443,16 @@ because of the behavior of the socket option ``SO_REUSEADDR`` in UDP. For more | ||
169 | details, see the documentation for ``loop.create_datagram_endpoint()``. | ||
170 | (Contributed by Kyle Stanley, Antoine Pitrou, and Yury Selivanov in | ||
171 | :issue:`37228`.) | ||
172 | + | ||
173 | +Notable changes in Python 3.6.13 | ||
174 | +================================ | ||
175 | + | ||
176 | +Earlier Python versions allowed using both ``;`` and ``&`` as | ||
177 | +query parameter separators in :func:`urllib.parse.parse_qs` and | ||
178 | +:func:`urllib.parse.parse_qsl`. Due to security concerns, and to conform with | ||
179 | +newer W3C recommendations, this has been changed to allow only a single | ||
180 | +separator key, with ``&`` as the default. This change also affects | ||
181 | +:func:`cgi.parse` and :func:`cgi.parse_multipart` as they use the affected | ||
182 | +functions internally. For more details, please see their respective | ||
183 | +documentation. | ||
184 | +(Contributed by Adam Goldschmidt, Senthil Kumaran and Ken Jin in :issue:`42967`.) | ||
185 | diff --git a/Doc/whatsnew/3.7.rst b/Doc/whatsnew/3.7.rst | ||
186 | index b9b5021..8f47a9f 100644 | ||
187 | --- a/Doc/whatsnew/3.7.rst | ||
188 | +++ b/Doc/whatsnew/3.7.rst | ||
189 | @@ -2556,3 +2556,16 @@ because of the behavior of the socket option ``SO_REUSEADDR`` in UDP. For more | ||
190 | details, see the documentation for ``loop.create_datagram_endpoint()``. | ||
191 | (Contributed by Kyle Stanley, Antoine Pitrou, and Yury Selivanov in | ||
192 | :issue:`37228`.) | ||
193 | + | ||
194 | +Notable changes in Python 3.7.10 | ||
195 | +================================ | ||
196 | + | ||
197 | +Earlier Python versions allowed using both ``;`` and ``&`` as | ||
198 | +query parameter separators in :func:`urllib.parse.parse_qs` and | ||
199 | +:func:`urllib.parse.parse_qsl`. Due to security concerns, and to conform with | ||
200 | +newer W3C recommendations, this has been changed to allow only a single | ||
201 | +separator key, with ``&`` as the default. This change also affects | ||
202 | +:func:`cgi.parse` and :func:`cgi.parse_multipart` as they use the affected | ||
203 | +functions internally. For more details, please see their respective | ||
204 | +documentation. | ||
205 | +(Contributed by Adam Goldschmidt, Senthil Kumaran and Ken Jin in :issue:`42967`.) | ||
206 | diff --git a/Lib/cgi.py b/Lib/cgi.py | ||
207 | index 5ace46a..13255a9 100755 | ||
208 | --- a/Lib/cgi.py | ||
209 | +++ b/Lib/cgi.py | ||
210 | @@ -106,7 +106,8 @@ log = initlog # The current logging function | ||
211 | # 0 ==> unlimited input | ||
212 | maxlen = 0 | ||
213 | |||
214 | -def parse(fp=None, environ=os.environ, keep_blank_values=0, strict_parsing=0): | ||
215 | +def parse(fp=None, environ=os.environ, keep_blank_values=0, | ||
216 | + strict_parsing=0, separator='&'): | ||
217 | """Parse a query in the environment or from a file (default stdin) | ||
218 | |||
219 | Arguments, all optional: | ||
220 | @@ -125,6 +126,9 @@ def parse(fp=None, environ=os.environ, keep_blank_values=0, strict_parsing=0): | ||
221 | strict_parsing: flag indicating what to do with parsing errors. | ||
222 | If false (the default), errors are silently ignored. | ||
223 | If true, errors raise a ValueError exception. | ||
224 | + | ||
225 | + separator: str. The symbol to use for separating the query arguments. | ||
226 | + Defaults to &. | ||
227 | """ | ||
228 | if fp is None: | ||
229 | fp = sys.stdin | ||
230 | @@ -145,7 +149,7 @@ def parse(fp=None, environ=os.environ, keep_blank_values=0, strict_parsing=0): | ||
231 | if environ['REQUEST_METHOD'] == 'POST': | ||
232 | ctype, pdict = parse_header(environ['CONTENT_TYPE']) | ||
233 | if ctype == 'multipart/form-data': | ||
234 | - return parse_multipart(fp, pdict) | ||
235 | + return parse_multipart(fp, pdict, separator=separator) | ||
236 | elif ctype == 'application/x-www-form-urlencoded': | ||
237 | clength = int(environ['CONTENT_LENGTH']) | ||
238 | if maxlen and clength > maxlen: | ||
239 | @@ -169,10 +173,10 @@ def parse(fp=None, environ=os.environ, keep_blank_values=0, strict_parsing=0): | ||
240 | qs = "" | ||
241 | environ['QUERY_STRING'] = qs # XXX Shouldn't, really | ||
242 | return urllib.parse.parse_qs(qs, keep_blank_values, strict_parsing, | ||
243 | - encoding=encoding) | ||
244 | + encoding=encoding, separator=separator) | ||
245 | |||
246 | |||
247 | -def parse_multipart(fp, pdict, encoding="utf-8", errors="replace"): | ||
248 | +def parse_multipart(fp, pdict, encoding="utf-8", errors="replace", separator='&'): | ||
249 | """Parse multipart input. | ||
250 | |||
251 | Arguments: | ||
252 | @@ -193,7 +197,7 @@ def parse_multipart(fp, pdict, encoding="utf-8", errors="replace"): | ||
253 | headers.set_type(ctype) | ||
254 | headers['Content-Length'] = pdict['CONTENT-LENGTH'] | ||
255 | fs = FieldStorage(fp, headers=headers, encoding=encoding, errors=errors, | ||
256 | - environ={'REQUEST_METHOD': 'POST'}) | ||
257 | + environ={'REQUEST_METHOD': 'POST'}, separator=separator) | ||
258 | return {k: fs.getlist(k) for k in fs} | ||
259 | |||
260 | def _parseparam(s): | ||
261 | @@ -303,7 +307,7 @@ class FieldStorage: | ||
262 | def __init__(self, fp=None, headers=None, outerboundary=b'', | ||
263 | environ=os.environ, keep_blank_values=0, strict_parsing=0, | ||
264 | limit=None, encoding='utf-8', errors='replace', | ||
265 | - max_num_fields=None): | ||
266 | + max_num_fields=None, separator='&'): | ||
267 | """Constructor. Read multipart/* until last part. | ||
268 | |||
269 | Arguments, all optional: | ||
270 | @@ -351,6 +355,7 @@ class FieldStorage: | ||
271 | self.keep_blank_values = keep_blank_values | ||
272 | self.strict_parsing = strict_parsing | ||
273 | self.max_num_fields = max_num_fields | ||
274 | + self.separator = separator | ||
275 | if 'REQUEST_METHOD' in environ: | ||
276 | method = environ['REQUEST_METHOD'].upper() | ||
277 | self.qs_on_post = None | ||
278 | @@ -577,7 +582,7 @@ class FieldStorage: | ||
279 | query = urllib.parse.parse_qsl( | ||
280 | qs, self.keep_blank_values, self.strict_parsing, | ||
281 | encoding=self.encoding, errors=self.errors, | ||
282 | - max_num_fields=self.max_num_fields) | ||
283 | + max_num_fields=self.max_num_fields, separator=self.separator) | ||
284 | self.list = [MiniFieldStorage(key, value) for key, value in query] | ||
285 | self.skip_lines() | ||
286 | |||
287 | @@ -593,7 +598,7 @@ class FieldStorage: | ||
288 | query = urllib.parse.parse_qsl( | ||
289 | self.qs_on_post, self.keep_blank_values, self.strict_parsing, | ||
290 | encoding=self.encoding, errors=self.errors, | ||
291 | - max_num_fields=self.max_num_fields) | ||
292 | + max_num_fields=self.max_num_fields, separator=self.separator) | ||
293 | self.list.extend(MiniFieldStorage(key, value) for key, value in query) | ||
294 | |||
295 | klass = self.FieldStorageClass or self.__class__ | ||
296 | @@ -637,7 +642,7 @@ class FieldStorage: | ||
297 | else self.limit - self.bytes_read | ||
298 | part = klass(self.fp, headers, ib, environ, keep_blank_values, | ||
299 | strict_parsing, limit, | ||
300 | - self.encoding, self.errors, max_num_fields) | ||
301 | + self.encoding, self.errors, max_num_fields, self.separator) | ||
302 | |||
303 | if max_num_fields is not None: | ||
304 | max_num_fields -= 1 | ||
305 | diff --git a/Lib/test/test_cgi.py b/Lib/test/test_cgi.py | ||
306 | index ab86771..bda03ee 100644 | ||
307 | --- a/Lib/test/test_cgi.py | ||
308 | +++ b/Lib/test/test_cgi.py | ||
309 | @@ -53,12 +53,9 @@ parse_strict_test_cases = [ | ||
310 | ("", ValueError("bad query field: ''")), | ||
311 | ("&", ValueError("bad query field: ''")), | ||
312 | ("&&", ValueError("bad query field: ''")), | ||
313 | - (";", ValueError("bad query field: ''")), | ||
314 | - (";&;", ValueError("bad query field: ''")), | ||
315 | # Should the next few really be valid? | ||
316 | ("=", {}), | ||
317 | ("=&=", {}), | ||
318 | - ("=;=", {}), | ||
319 | # This rest seem to make sense | ||
320 | ("=a", {'': ['a']}), | ||
321 | ("&=a", ValueError("bad query field: ''")), | ||
322 | @@ -73,8 +70,6 @@ parse_strict_test_cases = [ | ||
323 | ("a=a+b&b=b+c", {'a': ['a b'], 'b': ['b c']}), | ||
324 | ("a=a+b&a=b+a", {'a': ['a b', 'b a']}), | ||
325 | ("x=1&y=2.0&z=2-3.%2b0", {'x': ['1'], 'y': ['2.0'], 'z': ['2-3.+0']}), | ||
326 | - ("x=1;y=2.0&z=2-3.%2b0", {'x': ['1'], 'y': ['2.0'], 'z': ['2-3.+0']}), | ||
327 | - ("x=1;y=2.0;z=2-3.%2b0", {'x': ['1'], 'y': ['2.0'], 'z': ['2-3.+0']}), | ||
328 | ("Hbc5161168c542333633315dee1182227:key_store_seqid=400006&cuyer=r&view=bustomer&order_id=0bb2e248638833d48cb7fed300000f1b&expire=964546263&lobale=en-US&kid=130003.300038&ss=env", | ||
329 | {'Hbc5161168c542333633315dee1182227:key_store_seqid': ['400006'], | ||
330 | 'cuyer': ['r'], | ||
331 | @@ -187,6 +182,30 @@ Content-Length: 3 | ||
332 | else: | ||
333 | self.assertEqual(fs.getvalue(key), expect_val[0]) | ||
334 | |||
335 | + def test_separator(self): | ||
336 | + parse_semicolon = [ | ||
337 | + ("x=1;y=2.0", {'x': ['1'], 'y': ['2.0']}), | ||
338 | + ("x=1;y=2.0;z=2-3.%2b0", {'x': ['1'], 'y': ['2.0'], 'z': ['2-3.+0']}), | ||
339 | + (";", ValueError("bad query field: ''")), | ||
340 | + (";;", ValueError("bad query field: ''")), | ||
341 | + ("=;a", ValueError("bad query field: 'a'")), | ||
342 | + (";b=a", ValueError("bad query field: ''")), | ||
343 | + ("b;=a", ValueError("bad query field: 'b'")), | ||
344 | + ("a=a+b;b=b+c", {'a': ['a b'], 'b': ['b c']}), | ||
345 | + ("a=a+b;a=b+a", {'a': ['a b', 'b a']}), | ||
346 | + ] | ||
347 | + for orig, expect in parse_semicolon: | ||
348 | + env = {'QUERY_STRING': orig} | ||
349 | + fs = cgi.FieldStorage(separator=';', environ=env) | ||
350 | + if isinstance(expect, dict): | ||
351 | + for key in expect.keys(): | ||
352 | + expect_val = expect[key] | ||
353 | + self.assertIn(key, fs) | ||
354 | + if len(expect_val) > 1: | ||
355 | + self.assertEqual(fs.getvalue(key), expect_val) | ||
356 | + else: | ||
357 | + self.assertEqual(fs.getvalue(key), expect_val[0]) | ||
358 | + | ||
359 | def test_log(self): | ||
360 | cgi.log("Testing") | ||
361 | |||
362 | diff --git a/Lib/test/test_urlparse.py b/Lib/test/test_urlparse.py | ||
363 | index 4ae6ed3..90c8d69 100644 | ||
364 | --- a/Lib/test/test_urlparse.py | ||
365 | +++ b/Lib/test/test_urlparse.py | ||
366 | @@ -32,16 +32,10 @@ parse_qsl_test_cases = [ | ||
367 | (b"&a=b", [(b'a', b'b')]), | ||
368 | (b"a=a+b&b=b+c", [(b'a', b'a b'), (b'b', b'b c')]), | ||
369 | (b"a=1&a=2", [(b'a', b'1'), (b'a', b'2')]), | ||
370 | - (";", []), | ||
371 | - (";;", []), | ||
372 | - (";a=b", [('a', 'b')]), | ||
373 | - ("a=a+b;b=b+c", [('a', 'a b'), ('b', 'b c')]), | ||
374 | - ("a=1;a=2", [('a', '1'), ('a', '2')]), | ||
375 | - (b";", []), | ||
376 | - (b";;", []), | ||
377 | - (b";a=b", [(b'a', b'b')]), | ||
378 | - (b"a=a+b;b=b+c", [(b'a', b'a b'), (b'b', b'b c')]), | ||
379 | - (b"a=1;a=2", [(b'a', b'1'), (b'a', b'2')]), | ||
380 | + (";a=b", [(';a', 'b')]), | ||
381 | + ("a=a+b;b=b+c", [('a', 'a b;b=b c')]), | ||
382 | + (b";a=b", [(b';a', b'b')]), | ||
383 | + (b"a=a+b;b=b+c", [(b'a', b'a b;b=b c')]), | ||
384 | ] | ||
385 | |||
386 | # Each parse_qs testcase is a two-tuple that contains | ||
387 | @@ -68,16 +62,10 @@ parse_qs_test_cases = [ | ||
388 | (b"&a=b", {b'a': [b'b']}), | ||
389 | (b"a=a+b&b=b+c", {b'a': [b'a b'], b'b': [b'b c']}), | ||
390 | (b"a=1&a=2", {b'a': [b'1', b'2']}), | ||
391 | - (";", {}), | ||
392 | - (";;", {}), | ||
393 | - (";a=b", {'a': ['b']}), | ||
394 | - ("a=a+b;b=b+c", {'a': ['a b'], 'b': ['b c']}), | ||
395 | - ("a=1;a=2", {'a': ['1', '2']}), | ||
396 | - (b";", {}), | ||
397 | - (b";;", {}), | ||
398 | - (b";a=b", {b'a': [b'b']}), | ||
399 | - (b"a=a+b;b=b+c", {b'a': [b'a b'], b'b': [b'b c']}), | ||
400 | - (b"a=1;a=2", {b'a': [b'1', b'2']}), | ||
401 | + (";a=b", {';a': ['b']}), | ||
402 | + ("a=a+b;b=b+c", {'a': ['a b;b=b c']}), | ||
403 | + (b";a=b", {b';a': [b'b']}), | ||
404 | + (b"a=a+b;b=b+c", {b'a':[ b'a b;b=b c']}), | ||
405 | ] | ||
406 | |||
407 | class UrlParseTestCase(unittest.TestCase): | ||
408 | @@ -884,10 +872,46 @@ class UrlParseTestCase(unittest.TestCase): | ||
409 | def test_parse_qsl_max_num_fields(self): | ||
410 | with self.assertRaises(ValueError): | ||
411 | urllib.parse.parse_qs('&'.join(['a=a']*11), max_num_fields=10) | ||
412 | - with self.assertRaises(ValueError): | ||
413 | - urllib.parse.parse_qs(';'.join(['a=a']*11), max_num_fields=10) | ||
414 | urllib.parse.parse_qs('&'.join(['a=a']*10), max_num_fields=10) | ||
415 | |||
416 | + def test_parse_qs_separator(self): | ||
417 | + parse_qs_semicolon_cases = [ | ||
418 | + (";", {}), | ||
419 | + (";;", {}), | ||
420 | + (";a=b", {'a': ['b']}), | ||
421 | + ("a=a+b;b=b+c", {'a': ['a b'], 'b': ['b c']}), | ||
422 | + ("a=1;a=2", {'a': ['1', '2']}), | ||
423 | + (b";", {}), | ||
424 | + (b";;", {}), | ||
425 | + (b";a=b", {b'a': [b'b']}), | ||
426 | + (b"a=a+b;b=b+c", {b'a': [b'a b'], b'b': [b'b c']}), | ||
427 | + (b"a=1;a=2", {b'a': [b'1', b'2']}), | ||
428 | + ] | ||
429 | + for orig, expect in parse_qs_semicolon_cases: | ||
430 | + with self.subTest(f"Original: {orig!r}, Expected: {expect!r}"): | ||
431 | + result = urllib.parse.parse_qs(orig, separator=';') | ||
432 | + self.assertEqual(result, expect, "Error parsing %r" % orig) | ||
433 | + | ||
434 | + | ||
435 | + def test_parse_qsl_separator(self): | ||
436 | + parse_qsl_semicolon_cases = [ | ||
437 | + (";", []), | ||
438 | + (";;", []), | ||
439 | + (";a=b", [('a', 'b')]), | ||
440 | + ("a=a+b;b=b+c", [('a', 'a b'), ('b', 'b c')]), | ||
441 | + ("a=1;a=2", [('a', '1'), ('a', '2')]), | ||
442 | + (b";", []), | ||
443 | + (b";;", []), | ||
444 | + (b";a=b", [(b'a', b'b')]), | ||
445 | + (b"a=a+b;b=b+c", [(b'a', b'a b'), (b'b', b'b c')]), | ||
446 | + (b"a=1;a=2", [(b'a', b'1'), (b'a', b'2')]), | ||
447 | + ] | ||
448 | + for orig, expect in parse_qsl_semicolon_cases: | ||
449 | + with self.subTest(f"Original: {orig!r}, Expected: {expect!r}"): | ||
450 | + result = urllib.parse.parse_qsl(orig, separator=';') | ||
451 | + self.assertEqual(result, expect, "Error parsing %r" % orig) | ||
452 | + | ||
453 | + | ||
454 | def test_urlencode_sequences(self): | ||
455 | # Other tests incidentally urlencode things; test non-covered cases: | ||
456 | # Sequence and object values. | ||
457 | diff --git a/Lib/urllib/parse.py b/Lib/urllib/parse.py | ||
458 | index e2b6f13..5a3e847 100644 | ||
459 | --- a/Lib/urllib/parse.py | ||
460 | +++ b/Lib/urllib/parse.py | ||
461 | @@ -648,7 +648,7 @@ def unquote(string, encoding='utf-8', errors='replace'): | ||
462 | |||
463 | |||
464 | def parse_qs(qs, keep_blank_values=False, strict_parsing=False, | ||
465 | - encoding='utf-8', errors='replace', max_num_fields=None): | ||
466 | + encoding='utf-8', errors='replace', max_num_fields=None, separator='&'): | ||
467 | """Parse a query given as a string argument. | ||
468 | |||
469 | Arguments: | ||
470 | @@ -672,12 +672,15 @@ def parse_qs(qs, keep_blank_values=False, strict_parsing=False, | ||
471 | max_num_fields: int. If set, then throws a ValueError if there | ||
472 | are more than n fields read by parse_qsl(). | ||
473 | |||
474 | + separator: str. The symbol to use for separating the query arguments. | ||
475 | + Defaults to &. | ||
476 | + | ||
477 | Returns a dictionary. | ||
478 | """ | ||
479 | parsed_result = {} | ||
480 | pairs = parse_qsl(qs, keep_blank_values, strict_parsing, | ||
481 | encoding=encoding, errors=errors, | ||
482 | - max_num_fields=max_num_fields) | ||
483 | + max_num_fields=max_num_fields, separator=separator) | ||
484 | for name, value in pairs: | ||
485 | if name in parsed_result: | ||
486 | parsed_result[name].append(value) | ||
487 | @@ -687,7 +690,7 @@ def parse_qs(qs, keep_blank_values=False, strict_parsing=False, | ||
488 | |||
489 | |||
490 | def parse_qsl(qs, keep_blank_values=False, strict_parsing=False, | ||
491 | - encoding='utf-8', errors='replace', max_num_fields=None): | ||
492 | + encoding='utf-8', errors='replace', max_num_fields=None, separator='&'): | ||
493 | """Parse a query given as a string argument. | ||
494 | |||
495 | Arguments: | ||
496 | @@ -710,19 +713,25 @@ def parse_qsl(qs, keep_blank_values=False, strict_parsing=False, | ||
497 | max_num_fields: int. If set, then throws a ValueError | ||
498 | if there are more than n fields read by parse_qsl(). | ||
499 | |||
500 | + separator: str. The symbol to use for separating the query arguments. | ||
501 | + Defaults to &. | ||
502 | + | ||
503 | Returns a list, as G-d intended. | ||
504 | """ | ||
505 | qs, _coerce_result = _coerce_args(qs) | ||
506 | |||
507 | + if not separator or (not isinstance(separator, (str, bytes))): | ||
508 | + raise ValueError("Separator must be of type string or bytes.") | ||
509 | + | ||
510 | # If max_num_fields is defined then check that the number of fields | ||
511 | # is less than max_num_fields. This prevents a memory exhaustion DOS | ||
512 | # attack via post bodies with many fields. | ||
513 | if max_num_fields is not None: | ||
514 | - num_fields = 1 + qs.count('&') + qs.count(';') | ||
515 | + num_fields = 1 + qs.count(separator) | ||
516 | if max_num_fields < num_fields: | ||
517 | raise ValueError('Max number of fields exceeded') | ||
518 | |||
519 | - pairs = [s2 for s1 in qs.split('&') for s2 in s1.split(';')] | ||
520 | + pairs = [s1 for s1 in qs.split(separator)] | ||
521 | r = [] | ||
522 | for name_value in pairs: | ||
523 | if not name_value and not strict_parsing: | ||
524 | diff --git a/Misc/NEWS.d/next/Security/2021-02-14-15-59-16.bpo-42967.YApqDS.rst b/Misc/NEWS.d/next/Security/2021-02-14-15-59-16.bpo-42967.YApqDS.rst | ||
525 | new file mode 100644 | ||
526 | index 0000000..f08489b | ||
527 | --- /dev/null | ||
528 | +++ b/Misc/NEWS.d/next/Security/2021-02-14-15-59-16.bpo-42967.YApqDS.rst | ||
529 | @@ -0,0 +1 @@ | ||
530 | +Fix web cache poisoning vulnerability by defaulting the query args separator to ``&``, and allowing the user to choose a custom separator. | ||
diff --git a/meta/recipes-devtools/python/python3_3.8.2.bb b/meta/recipes-devtools/python/python3_3.8.2.bb index 762e9444b8..072ce97472 100644 --- a/meta/recipes-devtools/python/python3_3.8.2.bb +++ b/meta/recipes-devtools/python/python3_3.8.2.bb | |||
@@ -39,7 +39,6 @@ SRC_URI = "http://www.python.org/ftp/python/${PV}/Python-${PV}.tar.xz \ | |||
39 | file://CVE-2020-26116.patch \ | 39 | file://CVE-2020-26116.patch \ |
40 | file://CVE-2020-27619.patch \ | 40 | file://CVE-2020-27619.patch \ |
41 | file://CVE-2021-3177.patch \ | 41 | file://CVE-2021-3177.patch \ |
42 | file://CVE-2021-23336.patch \ | ||
43 | " | 42 | " |
44 | 43 | ||
45 | SRC_URI_append_class-native = " \ | 44 | SRC_URI_append_class-native = " \ |