From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: "Gregory P. Smith" <68491+gpshead@users.noreply.github.com> Date: Tue, 20 Jan 2026 14:51:43 -0800 Subject: 00473: CVE-2026-0865 gh-143916: Reject control characters in wsgiref.headers.Headers (GH-143917) * Add 'test.support' fixture for C0 control characters * gh-143916: Reject control characters in wsgiref.headers.Headers (cherry picked from commit 2f840249550e082dc351743f474ba56da10478d2) Co-authored-by: Seth Michael Larson --- Lib/test/support/__init__.py | 7 +++++++ Lib/test/test_wsgiref.py | 11 +++++++++++ Lib/wsgiref/headers.py | 3 +++ .../2026-01-16-11-07-36.gh-issue-143916.dpWeOD.rst | 2 ++ 4 files changed, 23 insertions(+) create mode 100644 Misc/NEWS.d/next/Security/2026-01-16-11-07-36.gh-issue-143916.dpWeOD.rst diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py index da1f2ee719..7c260a3dfe 100644 --- a/Lib/test/support/__init__.py +++ b/Lib/test/support/__init__.py @@ -2902,3 +2902,10 @@ def adjust_int_max_str_digits(max_digits): yield finally: sys.set_int_max_str_digits(current) + + +def control_characters_c0(): + """Returns a list of C0 control characters as strings. + C0 control characters defined as the byte range 0x00-0x1F, and 0x7F. + """ + return [chr(c) for c in range(0x00, 0x20)] + ["\x7F"] diff --git a/Lib/test/test_wsgiref.py b/Lib/test/test_wsgiref.py index 7708e20684..baac84d71c 100644 --- a/Lib/test/test_wsgiref.py +++ b/Lib/test/test_wsgiref.py @@ -1,5 +1,6 @@ from unittest import mock from test import support +from test.support import control_characters_c0 from test.test_httpservers import NoLogRequestHandler from unittest import TestCase from wsgiref.util import setup_testing_defaults @@ -517,6 +518,16 @@ class HeaderTests(TestCase): '\r\n' ) + def testRaisesControlCharacters(self): + headers = Headers() + for c0 in control_characters_c0(): + self.assertRaises(ValueError, headers.__setitem__, f"key{c0}", "val") + self.assertRaises(ValueError, headers.__setitem__, "key", f"val{c0}") + self.assertRaises(ValueError, headers.add_header, f"key{c0}", "val", param="param") + self.assertRaises(ValueError, headers.add_header, "key", f"val{c0}", param="param") + self.assertRaises(ValueError, headers.add_header, "key", "val", param=f"param{c0}") + + class ErrorHandler(BaseCGIHandler): """Simple handler subclass for testing BaseHandler""" diff --git a/Lib/wsgiref/headers.py b/Lib/wsgiref/headers.py index fab851c5a4..fd98e85d75 100644 --- a/Lib/wsgiref/headers.py +++ b/Lib/wsgiref/headers.py @@ -9,6 +9,7 @@ written by Barry Warsaw. # existence of which force quoting of the parameter value. import re tspecials = re.compile(r'[ \(\)<>@,;:\\"/\[\]\?=]') +_control_chars_re = re.compile(r'[\x00-\x1F\x7F]') def _formatparam(param, value=None, quote=1): """Convenience function to format and return a key=value pair. @@ -41,6 +42,8 @@ class Headers: def _convert_string_type(self, value): """Convert/check value type.""" if type(value) is str: + if _control_chars_re.search(value): + raise ValueError("Control characters not allowed in headers") return value raise AssertionError("Header names/values must be" " of type str (got {0})".format(repr(value))) diff --git a/Misc/NEWS.d/next/Security/2026-01-16-11-07-36.gh-issue-143916.dpWeOD.rst b/Misc/NEWS.d/next/Security/2026-01-16-11-07-36.gh-issue-143916.dpWeOD.rst new file mode 100644 index 0000000000..44bd0b2705 --- /dev/null +++ b/Misc/NEWS.d/next/Security/2026-01-16-11-07-36.gh-issue-143916.dpWeOD.rst @@ -0,0 +1,2 @@ +Reject C0 control characters within wsgiref.headers.Headers fields, values, +and parameters.