From 4a741d4b1ae989f5749785bea950ea7a104a26eb Mon Sep 17 00:00:00 2001 From: Titouan Christophe Date: Mon, 8 Jun 2026 23:20:50 +0200 Subject: [PATCH] package/python3: add patch for CVE-2026-8328 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This fixes the following vulnerability: - CVE-2026-8328: The ftpcp() function in Lib/ftplib.py was not updated when CVE-2021-4189 was fixed. While makepasv() was patched to replace server-supplied PASV host addresses with the actual peer address (getpeername()[0]), ftpcp() still calls parse227() directly and passes the raw attacker-controllable IP address and port to target.sendport(). This patch is related to CVE-2021-4189. https://www.cve.org/CVERecord?id=CVE-2026-8328 Signed-off-by: Titouan Christophe Signed-off-by: Thomas Perale --- ...E-2021-4189-PASV-fix-to-ftplib-ftpcp.patch | 118 ++++++++++++++++++ package/python3/python3.mk | 2 + 2 files changed, 120 insertions(+) create mode 100644 package/python3/0015-Apply-CVE-2021-4189-PASV-fix-to-ftplib-ftpcp.patch diff --git a/package/python3/0015-Apply-CVE-2021-4189-PASV-fix-to-ftplib-ftpcp.patch b/package/python3/0015-Apply-CVE-2021-4189-PASV-fix-to-ftplib-ftpcp.patch new file mode 100644 index 0000000000..c39ebfa1c3 --- /dev/null +++ b/package/python3/0015-Apply-CVE-2021-4189-PASV-fix-to-ftplib-ftpcp.patch @@ -0,0 +1,118 @@ +From b026be60f7e023719a4d8de89ae3c3248b7c5d40 Mon Sep 17 00:00:00 2001 +From: "Gregory P. Smith" <68491+gpshead@users.noreply.github.com> +Date: Wed, 13 May 2026 10:33:43 -0700 +Subject: [PATCH] gh-87451: Apply CVE-2021-4189 PASV fix to ftplib.ftpcp() + (GH-149648) + +ftpcp() called parse227() directly and passed the source server's +self-reported PASV IPv4 address to the target server's PORT command, +bypassing the CVE-2021-4189 fix that was applied only to FTP.makepasv(). +A malicious source FTP server could use this to redirect the target +server's data connection to an arbitrary host:port (SSRF). + +ftpcp() now uses the source server's actual peer address, honoring the +existing trust_server_pasv_ipv4_address opt-out, the same as makepasv(). + +Thanks to Qi Ding at Aurascape AI for the report. (GHSA-w8c5-q2xf-gf7c) +(cherry picked from commit eac4fe3b2c77693790a5ef7dfab127c1fee81bf9) + +Co-authored-by: Gregory P. Smith <68491+gpshead@users.noreply.github.com> + +Upstream: https://github.com/python/cpython/pull/149795 +CVE: CVE-2026-8328 +Signed-off-by: Titouan Christophe +--- + Lib/ftplib.py | 11 +++++- + Lib/test/test_ftplib.py | 36 ++++++++++++++++++- + ...6-05-10-18-05-32.gh-issue-87451.XkKB6M.rst | 6 ++++ + 3 files changed, 51 insertions(+), 2 deletions(-) + create mode 100644 Misc/NEWS.d/next/Security/2026-05-10-18-05-32.gh-issue-87451.XkKB6M.rst + +diff --git a/Lib/ftplib.py b/Lib/ftplib.py +index 10c5d1ea08ab115..463da58de85d721 100644 +--- a/Lib/ftplib.py ++++ b/Lib/ftplib.py +@@ -883,7 +883,16 @@ def ftpcp(source, sourcename, target, targetname = '', type = 'I'): + type = 'TYPE ' + type + source.voidcmd(type) + target.voidcmd(type) +- sourcehost, sourceport = parse227(source.sendcmd('PASV')) ++ # Don't trust the IPv4 address the source server advertises in its PASV ++ # reply: a malicious source could otherwise point the target's data ++ # connection at an arbitrary host (SSRF). A caller that needs the old ++ # behavior can set trust_server_pasv_ipv4_address on the source FTP ++ # object. See FTP.makepasv(), which applies the same rule. ++ untrusted_host, sourceport = parse227(source.sendcmd('PASV')) ++ if source.trust_server_pasv_ipv4_address: ++ sourcehost = untrusted_host ++ else: ++ sourcehost = source.sock.getpeername()[0] + target.sendport(sourcehost, sourceport) + # RFC 959: the user must "listen" [...] BEFORE sending the + # transfer request. +diff --git a/Lib/test/test_ftplib.py b/Lib/test/test_ftplib.py +index 204a77d14f03a50..7542f015f78c421 100644 +--- a/Lib/test/test_ftplib.py ++++ b/Lib/test/test_ftplib.py +@@ -16,7 +16,7 @@ + except ImportError: + ssl = None + +-from unittest import TestCase, skipUnless ++from unittest import mock, TestCase, skipUnless + from test import support + from test.support import threading_helper + from test.support import socket_helper +@@ -1142,6 +1142,40 @@ def testTimeoutDirectAccess(self): + ftp.close() + + ++class TestFtpcpSecurity(TestCase): ++ """ftpcp() must not trust the host a source server advertises in PASV. ++ ++ A malicious source server can otherwise redirect the target server's ++ data connection to an arbitrary host:port (SSRF), so ftpcp() uses the ++ source server's actual peer address instead, the same as FTP.makepasv(). ++ """ ++ ++ def _make_pair(self, *, advertised_host, real_host, trust=False): ++ source = mock.Mock(spec=ftplib.FTP) ++ source.trust_server_pasv_ipv4_address = trust ++ source.sock.getpeername.return_value = (real_host, 21) ++ # PASV replies give the host as comma-separated octets, not dotted. ++ advertised = advertised_host.replace('.', ',') ++ source.sendcmd.side_effect = lambda cmd: ( ++ f'227 Entering Passive Mode ({advertised},1,2).' ++ if cmd == 'PASV' else '150 ok') ++ target = mock.Mock(spec=ftplib.FTP) ++ target.sendcmd.return_value = '150 ok' ++ return source, target ++ ++ def test_ftpcp_ignores_untrusted_pasv_host(self): ++ source, target = self._make_pair(advertised_host='10.0.0.5', ++ real_host='198.51.100.7') ++ ftplib.ftpcp(source, 'a', target, 'b') ++ target.sendport.assert_called_once_with('198.51.100.7', 258) ++ ++ def test_ftpcp_trust_server_pasv_ipv4_address(self): ++ source, target = self._make_pair(advertised_host='10.0.0.5', ++ real_host='198.51.100.7', trust=True) ++ ftplib.ftpcp(source, 'a', target, 'b') ++ target.sendport.assert_called_once_with('10.0.0.5', 258) ++ ++ + class MiscTestCase(TestCase): + def test__all__(self): + not_exported = { +diff --git a/Misc/NEWS.d/next/Security/2026-05-10-18-05-32.gh-issue-87451.XkKB6M.rst b/Misc/NEWS.d/next/Security/2026-05-10-18-05-32.gh-issue-87451.XkKB6M.rst +new file mode 100644 +index 000000000000000..21a79c3e0e7db74 +--- /dev/null ++++ b/Misc/NEWS.d/next/Security/2026-05-10-18-05-32.gh-issue-87451.XkKB6M.rst +@@ -0,0 +1,6 @@ ++The :mod:`ftplib` module's undocumented ``ftpcp`` function no longer trusts ++the IPv4 address value returned from the source server in response to the ++``PASV`` command by default, completing the fix for CVE-2021-4189. As with ++:class:`ftplib.FTP`, the former behavior can be re-enabled by setting the ++``trust_server_pasv_ipv4_address`` attribute on the source :class:`ftplib.FTP` ++instance to ``True``. Thanks to Qi Deng at Aurascape AI for the report. diff --git a/package/python3/python3.mk b/package/python3/python3.mk index 96be4bd651..52f06e08df 100644 --- a/package/python3/python3.mk +++ b/package/python3/python3.mk @@ -17,6 +17,8 @@ PYTHON3_CPE_ID_PRODUCT = python PYTHON3_IGNORE_CVES += CVE-2026-3276 # 0014-tarfile-data_filter-validate-written-link.patch PYTHON3_IGNORE_CVES += CVE-2026-7774 +# 0015-Apply-CVE-2021-4189-PASV-fix-to-ftplib-ftpcp.patch +PYTHON3_IGNORE_CVES += CVE-2026-8328 # This host Python is installed in $(HOST_DIR), as it is needed when # cross-compiling third-party Python modules.