File CVE-2025-15282-urllib-ctrl-chars.patch of Package python3.42753
From d6c6f0880dbc6ffd770b859087f4cd749a1d0dbb Mon Sep 17 00:00:00 2001
From: Seth Michael Larson <seth@python.org>
Date: Tue, 20 Jan 2026 14:45:58 -0600
Subject: [PATCH] [3.10] gh-143925: Reject control characters in data: URL
mediatypes (cherry picked from commit
f25509e78e8be6ea73c811ac2b8c928c28841b9f) (cherry picked from commit
2c9c746077d8119b5bcf5142316992e464594946)
Co-authored-by: Seth Michael Larson <seth@python.org>
---
Lib/test/test_urllib.py | 50 +++++-----
Lib/urllib/request.py | 5 +
Misc/NEWS.d/next/Security/2026-01-16-11-51-19.gh-issue-143925.mrtcHW.rst | 1
3 files changed, 35 insertions(+), 21 deletions(-)
create mode 100644 Misc/NEWS.d/next/Security/2026-01-16-11-51-19.gh-issue-143925.mrtcHW.rst
Index: Python-3.6.15/Lib/test/test_urllib.py
===================================================================
--- Python-3.6.15.orig/Lib/test/test_urllib.py 2026-02-12 17:44:24.492793161 +0100
+++ Python-3.6.15/Lib/test/test_urllib.py 2026-02-12 17:49:52.026664784 +0100
@@ -8,7 +8,8 @@
import io
import unittest
from unittest.mock import patch
-from test import support
+from test.support import (check_warnings, control_characters_c0,
+ EnvironmentVarGuard, TESTFN)
import os
try:
import ssl
@@ -51,7 +52,7 @@
def FancyURLopener():
- with support.check_warnings(
+ with check_warnings(
('FancyURLopener style of invoking requests is deprecated.',
DeprecationWarning)):
return urllib.request.FancyURLopener()
@@ -137,18 +138,18 @@
# Create a temp file to use for testing
self.text = bytes("test_urllib: %s\n" % self.__class__.__name__,
"ascii")
- f = open(support.TESTFN, 'wb')
+ f = open(TESTFN, 'wb')
try:
f.write(self.text)
finally:
f.close()
- self.pathname = support.TESTFN
+ self.pathname = TESTFN
self.returned_obj = urlopen("file:%s" % self.pathname)
def tearDown(self):
"""Shut down the open object"""
self.returned_obj.close()
- os.remove(support.TESTFN)
+ os.remove(TESTFN)
def test_interface(self):
# Make sure object returned by urlopen() has the specified methods
@@ -212,7 +213,7 @@
def setUp(self):
# Records changes to env vars
- self.env = support.EnvironmentVarGuard()
+ self.env = EnvironmentVarGuard()
# Delete all proxy related env vars
for k in list(os.environ):
if 'proxy' in k.lower():
@@ -552,13 +553,13 @@
self.unfakehttp()
def test_URLopener_deprecation(self):
- with support.check_warnings(('',DeprecationWarning)):
+ with check_warnings(('',DeprecationWarning)):
urllib.request.URLopener()
@unittest.skipUnless(ssl, "ssl module required")
def test_cafile_and_context(self):
context = ssl.create_default_context()
- with support.check_warnings(('', DeprecationWarning)):
+ with check_warnings(('', DeprecationWarning)):
with self.assertRaises(ValueError):
urllib.request.urlopen(
"https://localhost", cafile="/nonexistent/path", context=context
@@ -638,6 +639,13 @@
# missing padding character
self.assertRaises(ValueError,urllib.request.urlopen,'data:;base64,Cg=')
+ def test_invalid_mediatype(self):
+ for c0 in control_characters_c0():
+ self.assertRaises(ValueError,urllib.request.urlopen,
+ f'data:text/html;{c0},data')
+ for c0 in control_characters_c0():
+ self.assertRaises(ValueError,urllib.request.urlopen,
+ f'data:text/html{c0};base64,ZGF0YQ==')
class urlretrieve_FileTests(unittest.TestCase):
"""Test urllib.urlretrieve() on local files"""
@@ -653,10 +661,10 @@
self.tempFiles = []
# Create a temporary file.
- self.registerFileForCleanUp(support.TESTFN)
+ self.registerFileForCleanUp(TESTFN)
self.text = b'testing urllib.urlretrieve'
try:
- FILE = open(support.TESTFN, 'wb')
+ FILE = open(TESTFN, 'wb')
FILE.write(self.text)
FILE.close()
finally:
@@ -699,18 +707,18 @@
def test_basic(self):
# Make sure that a local file just gets its own location returned and
# a headers value is returned.
- result = urllib.request.urlretrieve("file:%s" % support.TESTFN)
- self.assertEqual(result[0], support.TESTFN)
+ result = urllib.request.urlretrieve("file:%s" % TESTFN)
+ self.assertEqual(result[0], TESTFN)
self.assertIsInstance(result[1], email.message.Message,
"did not get an email.message.Message instance "
"as second returned value")
def test_copy(self):
# Test that setting the filename argument works.
- second_temp = "%s.2" % support.TESTFN
+ second_temp = "%s.2" % TESTFN
self.registerFileForCleanUp(second_temp)
result = urllib.request.urlretrieve(self.constructLocalFileUrl(
- support.TESTFN), second_temp)
+ TESTFN), second_temp)
self.assertEqual(second_temp, result[0])
self.assertTrue(os.path.exists(second_temp), "copy of the file was not "
"made")
@@ -731,10 +739,10 @@
self.assertIsInstance(file_size, int)
self.assertEqual(block_count, count_holder[0])
count_holder[0] = count_holder[0] + 1
- second_temp = "%s.2" % support.TESTFN
+ second_temp = "%s.2" % TESTFN
self.registerFileForCleanUp(second_temp)
urllib.request.urlretrieve(
- self.constructLocalFileUrl(support.TESTFN),
+ self.constructLocalFileUrl(TESTFN),
second_temp, hooktester)
def test_reporthook_0_bytes(self):
@@ -744,7 +752,7 @@
_report.append((block_count, block_read_size, file_size))
srcFileName = self.createNewTempFile()
urllib.request.urlretrieve(self.constructLocalFileUrl(srcFileName),
- support.TESTFN, hooktester)
+ TESTFN, hooktester)
self.assertEqual(len(report), 1)
self.assertEqual(report[0][2], 0)
@@ -757,7 +765,7 @@
_report.append((block_count, block_read_size, file_size))
srcFileName = self.createNewTempFile(b"x" * 5)
urllib.request.urlretrieve(self.constructLocalFileUrl(srcFileName),
- support.TESTFN, hooktester)
+ TESTFN, hooktester)
self.assertEqual(len(report), 2)
self.assertEqual(report[0][2], 5)
self.assertEqual(report[1][2], 5)
@@ -771,7 +779,7 @@
_report.append((block_count, block_read_size, file_size))
srcFileName = self.createNewTempFile(b"x" * 8193)
urllib.request.urlretrieve(self.constructLocalFileUrl(srcFileName),
- support.TESTFN, hooktester)
+ TESTFN, hooktester)
self.assertEqual(len(report), 3)
self.assertEqual(report[0][2], 8193)
self.assertEqual(report[0][1], 8192)
@@ -1054,7 +1062,7 @@
"%s" % result)
self.assertRaises((TypeError, AttributeError), urllib.parse.unquote, None)
self.assertRaises((TypeError, AttributeError), urllib.parse.unquote, ())
- with support.check_warnings(('', BytesWarning), quiet=True):
+ with check_warnings(('', BytesWarning), quiet=True):
self.assertRaises((TypeError, AttributeError), urllib.parse.unquote, b'')
def test_unquoting_badpercent(self):
@@ -1485,7 +1493,7 @@
class DummyURLopener(urllib.request.URLopener):
def open_spam(self, url):
return url
- with support.check_warnings(
+ with check_warnings(
('DummyURLopener style of invoking requests is deprecated.',
DeprecationWarning)):
self.assertEqual(DummyURLopener().open(
Index: Python-3.6.15/Lib/urllib/request.py
===================================================================
--- Python-3.6.15.orig/Lib/urllib/request.py 2026-02-12 17:44:24.492793161 +0100
+++ Python-3.6.15/Lib/urllib/request.py 2026-02-12 17:45:36.957749972 +0100
@@ -1655,6 +1655,11 @@
scheme, data = url.split(":",1)
mediatype, data = data.split(",",1)
+ # Disallow control characters within mediatype.
+ if re.search(r"[\x00-\x1F\x7F]", mediatype):
+ raise ValueError(
+ "Control characters not allowed in data: mediatype")
+
# even base64 encoded data URLs might be quoted so unquote in any case:
data = unquote_to_bytes(data)
if mediatype.endswith(";base64"):
Index: Python-3.6.15/Misc/NEWS.d/next/Security/2026-01-16-11-51-19.gh-issue-143925.mrtcHW.rst
===================================================================
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ Python-3.6.15/Misc/NEWS.d/next/Security/2026-01-16-11-51-19.gh-issue-143925.mrtcHW.rst 2026-02-12 17:45:36.958441556 +0100
@@ -0,0 +1 @@
+Reject control characters in ``data:`` URL media types.