summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--meta/recipes-devtools/python/python3/CVE-2021-23336.patch530
-rw-r--r--meta/recipes-devtools/python/python3_3.8.2.bb1
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 @@
1From 3ab6f812653e79d008d5eba31dc25d34f3ca7170 Mon Sep 17 00:00:00 2001
2From: Senthil Kumaran <senthil@uthcode.com>
3Date: Mon, 15 Feb 2021 10:15:02 -0800
4Subject: [PATCH] bpo-42967: only use '&' as a query string separator
5MIME-Version: 1.0
6Content-Type: text/plain; charset=UTF-8
7Content-Transfer-Encoding: 8bit
8
9 (GH-24297) (#24529)
10MIME-Version: 1.0
11Content-Type: text/plain; charset=UTF-8
12Content-Transfer-Encoding: 8bit
13
14* bpo-42967: only use '&' as a query string separator (#24297)
15
16bpo-42967: [security] Address a web cache-poisoning issue reported in
17urllib.parse.parse_qsl().
18
19urllib.parse will only us "&" as query string separator by default
20instead of both ";" and "&" as allowed in earlier versions. An optional
21argument seperator with default value "&" is added to specify the
22separator.
23
24Co-authored-by: Éric Araujo <merwok@netwok.org>
25Co-authored-by: blurb-it[bot] <43283697+blurb-it[bot]@users.noreply.github.com>
26Co-authored-by: Ken Jin <28750310+Fidget-Spinner@users.noreply.github.com>
27Co-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
32bpo-42967: [security] Address a web cache-poisoning issue reported in urllib.parse.parse_qsl().
33
34urllib.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
36Co-authored-by: Éric Araujo <merwok@netwok.org>
37Co-authored-by: blurb-it[bot] <43283697+blurb-it[bot]@users.noreply.github.com>
38Co-authored-by: Ken Jin <28750310+Fidget-Spinner@users.noreply.github.com>
39Co-authored-by: Éric Araujo <merwok@netwok.org>.
40(cherry picked from commit fcbe0cb04d35189401c0c880ebfb4311e952d776)
41
42Co-authored-by: Adam Goldschmidt <adamgold7@gmail.com>
43
44* Update correct version information.
45
46* fix docs and make logic clearer
47
48Co-authored-by: Adam Goldschmidt <adamgold7@gmail.com>
49Co-authored-by: Fidget-Spinner <28750310+Fidget-Spinner@users.noreply.github.com>
50
51Upstream-Status: Backport [https://github.com/python/cpython/commit/e3110c3cfbb7daa690d54d0eff6c264c870a71bf]
52CVE: CVE-2020-23336
53Signed-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
68diff --git a/Doc/library/cgi.rst b/Doc/library/cgi.rst
69index 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
102diff --git a/Doc/library/urllib.parse.rst b/Doc/library/urllib.parse.rst
103index 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
164diff --git a/Doc/whatsnew/3.6.rst b/Doc/whatsnew/3.6.rst
165index 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`.)
185diff --git a/Doc/whatsnew/3.7.rst b/Doc/whatsnew/3.7.rst
186index 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`.)
206diff --git a/Lib/cgi.py b/Lib/cgi.py
207index 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
305diff --git a/Lib/test/test_cgi.py b/Lib/test/test_cgi.py
306index 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
362diff --git a/Lib/test/test_urlparse.py b/Lib/test/test_urlparse.py
363index 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.
457diff --git a/Lib/urllib/parse.py b/Lib/urllib/parse.py
458index 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:
524diff --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
525new file mode 100644
526index 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
45SRC_URI_append_class-native = " \ 44SRC_URI_append_class-native = " \