Submitted By: Joe Locash <jlocash at gmail dot com>
Date: 2026-06-23
Initial Package Version: 3.14.6
Upstream Status: Applied
Origin: Upstream:
https://github.com/python/cpython/pull/151565
https://github.com/python/cpython/pull/151998
https://github.com/python/cpython/pull/151565
Description: Upsream fixes for CVE-2026-12003, CVE-2026-11940, and
CVE-2026-0864.
From 4d228c411bf23ff71e36db7d285d7e23e875b926 Mon Sep 17 00:00:00 2001
From: Steve Dower <steve.dower@python.org>
Date: Wed, 17 Jun 2026 00:16:06 +0100
Subject: [PATCH 1/4] gh-151544: Fixes CVE-2026-12003 by removing the fallback
to %VPATH%/Modules/Setup.local for discovering sources in getpath.py
(GH-151545) (cherry picked from commit
9e863fab283eddca9c2a8f9d1ee30f4dc243e314)
Co-authored-by: Steve Dower <steve.dower@python.org>
---
Makefile.pre.in | 2 ++
...2026-06-16-14-58-02.gh-issue-151544._bexVy.rst | 4 ++++
Modules/getpath.py | 15 ++++-----------
3 files changed, 10 insertions(+), 11 deletions(-)
create mode 100644 Misc/NEWS.d/next/Security/2026-06-16-14-58-02.gh-issue-151544._bexVy.rst
diff --git a/Makefile.pre.in b/Makefile.pre.in
index f86d7363e0900f..75a892e94b0965 100644
|
a
|
b
|
Programs/_bootstrap_python.o: Programs/_bootstrap_python.c $(BOOTSTRAP_HEADERS)
|
| 1679 | 1679 | _bootstrap_python: $(LIBRARY_OBJS_OMIT_FROZEN) Programs/_bootstrap_python.o Modules/getpath.o Modules/Setup.local |
| 1680 | 1680 | $(LINKCC) $(PY_LDFLAGS_NOLTO) -o $@ $(LIBRARY_OBJS_OMIT_FROZEN) \ |
| 1681 | 1681 | Programs/_bootstrap_python.o Modules/getpath.o $(LIBS) $(MODLIBS) $(SYSLIBS) |
| | 1682 | # Dummy pybuilddir.txt is needed for _bootstrap_python to be runnable |
| | 1683 | @echo "none" > ./pybuilddir.txt |
| 1682 | 1684 | |
| 1683 | 1685 | |
| 1684 | 1686 | ############################################################################ |
diff --git a/Misc/NEWS.d/next/Security/2026-06-16-14-58-02.gh-issue-151544._bexVy.rst b/Misc/NEWS.d/next/Security/2026-06-16-14-58-02.gh-issue-151544._bexVy.rst
new file mode 100644
index 00000000000000..418e3b4b967794
|
-
|
+
|
|
| | 1 | :file:`Modules/Setup.local` is no longer used as a landmark to discover |
| | 2 | whether Python is running in a source tree, as it could potentially affect |
| | 3 | actual installs. The :file:`pybuilddir.txt` file is now the sole indicator |
| | 4 | of running in a source tree. |
diff --git a/Modules/getpath.py b/Modules/getpath.py
index b89d7427e3febd..0e4f1e87e7342a 100644
|
a
|
b
|
|
| 129 | 129 | # checked by looking for the BUILDDIR_TXT file, which contains the |
| 130 | 130 | # relative path to the platlib dir. The executable_dir value is |
| 131 | 131 | # derived from joining the VPATH preprocessor variable to the |
| 132 | | # directory containing pybuilddir.txt. If it is not found, the |
| 133 | | # BUILD_LANDMARK file is found, which is part of the source tree. |
| | 132 | # directory containing pybuilddir.txt. |
| 134 | 133 | # prefix is then found by searching up for a file that should only |
| 135 | 134 | # exist in the source tree, and the stdlib dir is set to prefix/Lib. |
| 136 | 135 | |
| … |
… |
|
| 177 | 176 | |
| 178 | 177 | if os_name == 'posix' or os_name == 'darwin': |
| 179 | 178 | BUILDDIR_TXT = 'pybuilddir.txt' |
| 180 | | BUILD_LANDMARK = 'Modules/Setup.local' |
| 181 | 179 | DEFAULT_PROGRAM_NAME = f'python{VERSION_MAJOR}' |
| 182 | 180 | STDLIB_SUBDIR = f'{platlibdir}/python{VERSION_MAJOR}.{VERSION_MINOR}{ABI_THREAD}' |
| 183 | 181 | STDLIB_LANDMARKS = [f'{STDLIB_SUBDIR}/os.py', f'{STDLIB_SUBDIR}/os.pyc'] |
| … |
… |
|
| 190 | 188 | |
| 191 | 189 | elif os_name == 'nt': |
| 192 | 190 | BUILDDIR_TXT = 'pybuilddir.txt' |
| 193 | | BUILD_LANDMARK = f'{VPATH}\\Modules\\Setup.local' |
| 194 | 191 | DEFAULT_PROGRAM_NAME = f'python' |
| 195 | 192 | STDLIB_SUBDIR = 'Lib' |
| 196 | 193 | STDLIB_LANDMARKS = [f'{STDLIB_SUBDIR}\\os.py', f'{STDLIB_SUBDIR}\\os.pyc'] |
| … |
… |
def search_up(prefix, *landmarks, test=isfile):
|
| 512 | 509 | platstdlib_dir = real_executable_dir |
| 513 | 510 | build_prefix = joinpath(real_executable_dir, VPATH) |
| 514 | 511 | except (FileNotFoundError, PermissionError): |
| 515 | | if isfile(joinpath(real_executable_dir, BUILD_LANDMARK)): |
| 516 | | build_prefix = joinpath(real_executable_dir, VPATH) |
| 517 | | if os_name == 'nt': |
| 518 | | # QUIRK: Windows builds need platstdlib_dir to be the executable |
| 519 | | # dir. Normally the builddir marker handles this, but in this |
| 520 | | # case we need to correct manually. |
| 521 | | platstdlib_dir = real_executable_dir |
| | 512 | # We used to check for an alternate landmark here, but now we require |
| | 513 | # BUILDDIR_TXT to exist. (gh-151544; CVE-2026-12003) |
| | 514 | pass |
| 522 | 515 | |
| 523 | 516 | if build_prefix: |
| 524 | 517 | if os_name == 'nt': |
From b76f1f9ba175c511063c086c083614b8bd5cd4bc Mon Sep 17 00:00:00 2001
From: Steve Dower <steve.dower@python.org>
Date: Wed, 17 Jun 2026 19:36:56 +0100
Subject: [PATCH 2/4] Add argv0 setting for Python process in WASM
---
Tools/wasm/wasi/__main__.py | 2 ++
1 file changed, 2 insertions(+)
diff --git a/Tools/wasm/wasi/__main__.py b/Tools/wasm/wasi/__main__.py
index b57bcaca924380..25c1829e1295e9 100644
|
a
|
b
|
def main():
|
| 421 | 421 | "--wasm max-wasm-stack=16777216 " |
| 422 | 422 | # Enable thread support; causes use of preview1. |
| 423 | 423 | # "--wasm threads=y --wasi threads=y " |
| | 424 | # Set argv0 to the Python process |
| | 425 | "--argv0 {PYTHON_WASM} " |
| 424 | 426 | # Map the checkout to / to load the stdlib from /Lib. |
| 425 | 427 | "--dir {HOST_DIR}::{GUEST_DIR} " |
| 426 | 428 | # Set PYTHONPATH to the sysconfig data. |
From b002dba22ac4bdec24e7b89cad3e4cc50dbec081 Mon Sep 17 00:00:00 2001
From: Steve Dower <steve.dower@python.org>
Date: Wed, 17 Jun 2026 20:46:53 +0100
Subject: [PATCH 3/4] Add symlink creation for pybuilddir.txt
---
Tools/wasm/wasi/__main__.py | 11 +++++++++--
1 file changed, 9 insertions(+), 2 deletions(-)
diff --git a/Tools/wasm/wasi/__main__.py b/Tools/wasm/wasi/__main__.py
index 25c1829e1295e9..3d15b9f62df000 100644
|
a
|
b
|
def configure_wasi_python(context, working_dir):
|
| 368 | 368 | file.write(f'#!/bin/sh\nexec {host_runner} {python_wasm} "$@"\n') |
| 369 | 369 | exec_script.chmod(0o755) |
| 370 | 370 | log("๐", f"Created {exec_script} (--host-runner)... ") |
| | 371 | pybuilddir_txt = working_dir / "pybuilddir.txt" |
| | 372 | if not pybuilddir_txt.exists(): |
| | 373 | os.symlink(CHECKOUT / "pybuilddir.txt", pybuilddir_txt) |
| | 374 | log("๐", f"Symlinked {pybuilddir_txt} to normal location") |
| 371 | 375 | sys.stdout.flush() |
| 372 | 376 | |
| 373 | 377 | |
| … |
… |
def clean_contents(context):
|
| 399 | 403 | if LOCAL_SETUP.read_bytes() == LOCAL_SETUP_MARKER: |
| 400 | 404 | log("๐งน", f"Deleting generated {LOCAL_SETUP} ...") |
| 401 | 405 | |
| | 406 | pybuilddir_txt = working_dir / "pybuilddir.txt" |
| | 407 | if pybuilddir_txt.exists(): |
| | 408 | log("๐งน", f"Deleting {pybuilddir_txt} ...") |
| | 409 | pybuilddir_txt.unlink() |
| | 410 | |
| 402 | 411 | |
| 403 | 412 | def build_steps(*steps): |
| 404 | 413 | """Construct a command from other steps.""" |
| … |
… |
def main():
|
| 421 | 430 | "--wasm max-wasm-stack=16777216 " |
| 422 | 431 | # Enable thread support; causes use of preview1. |
| 423 | 432 | # "--wasm threads=y --wasi threads=y " |
| 424 | | # Set argv0 to the Python process |
| 425 | | "--argv0 {PYTHON_WASM} " |
| 426 | 433 | # Map the checkout to / to load the stdlib from /Lib. |
| 427 | 434 | "--dir {HOST_DIR}::{GUEST_DIR} " |
| 428 | 435 | # Set PYTHONPATH to the sysconfig data. |
From c5125ff1e63a50694f17b712a600b4f971e8d1ff Mon Sep 17 00:00:00 2001
From: Steve Dower <steve.dower@python.org>
Date: Wed, 17 Jun 2026 21:02:34 +0100
Subject: [PATCH 4/4] Remove unnecessary deletion of pybuilddir.txt
---
Tools/wasm/wasi/__main__.py | 5 -----
1 file changed, 5 deletions(-)
diff --git a/Tools/wasm/wasi/__main__.py b/Tools/wasm/wasi/__main__.py
index 3d15b9f62df000..abe30fe0b4c77f 100644
|
a
|
b
|
def clean_contents(context):
|
| 403 | 403 | if LOCAL_SETUP.read_bytes() == LOCAL_SETUP_MARKER: |
| 404 | 404 | log("๐งน", f"Deleting generated {LOCAL_SETUP} ...") |
| 405 | 405 | |
| 406 | | pybuilddir_txt = working_dir / "pybuilddir.txt" |
| 407 | | if pybuilddir_txt.exists(): |
| 408 | | log("๐งน", f"Deleting {pybuilddir_txt} ...") |
| 409 | | pybuilddir_txt.unlink() |
| 410 | | |
| 411 | 406 | |
| 412 | 407 | def build_steps(*steps): |
| 413 | 408 | """Construct a command from other steps.""" |
From 6b66c843887161f1e82c61c9e9489ba7cf5d36cf Mon Sep 17 00:00:00 2001
From: Stan Ulbrych <stan@python.org>
Date: Tue, 23 Jun 2026 14:31:38 +0100
Subject: [PATCH] gh-151558: Fix symlink escape via `tarfile`
hardlink-extraction fallback (GH-151559) (cherry picked from commit
27dd970bf6b17ebca7c8ed486a40ab043ed7af8f)
Co-authored-by: Stan Ulbrych <stan@python.org>
---
Lib/tarfile.py | 3 +++
Lib/test/test_tarfile.py | 24 +++++++++++++++++++
...-06-10-13-08-19.gh-issue-151558.mL74i2.rst | 3 +++
3 files changed, 30 insertions(+)
create mode 100644 Misc/NEWS.d/next/Security/2026-06-10-13-08-19.gh-issue-151558.mL74i2.rst
diff --git a/Lib/tarfile.py b/Lib/tarfile.py
index e6734db24f642e8..63f23490e8a149e 100644
|
a
|
b
|
def makelink_with_filter(self, tarinfo, targetpath,
|
| 2782 | 2782 | "makelink_with_filter: if filter_function is not None, " |
| 2783 | 2783 | + "extraction_root must also not be None") |
| 2784 | 2784 | try: |
| | 2785 | filter_function( |
| | 2786 | unfiltered.replace(name=tarinfo.name, deep=False), |
| | 2787 | extraction_root) |
| 2785 | 2788 | filtered = filter_function(unfiltered, extraction_root) |
| 2786 | 2789 | except _FILTER_ERRORS as cause: |
| 2787 | 2790 | raise LinkFallbackError(tarinfo, unfiltered.name) from cause |
diff --git a/Lib/test/test_tarfile.py b/Lib/test/test_tarfile.py
index d974c7d46ec18c0..0fc7413be8db28c 100644
|
a
|
b
|
def test_sneaky_hardlink_fallback(self):
|
| 4344 | 4344 | self.expect_file("boom", symlink_to='../../link_here') |
| 4345 | 4345 | self.expect_file("c", symlink_to='b') |
| 4346 | 4346 | |
| | 4347 | @symlink_test |
| | 4348 | def test_sneaky_hardlink_fallback_deep(self): |
| | 4349 | # (CVE-2026-11940) |
| | 4350 | with ArchiveMaker() as arc: |
| | 4351 | arc.add("a/b/s", symlink_to=os.path.join("..", "escape")) |
| | 4352 | arc.add("s", hardlink_to=os.path.join("a", "b", "s")) |
| | 4353 | |
| | 4354 | with self.check_context(arc.open(), 'data'): |
| | 4355 | e = self.expect_exception( |
| | 4356 | tarfile.LinkFallbackError, |
| | 4357 | "link 's' would be extracted as a copy of " |
| | 4358 | + "'a/b/s', which was rejected") |
| | 4359 | self.assertIsInstance(e.__cause__, |
| | 4360 | tarfile.LinkOutsideDestinationError) |
| | 4361 | |
| | 4362 | for filter in 'tar', 'fully_trusted': |
| | 4363 | with self.subTest(filter), self.check_context(arc.open(), filter): |
| | 4364 | if not os_helper.can_symlink(): |
| | 4365 | self.expect_file("a/") |
| | 4366 | self.expect_file("a/b/") |
| | 4367 | else: |
| | 4368 | self.expect_file("a/b/s", symlink_to=os.path.join('..', 'escape')) |
| | 4369 | self.expect_file("s", symlink_to=os.path.join('..', 'escape')) |
| | 4370 | |
| 4347 | 4371 | @symlink_test |
| 4348 | 4372 | def test_exfiltration_via_symlink(self): |
| 4349 | 4373 | # (CVE-2025-4138) |
diff --git a/Misc/NEWS.d/next/Security/2026-06-10-13-08-19.gh-issue-151558.mL74i2.rst b/Misc/NEWS.d/next/Security/2026-06-10-13-08-19.gh-issue-151558.mL74i2.rst
new file mode 100644
index 000000000000000..74459d5680e21a3
|
-
|
+
|
|
| | 1 | Fixed an vulnerability in the :mod:`tarfile` ``data`` and ``tar`` extraction |
| | 2 | filters where crafted archives could create a symlink pointing outside the |
| | 3 | destination directory. This was a bypass of :cve:`2025-4330`. |
From 314f46b6c51d366460b66292b1aecfe9fb71de94 Mon Sep 17 00:00:00 2001
From: Seth Larson <seth@python.org>
Date: Tue, 23 Jun 2026 08:33:51 -0500
Subject: [PATCH] gh-143927: Normalize all line endings (CR, CRLF, and LF) in
configparser (GH-143929) (cherry picked from commit
5858e42c539dac8394636a6e9b30472b8994851f)
Co-authored-by: Seth Larson <seth@python.org>
---
Lib/configparser.py | 4 +++-
Lib/test/test_configparser.py | 11 +++++++++++
.../2026-01-16-11-58-19.gh-issue-143927.aviFeG.rst | 2 ++
3 files changed, 16 insertions(+), 1 deletion(-)
create mode 100644 Misc/NEWS.d/next/Security/2026-01-16-11-58-19.gh-issue-143927.aviFeG.rst
diff --git a/Lib/configparser.py b/Lib/configparser.py
index a53ac87276445ad..3c452afe8ade485 100644
|
a
|
b
|
def _write_section(self, fp, section_name, section_items, delimiter, unnamed=Fal
|
| 992 | 992 | value = self._interpolation.before_write(self, section_name, key, |
| 993 | 993 | value) |
| 994 | 994 | if value is not None or not self._allow_no_value: |
| 995 | | value = delimiter + str(value).replace('\n', '\n\t') |
| | 995 | # Convert all possible line-endings into '\n\t' |
| | 996 | value = (delimiter + str(value).replace('\r\n', '\n') |
| | 997 | .replace('\r', '\n').replace('\n', '\n\t')) |
| 996 | 998 | else: |
| 997 | 999 | value = "" |
| 998 | 1000 | fp.write("{}{}\n".format(key, value)) |
diff --git a/Lib/test/test_configparser.py b/Lib/test/test_configparser.py
index 8d8dd2a2bf27fbf..4783943f71a1092 100644
|
a
|
b
|
def test_default_case_sensitivity(self):
|
| 526 | 526 | cf.get(self.default_section, "Foo"), "Bar", |
| 527 | 527 | "could not locate option, expecting case-insensitive defaults") |
| 528 | 528 | |
| | 529 | def test_crlf_normalization(self): |
| | 530 | cf = self.newconfig({"key1": "a\nb","key2": "a\rb", "key3": "a\r\nb", "key4": "a\r\nb"}) |
| | 531 | buf = io.StringIO() |
| | 532 | cf.write(buf) |
| | 533 | cf_str = buf.getvalue() |
| | 534 | self.assertNotIn("\r", cf_str) |
| | 535 | self.assertNotIn("\r\n", cf_str) |
| | 536 | self.assertEqual(cf_str.count("\n"), 10) |
| | 537 | self.assertEqual(cf_str.count("\n\t"), 4) |
| | 538 | self.assertTrue(cf_str.endswith("\n\n")) |
| | 539 | |
| 529 | 540 | def test_parse_errors(self): |
| 530 | 541 | cf = self.newconfig() |
| 531 | 542 | self.parse_error(cf, configparser.ParsingError, |
diff --git a/Misc/NEWS.d/next/Security/2026-01-16-11-58-19.gh-issue-143927.aviFeG.rst b/Misc/NEWS.d/next/Security/2026-01-16-11-58-19.gh-issue-143927.aviFeG.rst
new file mode 100644
index 000000000000000..ca554997e5c3963
|
-
|
+
|
|
| | 1 | Normalize all line endings (CR, CRLF, and LF) to LF+TAB when writing |
| | 2 | multi-line configparser values. |