Skip to content

Commit ff782f2

Browse files
committed
Merge branch 'main' into issue-151039
2 parents fb24376 + c3cd75a commit ff782f2

29 files changed

Lines changed: 504 additions & 55 deletions

.github/SECURITY.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Security Policy
22

33
Python [provides a security policy and threat model](https://devguide.python.org/security/policy/)
4-
in the Python Development Guide documenting what bugs are vulnerabilities,
4+
in the Python Developer's Guide documenting what bugs are vulnerabilities,
55
how to structure reports, and what versions of Python accept reports.
66

77
Python Security Response Team (PSRT) members

Doc/library/collections.abc.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -456,7 +456,7 @@ Notes on using :class:`Set` and :class:`MutableSet` as a mixin:
456456
The :class:`Set` mixin provides a :meth:`!_hash` method to compute a hash value
457457
for the set; however, :meth:`~object.__hash__` is not defined because not all sets
458458
are :term:`hashable` or immutable. To add set hashability using mixins,
459-
inherit from both :meth:`Set` and :meth:`Hashable`, then define
459+
inherit from both :class:`Set` and :class:`Hashable`, then define
460460
``__hash__ = Set._hash``.
461461

462462
.. seealso::

Doc/library/profiling.sampling.rst

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -387,11 +387,6 @@ This requires one of:
387387
On Windows, the profiler requires administrative privileges or the
388388
``SeDebugPrivilege`` privilege to read another process's memory.
389389

390-
*Note*: On Windows, ``python -m profiling.sampling`` fails inside a virtual
391-
environment because the venv's ``python.exe`` is just a launcher shim that
392-
re-executes the base interpreter as a child process. The shim itself isn't
393-
a Python process and has no ``PyRuntime`` section to attach to. Instead,
394-
run it from the global Python installation.
395390

396391
Version compatibility
397392
---------------------

Lib/profiling/sampling/sample.py

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,38 @@ def _pause_threads(unwinder, blocking):
5050
# Maximum number of consecutive identical samples to keep before flushing.
5151
MAX_PENDING_SAMPLES = 8192
5252

53+
54+
def _resolve_python_pid(pid):
55+
"""On Windows, if pid is a venvlauncher process, return the child Python PID.
56+
57+
The venvlauncher (used as python.exe in venvs) spawns the real Python
58+
interpreter as a child process via CreateProcessW. The RemoteUnwinder
59+
needs the child's PID, not the launcher's.
60+
61+
Returns the original pid if not on Windows, not a venv launcher,
62+
or no child process is found.
63+
"""
64+
if os.name != "nt" or sys.prefix == sys.base_prefix:
65+
return pid
66+
try:
67+
children = _remote_debugging.get_child_pids(pid, recursive=False)
68+
python_children = [
69+
child for child in children
70+
if _remote_debugging.is_python_process(child)
71+
]
72+
if len(python_children) == 1:
73+
return python_children[0]
74+
except (OSError, RuntimeError) as err:
75+
raise SystemExit(
76+
f"Failed to initialize profiler from virtualenv: {err}\n"
77+
f"Try running with the base interpreter: {sys._base_executable}"
78+
) from err
79+
return pid
80+
81+
5382
class SampleProfiler:
5483
def __init__(self, pid, sample_interval_usec, all_threads, *, mode=PROFILING_MODE_WALL, native=False, gc=True, opcodes=False, skip_non_matching_threads=True, collect_stats=False, blocking=False):
55-
self.pid = pid
84+
self.pid = _resolve_python_pid(pid)
5685
self.sample_interval_usec = sample_interval_usec
5786
self.all_threads = all_threads
5887
self.mode = mode # Store mode for later use
@@ -61,10 +90,6 @@ def __init__(self, pid, sample_interval_usec, all_threads, *, mode=PROFILING_MOD
6190
try:
6291
self.unwinder = self._new_unwinder(native, gc, opcodes, skip_non_matching_threads)
6392
except RuntimeError as err:
64-
if os.name == "nt" and sys.executable.endswith("python.exe"):
65-
raise SystemExit(
66-
"Running profiling.sampling from virtualenv on Windows platform is not supported"
67-
) from err
6893
raise SystemExit(err) from err
6994
# Track sample intervals and total sample count
7095
self.sample_intervals = deque(maxlen=100)

Lib/test/test_capi/test_weakref.py

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
import weakref
2+
import unittest
3+
from test.support import import_helper
4+
5+
_testcapi = import_helper.import_module('_testcapi')
6+
_testlimitedcapi = import_helper.import_module('_testlimitedcapi')
7+
NULL = None
8+
9+
class Object:
10+
pass
11+
12+
class Ref(weakref.ReferenceType):
13+
pass
14+
15+
16+
class CAPIWeakrefTest(unittest.TestCase):
17+
def test_pyweakref_check(self):
18+
# Test PyWeakref_Check()
19+
check = _testlimitedcapi.pyweakref_check
20+
obj = Object()
21+
self.assertEqual(check(obj), 0)
22+
self.assertEqual(check(weakref.ref(obj)), 1)
23+
self.assertEqual(check(Ref(obj)), 1)
24+
self.assertEqual(check(weakref.proxy(obj)), 1)
25+
26+
# CRASHES check(NULL)
27+
28+
def test_pyweakref_checkref(self):
29+
# Test PyWeakref_CheckRef()
30+
checkref = _testlimitedcapi.pyweakref_checkref
31+
obj = Object()
32+
self.assertEqual(checkref(obj), 0)
33+
self.assertEqual(checkref(weakref.ref(obj)), 1)
34+
self.assertEqual(checkref(Ref(obj)), 1)
35+
self.assertEqual(checkref(weakref.proxy(obj)), 0)
36+
37+
# CRASHES checkref(NULL)
38+
39+
def test_pyweakref_checkrefexact(self):
40+
# Test PyWeakref_CheckRefExact()
41+
checkrefexact = _testlimitedcapi.pyweakref_checkrefexact
42+
obj = Object()
43+
self.assertEqual(checkrefexact(obj), 0)
44+
self.assertEqual(checkrefexact(weakref.ref(obj)), 1)
45+
self.assertEqual(checkrefexact(Ref(obj)), 0)
46+
self.assertEqual(checkrefexact(weakref.proxy(obj)), 0)
47+
48+
# CRASHES checkrefexact(NULL)
49+
50+
def test_pyweakref_checkproxy(self):
51+
# Test PyWeakref_CheckProxy()
52+
checkproxy = _testlimitedcapi.pyweakref_checkproxy
53+
obj = Object()
54+
self.assertEqual(checkproxy(obj), 0)
55+
self.assertEqual(checkproxy(weakref.ref(obj)), 0)
56+
self.assertEqual(checkproxy(Ref(obj)), 0)
57+
self.assertEqual(checkproxy(weakref.proxy(obj)), 1)
58+
59+
# CRASHES checkproxy(NULL)
60+
61+
def test_pyweakref_getref(self):
62+
# Test PyWeakref_GetRef()
63+
getref = _testcapi.pyweakref_getref
64+
obj = Object()
65+
wr = weakref.ref(obj)
66+
wp = weakref.proxy(obj)
67+
self.assertEqual(getref(wr), (1, obj))
68+
self.assertEqual(getref(wp), (1, obj))
69+
del obj
70+
self.assertEqual(getref(wr), 0)
71+
self.assertEqual(getref(wp), 0)
72+
73+
self.assertRaises(TypeError, getref, 42)
74+
self.assertRaises(SystemError, getref, NULL)
75+
76+
def test_pyweakref_isdead(self):
77+
# Test PyWeakref_IsDead()
78+
isdead = _testcapi.pyweakref_isdead
79+
obj = Object()
80+
wr = weakref.ref(obj)
81+
wp = weakref.proxy(obj)
82+
self.assertEqual(isdead(wr), 0)
83+
self.assertEqual(isdead(wp), 0)
84+
del obj
85+
self.assertEqual(isdead(wr), 1)
86+
self.assertEqual(isdead(wp), 1)
87+
88+
self.assertRaises(TypeError, isdead, 42)
89+
self.assertRaises(SystemError, isdead, NULL)
90+
91+
def test_pyweakref_newref(self):
92+
# Test PyWeakref_NewRef()
93+
newref = _testlimitedcapi.pyweakref_newref
94+
obj = Object()
95+
wr = newref(obj)
96+
self.assertIs(type(wr), weakref.ReferenceType)
97+
# PyWeakref_NewRef() handles None callback as NULL callback
98+
wr = newref(obj, None)
99+
self.assertIs(type(wr), weakref.ReferenceType)
100+
log = []
101+
wr = newref(obj, log.append)
102+
self.assertIs(type(wr), weakref.ReferenceType)
103+
self.assertEqual(log, [])
104+
del obj
105+
self.assertEqual(log, [wr])
106+
107+
self.assertRaises(TypeError, newref, [])
108+
# CRASHES newref(NULL)
109+
110+
def test_pyweakref_newproxy(self):
111+
# Test PyWeakref_NewProxy()
112+
newproxy = _testlimitedcapi.pyweakref_newproxy
113+
obj = Object()
114+
wp = newproxy(obj)
115+
self.assertIs(type(wp), weakref.ProxyType)
116+
# PyWeakref_NewProxy() handles None callback as NULL callback
117+
wp = newproxy(obj, None)
118+
self.assertIs(type(wp), weakref.ProxyType)
119+
log = []
120+
wp = newproxy(obj, log.append)
121+
self.assertIs(type(wp), weakref.ProxyType)
122+
self.assertEqual(log, [])
123+
del obj
124+
self.assertEqual(log, [wp])
125+
126+
def func():
127+
pass
128+
wp = newproxy(func)
129+
self.assertIs(type(wp), weakref.CallableProxyType)
130+
131+
self.assertRaises(TypeError, newproxy, [])
132+
# CRASHES newproxy(NULL)
133+
134+
135+
if __name__ == "__main__":
136+
unittest.main()

Lib/test/test_import/__init__.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,15 @@ def test_import_raises_ModuleNotFoundError(self):
364364
with self.assertRaises(ModuleNotFoundError):
365365
import something_that_should_not_exist_anywhere
366366

367+
def test_import_null_byte_in_name_raises_ModuleNotFoundError(self):
368+
# gh-150633: module names containing null bytes should not
369+
# lead to duplicates in sys.modules
370+
before = set(sys.modules)
371+
with self.assertRaises(ModuleNotFoundError):
372+
__import__('zipimport\x00junk')
373+
374+
self.assertEqual(set(sys.modules), before)
375+
367376
def test_from_import_missing_module_raises_ModuleNotFoundError(self):
368377
with self.assertRaises(ModuleNotFoundError):
369378
from something_that_should_not_exist_anywhere import blah

Lib/test/test_io/test_textio.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1560,6 +1560,56 @@ def closed(self):
15601560
wrapper = self.TextIOWrapper(raw)
15611561
wrapper.close() # should not crash
15621562

1563+
def test_reentrant_detach_during_flush(self):
1564+
# gh-143008: Reentrant detach() during flush should not crash.
1565+
1566+
class DetachOnce(self.BufferedRandom):
1567+
wrapper = None
1568+
1569+
def detach_once(self):
1570+
original = self.wrapper
1571+
self.wrapper = None
1572+
if original is not None:
1573+
original.detach()
1574+
original.flush()
1575+
1576+
class DetachOnFlush(DetachOnce):
1577+
def flush(self):
1578+
self.detach_once()
1579+
1580+
class DetachOnWrite(DetachOnce):
1581+
def write(self, b):
1582+
self.detach_once()
1583+
return len(b)
1584+
1585+
# Separate reference for after detach_once.
1586+
wrapper = None
1587+
1588+
def make_text(buffer):
1589+
nonlocal wrapper
1590+
buffer.wrapper = self.TextIOWrapper(buffer, encoding='utf-8')
1591+
wrapper = buffer.wrapper
1592+
1593+
# Many calls could result in the same null self->buffer crash.
1594+
tests = [
1595+
('truncate', lambda: wrapper.truncate(0)),
1596+
('close', lambda: wrapper.close()),
1597+
('detach', lambda: wrapper.detach()),
1598+
('seek', lambda: wrapper.seek(0)),
1599+
('tell', lambda: wrapper.tell()),
1600+
('reconfigure', lambda: wrapper.reconfigure(line_buffering=True)),
1601+
]
1602+
for name, method in tests:
1603+
with self.subTest(name):
1604+
make_text(DetachOnFlush(self.MockRawIO()))
1605+
self.assertRaisesRegex(ValueError, "detached", method)
1606+
1607+
# Should not crash.
1608+
with self.subTest('read via writeflush'):
1609+
make_text(DetachOnWrite(self.MockRawIO()))
1610+
wrapper.write('x')
1611+
self.assertRaisesRegex(ValueError, "detached", wrapper.read)
1612+
15631613

15641614
class PyTextIOWrapperTest(TextIOWrapperTest, PyTestCase):
15651615
shutdown_error = "LookupError: unknown encoding: ascii"

Lib/test/test_os/test_os.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3793,7 +3793,6 @@ async def test_trailers(self):
37933793
@requires_headers_trailers
37943794
@requires_32b
37953795
async def test_headers_overflow_32bits(self):
3796-
self.server.handler_instance.accumulate = False
37973796
with self.assertRaises(OSError) as cm:
37983797
await self.async_sendfile(self.sockno, self.fileno, 0, 0,
37993798
headers=[b"x" * 2**16] * 2**15)
@@ -3802,7 +3801,6 @@ async def test_headers_overflow_32bits(self):
38023801
@requires_headers_trailers
38033802
@requires_32b
38043803
async def test_trailers_overflow_32bits(self):
3805-
self.server.handler_instance.accumulate = False
38063804
with self.assertRaises(OSError) as cm:
38073805
await self.async_sendfile(self.sockno, self.fileno, 0, 0,
38083806
trailers=[b"x" * 2**16] * 2**15)
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix the frozen importer accepting module names with embedded null bytes, which
2+
caused it to bypass the :data:`sys.modules` cache and create duplicate module
3+
objects.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix a crash, when there's no memory left on a device,
2+
which happened in code compilation.
3+
Now it raises a proper :exc:`MemoryError`.

0 commit comments

Comments
 (0)