Skip to content

Commit 1f1d878

Browse files
committed
Merge branch 'main' into writer_canary
2 parents 0dbd2cc + b38be2e commit 1f1d878

12 files changed

Lines changed: 121 additions & 46 deletions

Doc/whatsnew/3.16.rst

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -412,9 +412,9 @@ io
412412
ipaddress
413413
---------
414414

415-
* Add :meth:`~ipaddress.IPv4Network.next_network` and
416-
:meth:`~ipaddress.IPv6Network.next_network` methods to find the next nearest
417-
network with a specific prefix size.
415+
* Add :meth:`IPv4Network.next_network() <ipaddress.IPv4Network.next_network>`
416+
and :meth:`IPv6Network.next_network() <ipaddress.IPv6Network.next_network>`
417+
methods to find the next nearest network with a specific prefix size.
418418
(Contributed by Faisal Mahmood in :gh:`87027`.)
419419

420420

Lib/ipaddress.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1124,11 +1124,15 @@ def next_network(self, next_prefix=None):
11241124
11251125
Args:
11261126
next_prefix: The desired next prefix length, if not specified the
1127-
same self.prefixlen will be used
1127+
same self.prefixlen will be used.
11281128
11291129
Returns:
11301130
An IPv(4|6) Network object of the next closest network.
11311131
1132+
Raises:
1133+
ValueError: If next_prefix is outside the range of valid prefix
1134+
lengths, or if no further network of that size exists.
1135+
11321136
"""
11331137
if next_prefix is None:
11341138
next_prefix = self.prefixlen
@@ -1150,15 +1154,13 @@ def next_network(self, next_prefix=None):
11501154
((new_netmask._ip & self.network_address._ip) >> bit_shift) + 1
11511155
) << bit_shift
11521156

1153-
try:
1154-
return self.__class__(
1155-
f"{self._string_from_ip_int(next_ip)}/{next_prefix}"
1156-
)
1157-
except OverflowError:
1157+
if next_ip > self._ALL_ONES:
11581158
raise ValueError(
11591159
f"out of address space, cannot make another /{next_prefix} "
11601160
"network"
1161-
) from None
1161+
)
1162+
1163+
return self.__class__((next_ip, next_prefix))
11621164

11631165

11641166
class _BaseConstants:

Lib/tarfile.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2815,7 +2815,11 @@ def makelink_with_filter(self, tarinfo, targetpath,
28152815
if os.path.lexists(targetpath):
28162816
# Avoid FileExistsError on following os.link.
28172817
os.unlink(targetpath)
2818-
os.link(tarinfo._link_target, targetpath)
2818+
# Resolve the target so the hard link points to the file
2819+
# itself. Otherwise os.link() may duplicate a symlink to a
2820+
# shallower location, where its relative target escapes the
2821+
# destination directory. (CVE-2026-82049)
2822+
os.link(os.path.realpath(tarinfo._link_target), targetpath)
28192823
return
28202824
except symlink_exception:
28212825
keyerror_to_extracterror = True

Lib/test/test_bytes.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1611,6 +1611,14 @@ def test_take_bytes(self):
16111611
self.assertRaises(BufferError, ba.take_bytes)
16121612
self.assertEqual(ba.take_bytes(), b'abc')
16131613

1614+
# Leaving one byte must not adopt the shared single-byte bytes object
1615+
# as the buffer.
1616+
ba = bytearray(b'abc')
1617+
self.assertEqual(ba.take_bytes(2), b'ab')
1618+
ba[0] = ord('A')
1619+
self.assertEqual(ba, bytearray(b'A'))
1620+
self.assertEqual(ord(b'c'), ord('c'))
1621+
16141622
@support.cpython_only # tests an implementation detail
16151623
def test_take_bytes_optimization(self):
16161624
# Validate optimization around taking lots of little chunks out of a
@@ -3055,5 +3063,18 @@ def resize_stress(ba):
30553063
with threading_helper.start_threads(threads):
30563064
pass
30573065

3066+
@threading_helper.reap_threads
3067+
@threading_helper.requires_working_threading()
3068+
def test_free_threading_bytearray_resize_other_thread(self):
3069+
# Shrinking a bytearray whose buffer another thread owns must not
3070+
# adopt the immortal single-byte bytes object a the buffer.
3071+
ba = bytearray(b'abc')
3072+
thread = threading.Thread(target=ba.resize, args=(1,))
3073+
with threading_helper.start_threads([thread]):
3074+
pass
3075+
ba[0] = ord('X')
3076+
self.assertEqual(ba, bytearray(b'X'))
3077+
self.assertEqual(ord(b'a'), ord('a'))
3078+
30583079
if __name__ == "__main__":
30593080
unittest.main()

Lib/test/test_capi/test_bytes.py

Lines changed: 34 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import sys
12
import textwrap
23
import unittest
34
from test import support
@@ -234,26 +235,41 @@ def test_decodeescape(self):
234235

235236
def test_resize(self):
236237
"""Test _PyBytes_Resize()"""
237-
resize = _testcapi.bytes_resize
238+
_resize = _testcapi.bytes_resize
239+
240+
def resize(obj, size, new):
241+
result = _resize(obj, size, new)
242+
if 1 <= len(result):
243+
if new or size != len(obj):
244+
# gh-156995: Make sure that the result is a fresh object.
245+
# Previously, _PyBytes_Resize(&obj, 1) returned a singleton
246+
# if _PyObject_IsUniquelyReferenced() is false.
247+
self.assertEqual(sys.getrefcount(result), 1)
248+
self.assertFalse(sys._is_immortal(result))
249+
else:
250+
# check that the result is the empty bytes string singleton
251+
self.assertTrue(sys._is_immortal(result))
252+
return result
238253

239254
for new in True, False:
240-
self.assertEqual(resize(b'abc', 0, new), b'')
241-
self.assertEqual(resize(b'abc', 1, new), b'a')
242-
self.assertEqual(resize(b'abc', 2, new), b'ab')
243-
self.assertEqual(resize(b'abc', 3, new), b'abc')
244-
b = resize(b'abc', 4, new)
245-
self.assertEqual(len(b), 4)
246-
self.assertEqual(b[:3], b'abc')
247-
248-
self.assertEqual(resize(b'a', 0, new), b'')
249-
self.assertEqual(resize(b'a', 1, new), b'a')
250-
b = resize(b'a', 2, new)
251-
self.assertEqual(len(b), 2)
252-
self.assertEqual(b[:1], b'a')
253-
254-
self.assertEqual(resize(b'', 0, new), b'')
255-
self.assertEqual(len(resize(b'', 1, new)), 1)
256-
self.assertEqual(len(resize(b'', 2, new)), 2)
255+
with self.subTest(new=new):
256+
self.assertEqual(resize(b'abc', 0, new), b'')
257+
self.assertEqual(resize(b'abc', 1, new), b'a')
258+
self.assertEqual(resize(b'abc', 2, new), b'ab')
259+
self.assertEqual(resize(b'abc', 3, new), b'abc')
260+
b = resize(b'abc', 4, new)
261+
self.assertEqual(len(b), 4)
262+
self.assertEqual(b[:3], b'abc')
263+
264+
self.assertEqual(resize(b'a', 0, new), b'')
265+
self.assertEqual(resize(b'a', 1, new), b'a')
266+
b = resize(b'a', 2, new)
267+
self.assertEqual(len(b), 2)
268+
self.assertEqual(b[:1], b'a')
269+
270+
self.assertEqual(resize(b'', 0, new), b'')
271+
self.assertEqual(len(resize(b'', 1, new)), 1)
272+
self.assertEqual(len(resize(b'', 2, new)), 2)
257273

258274
self.assertRaises(SystemError, resize, b'abc', -1, False)
259275
self.assertRaises(SystemError, resize, bytearray(b'abc'), 3, False)

Lib/test/test_ipaddress.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1596,9 +1596,15 @@ def testNextNetworkWithBadPrefix(self):
15961596

15971597
def testNextNetworkOutOfAddressSpace(self):
15981598
ipv4 = ipaddress.IPv4Network('255.255.255.0/24')
1599-
self.assertRaises(ValueError, ipv4.next_network)
1599+
self.assertRaisesRegex(
1600+
ValueError,
1601+
'out of address space, cannot make another /24 network',
1602+
ipv4.next_network)
16001603
ipv6 = ipaddress.IPv6Network('ffff:ffff:ffff:ffff:ffff:ffff:ffff:0/112')
1601-
self.assertRaises(ValueError, ipv6.next_network)
1604+
self.assertRaisesRegex(
1605+
ValueError,
1606+
'out of address space, cannot make another /112 network',
1607+
ipv6.next_network)
16021608

16031609
def testFancySubnetting(self):
16041610
self.assertEqual(sorted(self.ipv4_network.subnets(prefixlen_diff=3)),

Lib/test/test_tarfile.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4655,6 +4655,24 @@ def test_sneaky_hardlink_fallback_deep(self):
46554655
self.expect_file("a/b/s", symlink_to=os.path.join('..', 'escape'))
46564656
self.expect_file("s", symlink_to=os.path.join('..', 'escape'))
46574657

4658+
@symlink_test
4659+
@os_helper.skip_unless_hardlink
4660+
def test_sneaky_hardlink_relocation(self):
4661+
with ArchiveMaker() as arc:
4662+
arc.add("a/escape", content="decoy")
4663+
arc.add("a/b/s", symlink_to=os.path.join("..", "escape"))
4664+
arc.add("s", hardlink_to=os.path.join("a", "b", "s"))
4665+
4666+
for filter in 'data', 'tar':
4667+
with self.subTest(filter), self.check_context(arc.open(), filter):
4668+
self.expect_file("a/escape", content="decoy")
4669+
if os_helper.can_symlink():
4670+
self.expect_file("a/b/s", symlink_to=os.path.join('..', 'escape'))
4671+
else:
4672+
self.expect_file("a/b/s", content="decoy")
4673+
self.expect_file("s", content="decoy")
4674+
self.assertFalse((self.destdir / "s").is_symlink())
4675+
46584676
@symlink_test
46594677
def test_exfiltration_via_symlink(self):
46604678
# (CVE-2025-4138)
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Fix :class:`bytearray` sharing its buffer with the single-byte :class:`bytes`
2+
object of the same value, so that writing to the bytearray modified that
3+
:class:`bytes` object. This happened with :meth:`bytearray.take_bytes` when
4+
exactly one byte remained, and on the free-threaded build when a bytearray was
5+
shrunk to one byte from another thread.
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
1-
Add :meth:`~ipaddress.IPv4Network.next_network` and
2-
:meth:`~ipaddress.IPv6Network.next_network`. Patch by Faisal Mahmood.
1+
Add :meth:`IPv4Network.next_network() <ipaddress.IPv4Network.next_network>`
2+
and :meth:`IPv6Network.next_network() <ipaddress.IPv6Network.next_network>`.
3+
Patch by Faisal Mahmood.

Misc/NEWS.d/next/Library/2026-06-11-06-56-31.gh-issue-150994.gd1wVw.rst

Lines changed: 0 additions & 1 deletion
This file was deleted.

0 commit comments

Comments
 (0)