diff options
Diffstat (limited to 'meta-python/recipes-devtools/python/python3-tornado/CVE-2026-35536.patch')
| -rw-r--r-- | meta-python/recipes-devtools/python/python3-tornado/CVE-2026-35536.patch | 155 |
1 files changed, 155 insertions, 0 deletions
diff --git a/meta-python/recipes-devtools/python/python3-tornado/CVE-2026-35536.patch b/meta-python/recipes-devtools/python/python3-tornado/CVE-2026-35536.patch new file mode 100644 index 0000000000..783d0aa116 --- /dev/null +++ b/meta-python/recipes-devtools/python/python3-tornado/CVE-2026-35536.patch | |||
| @@ -0,0 +1,155 @@ | |||
| 1 | From 66587e51009457274cedec28f5fd43000d129e4e Mon Sep 17 00:00:00 2001 | ||
| 2 | From: Ben Darnell <ben@bendarnell.com> | ||
| 3 | Date: Fri, 6 Mar 2026 14:50:25 -0500 | ||
| 4 | Subject: [PATCH] web: Validate characters in all cookie attributes. | ||
| 5 | |||
| 6 | Our previous control character check was missing a check for | ||
| 7 | U+007F, and also semicolons, which are only allowed in quoted | ||
| 8 | parts of values. This commit checks all attributes and | ||
| 9 | updates the set of disallowed characters. | ||
| 10 | |||
| 11 | CVE: CVE-2026-35536 | ||
| 12 | Upstream-Status: Backport [https://github.com/tornadoweb/tornado/commit/24a2d96ea115f663b223887deb0060f13974c104] | ||
| 13 | Signed-off-by: Ankur Tyagi <ankur.tyagi85@gmail.com> | ||
| 14 | --- | ||
| 15 | tornado/test/web_test.py | 65 ++++++++++++++++++++++++++++++++++++++++ | ||
| 16 | tornado/web.py | 27 +++++++++++++++-- | ||
| 17 | 2 files changed, 89 insertions(+), 3 deletions(-) | ||
| 18 | |||
| 19 | diff --git a/tornado/test/web_test.py b/tornado/test/web_test.py | ||
| 20 | index 801a80ed..ae39e8fc 100644 | ||
| 21 | --- a/tornado/test/web_test.py | ||
| 22 | +++ b/tornado/test/web_test.py | ||
| 23 | @@ -1,3 +1,5 @@ | ||
| 24 | +import http | ||
| 25 | + | ||
| 26 | from tornado.concurrent import Future | ||
| 27 | from tornado import gen | ||
| 28 | from tornado.escape import ( | ||
| 29 | @@ -291,11 +293,67 @@ class CookieTest(WebTestCase): | ||
| 30 | self.set_cookie("unicode_args", "blah", domain="foo.com", path="/foo") | ||
| 31 | |||
| 32 | class SetCookieSpecialCharHandler(RequestHandler): | ||
| 33 | + # "Special" characters are allowed in cookie values, but trigger special quoting. | ||
| 34 | def get(self): | ||
| 35 | self.set_cookie("equals", "a=b") | ||
| 36 | self.set_cookie("semicolon", "a;b") | ||
| 37 | self.set_cookie("quote", 'a"b') | ||
| 38 | |||
| 39 | + class SetCookieForbiddenCharHandler(RequestHandler): | ||
| 40 | + def get(self): | ||
| 41 | + # Control characters and semicolons raise errors in cookie names and attributes | ||
| 42 | + # (but not values, which are tested in SetCookieSpecialCharHandler) | ||
| 43 | + for char in list(map(chr, range(0x20))) + [chr(0x7F), ";"]: | ||
| 44 | + try: | ||
| 45 | + self.set_cookie("foo" + char, "bar") | ||
| 46 | + self.write( | ||
| 47 | + "Didn't get expected exception for char %r in name\n" % char | ||
| 48 | + ) | ||
| 49 | + except http.cookies.CookieError as e: | ||
| 50 | + if "Invalid cookie attribute name" not in str(e): | ||
| 51 | + self.write( | ||
| 52 | + "unexpected exception for char %r in name: %s\n" | ||
| 53 | + % (char, e) | ||
| 54 | + ) | ||
| 55 | + | ||
| 56 | + try: | ||
| 57 | + self.set_cookie("foo", "bar", domain="example" + char + ".com") | ||
| 58 | + self.write( | ||
| 59 | + "Didn't get expected exception for char %r in domain\n" | ||
| 60 | + % char | ||
| 61 | + ) | ||
| 62 | + except http.cookies.CookieError as e: | ||
| 63 | + if "Invalid cookie attribute domain" not in str(e): | ||
| 64 | + self.write( | ||
| 65 | + "unexpected exception for char %r in domain: %s\n" | ||
| 66 | + % (char, e) | ||
| 67 | + ) | ||
| 68 | + | ||
| 69 | + try: | ||
| 70 | + self.set_cookie("foo", "bar", path="/" + char) | ||
| 71 | + self.write( | ||
| 72 | + "Didn't get expected exception for char %r in path\n" % char | ||
| 73 | + ) | ||
| 74 | + except http.cookies.CookieError as e: | ||
| 75 | + if "Invalid cookie attribute path" not in str(e): | ||
| 76 | + self.write( | ||
| 77 | + "unexpected exception for char %r in path: %s\n" | ||
| 78 | + % (char, e) | ||
| 79 | + ) | ||
| 80 | + | ||
| 81 | + try: | ||
| 82 | + self.set_cookie("foo", "bar", samesite="a" + char) | ||
| 83 | + self.write( | ||
| 84 | + "Didn't get expected exception for char %r in samesite\n" | ||
| 85 | + % char | ||
| 86 | + ) | ||
| 87 | + except http.cookies.CookieError as e: | ||
| 88 | + if "Invalid cookie attribute samesite" not in str(e): | ||
| 89 | + self.write( | ||
| 90 | + "unexpected exception for char %r in samesite: %s\n" | ||
| 91 | + % (char, e) | ||
| 92 | + ) | ||
| 93 | + | ||
| 94 | class SetCookieOverwriteHandler(RequestHandler): | ||
| 95 | def get(self): | ||
| 96 | self.set_cookie("a", "b", domain="example.com") | ||
| 97 | @@ -329,6 +387,7 @@ class CookieTest(WebTestCase): | ||
| 98 | ("/get", GetCookieHandler), | ||
| 99 | ("/set_domain", SetCookieDomainHandler), | ||
| 100 | ("/special_char", SetCookieSpecialCharHandler), | ||
| 101 | + ("/forbidden_char", SetCookieForbiddenCharHandler), | ||
| 102 | ("/set_overwrite", SetCookieOverwriteHandler), | ||
| 103 | ("/set_max_age", SetCookieMaxAgeHandler), | ||
| 104 | ("/set_expires_days", SetCookieExpiresDaysHandler), | ||
| 105 | @@ -385,6 +444,12 @@ class CookieTest(WebTestCase): | ||
| 106 | response = self.fetch("/get", headers={"Cookie": header}) | ||
| 107 | self.assertEqual(response.body, utf8(expected)) | ||
| 108 | |||
| 109 | + def test_set_cookie_forbidden_char(self): | ||
| 110 | + response = self.fetch("/forbidden_char") | ||
| 111 | + self.assertEqual(response.code, 200) | ||
| 112 | + self.maxDiff = 10000 | ||
| 113 | + self.assertMultiLineEqual(to_unicode(response.body), "") | ||
| 114 | + | ||
| 115 | def test_set_cookie_overwrite(self): | ||
| 116 | response = self.fetch("/set_overwrite") | ||
| 117 | headers = response.headers.get_list("Set-Cookie") | ||
| 118 | diff --git a/tornado/web.py b/tornado/web.py | ||
| 119 | index 8a740504..4b70ea93 100644 | ||
| 120 | --- a/tornado/web.py | ||
| 121 | +++ b/tornado/web.py | ||
| 122 | @@ -643,9 +643,30 @@ class RequestHandler(object): | ||
| 123 | # The cookie library only accepts type str, in both python 2 and 3 | ||
| 124 | name = escape.native_str(name) | ||
| 125 | value = escape.native_str(value) | ||
| 126 | - if re.search(r"[\x00-\x20]", name + value): | ||
| 127 | - # Don't let us accidentally inject bad stuff | ||
| 128 | - raise ValueError("Invalid cookie %r: %r" % (name, value)) | ||
| 129 | + if re.search(r"[\x00-\x20]", value): | ||
| 130 | + # Legacy check for control characters in cookie values. This check is no longer needed | ||
| 131 | + # since the cookie library escapes these characters correctly now. It will be removed | ||
| 132 | + # in the next feature release. | ||
| 133 | + raise ValueError(f"Invalid cookie {name!r}: {value!r}") | ||
| 134 | + for attr_name, attr_value in [ | ||
| 135 | + ("name", name), | ||
| 136 | + ("domain", domain), | ||
| 137 | + ("path", path), | ||
| 138 | + ("samesite", samesite), | ||
| 139 | + ]: | ||
| 140 | + # Cookie attributes may not contain control characters or semicolons (except when | ||
| 141 | + # escaped in the value). A check for control characters was added to the http.cookies | ||
| 142 | + # library in a Feb 2026 security release; as of March it still does not check for | ||
| 143 | + # semicolons. | ||
| 144 | + # | ||
| 145 | + # When a semicolon check is added to the standard library (and the release has had time | ||
| 146 | + # for adoption), this check may be removed, but be mindful of the fact that this may | ||
| 147 | + # change the timing of the exception (to the generation of the Set-Cookie header in | ||
| 148 | + # flush()). We m | ||
| 149 | + if attr_value is not None and re.search(r"[\x00-\x20\x3b\x7f]", attr_value): | ||
| 150 | + raise http.cookies.CookieError( | ||
| 151 | + f"Invalid cookie attribute {attr_name}={attr_value!r} for cookie {name!r}" | ||
| 152 | + ) | ||
| 153 | if not hasattr(self, "_new_cookie"): | ||
| 154 | self._new_cookie = ( | ||
| 155 | http.cookies.SimpleCookie() | ||
