Skip to content

Commit 746605b

Browse files
authored
Add http.coookies monkeypatch (SYN-8465) (#4045)
Vendor the fixes from CPython for GHSA-7pwv-g7hj-39pr and applies them at import time of `synapse.common`. python/cpython#123067 python/cpython#123075
1 parent 23a328f commit 746605b

File tree

5 files changed

+128
-0
lines changed

5 files changed

+128
-0
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
desc: Added a patch for Python ``http.cookies`` module to address CVE-2024-7592 exposure.
3+
prs: []
4+
type: bug
5+
...

synapse/common.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929
import contextlib
3030
import collections
3131

32+
import http.cookies
33+
3234
import yaml
3335
import regex
3436

@@ -38,6 +40,8 @@
3840
import synapse.lib.structlog as s_structlog
3941

4042
import synapse.vendor.cpython.lib.ipaddress as ipaddress
43+
import synapse.vendor.cpython.lib.http.cookies as v_cookies
44+
4145

4246
try:
4347
from yaml import CSafeLoader as Loader
@@ -1218,6 +1222,17 @@ def trimText(text: str, n: int = 256, placeholder: str = '...') -> str:
12181222
assert n > plen
12191223
return f'{text[:mlen]}{placeholder}'
12201224

1225+
def _patch_http_cookies():
1226+
'''
1227+
Patch stdlib http.cookies._unquote from the 3.11.10 implementation if
1228+
the interpreter we are using is not patched for CVE-2024-7592.
1229+
'''
1230+
if not hasattr(http.cookies, '_QuotePatt'):
1231+
return
1232+
http.cookies._unquote = v_cookies._unquote
1233+
1234+
_patch_http_cookies()
1235+
12211236
# TODO: Switch back to using asyncio.wait_for when we are using py 3.12+
12221237
# This is a workaround for a race where asyncio.wait_for can end up
12231238
# ignoring cancellation https://github.com/python/cpython/issues/86296

synapse/vendor/cpython/lib/http/__init__.py

Whitespace-only changes.
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
##############################################################################
2+
# Taken from the cpython 3.11 source branch after the 3.11.10 release.
3+
##############################################################################
4+
####
5+
# Copyright 2000 by Timothy O'Malley <timo@alum.mit.edu>
6+
#
7+
# All Rights Reserved
8+
#
9+
# Permission to use, copy, modify, and distribute this software
10+
# and its documentation for any purpose and without fee is hereby
11+
# granted, provided that the above copyright notice appear in all
12+
# copies and that both that copyright notice and this permission
13+
# notice appear in supporting documentation, and that the name of
14+
# Timothy O'Malley not be used in advertising or publicity
15+
# pertaining to distribution of the software without specific, written
16+
# prior permission.
17+
#
18+
# Timothy O'Malley DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS
19+
# SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
20+
# AND FITNESS, IN NO EVENT SHALL Timothy O'Malley BE LIABLE FOR
21+
# ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
22+
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
23+
# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
24+
# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
25+
# PERFORMANCE OF THIS SOFTWARE.
26+
#
27+
28+
#
29+
# Import our required modules
30+
#
31+
import re
32+
33+
_unquote_sub = re.compile(r'\\(?:([0-3][0-7][0-7])|(.))').sub
34+
35+
def _unquote_replace(m):
36+
if m[1]:
37+
return chr(int(m[1], 8))
38+
else:
39+
return m[2]
40+
41+
def _unquote(str):
42+
# If there aren't any doublequotes,
43+
# then there can't be any special characters. See RFC 2109.
44+
if str is None or len(str) < 2:
45+
return str
46+
if str[0] != '"' or str[-1] != '"':
47+
return str
48+
49+
# We have to assume that we must decode this string.
50+
# Down to work.
51+
52+
# Remove the "s
53+
str = str[1:-1]
54+
55+
# Check for special sequences. Examples:
56+
# \012 --> \n
57+
# \" --> "
58+
#
59+
return _unquote_sub(_unquote_replace, str)
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
##############################################################################
2+
# Taken from the cpython 3.11 source branch after the 3.11.10 release.
3+
# It has been modified for vendored imports and vendored test harness.
4+
##############################################################################
5+
6+
# Simple test suite for http/cookies.py
7+
8+
from http import cookies
9+
10+
# s_v_utils runs the monkeypatch
11+
import synapse.vendor.utils as s_v_utils
12+
13+
class CookieTests(s_v_utils.VendorTest):
14+
15+
def test_unquote(self):
16+
cases = [
17+
(r'a="b=\""', 'b="'),
18+
(r'a="b=\\"', 'b=\\'),
19+
(r'a="b=\="', 'b=='),
20+
(r'a="b=\n"', 'b=n'),
21+
(r'a="b=\042"', 'b="'),
22+
(r'a="b=\134"', 'b=\\'),
23+
(r'a="b=\377"', 'b=\xff'),
24+
(r'a="b=\400"', 'b=400'),
25+
(r'a="b=\42"', 'b=42'),
26+
(r'a="b=\\042"', 'b=\\042'),
27+
(r'a="b=\\134"', 'b=\\134'),
28+
(r'a="b=\\\""', 'b=\\"'),
29+
(r'a="b=\\\042"', 'b=\\"'),
30+
(r'a="b=\134\""', 'b=\\"'),
31+
(r'a="b=\134\042"', 'b=\\"'),
32+
]
33+
for encoded, decoded in cases:
34+
with self.subTest(encoded):
35+
C = cookies.SimpleCookie()
36+
C.load(encoded)
37+
self.assertEqual(C['a'].value, decoded)
38+
39+
def test_unquote_large(self):
40+
n = 10**6
41+
for encoded in r'\\', r'\134':
42+
with self.subTest(encoded):
43+
data = 'a="b=' + encoded * n + ';"'
44+
C = cookies.SimpleCookie()
45+
C.load(data)
46+
value = C['a'].value
47+
self.assertEqual(value[:3], 'b=\\')
48+
self.assertEqual(value[-2:], '\\;')
49+
self.assertEqual(len(value), n + 3)

0 commit comments

Comments
 (0)