From a7b11641fe1ce780213bf6d4355b2d82016b3708934f8aaf6185481dd82ecd6d Mon Sep 17 00:00:00 2001 From: Daniel Garcia Date: Fri, 15 Dec 2023 12:09:56 +0000 Subject: [PATCH 1/7] Accepting request 1133399 from home:dgarcia:branches:devel:languages:python:Factory MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Update patch fix_configure_rst.patch - Update to 3.11.7: - Core and Builtins - gh-112625: Fixes a bug where a bytearray object could be cleared while iterating over an argument in the bytearray.join() method that could result in reading memory after it was freed. - gh-112388: Fix an error that was causing the parser to try to overwrite tokenizer errors. Patch by pablo Galindo - gh-112387: Fix error positions for decoded strings with backwards tokenize errors. Patch by Pablo Galindo - gh-112266: Change docstrings of __dict__ and __weakref__. - gh-109181: Speed up Traceback object creation by lazily compute the line number. Patch by Pablo Galindo - gh-102388: Fix a bug where iso2022_jp_3 and iso2022_jp_2004 codecs read out of bounds - gh-111366: Fix an issue in the codeop that was causing SyntaxError exceptions raised in the presence of invalid syntax to not contain precise error messages. Patch by Pablo Galindo - gh-111380: Fix a bug that was causing SyntaxWarning to appear twice when parsing if invalid syntax is encountered later. Patch by Pablo galindo - gh-88116: Traceback location ranges involving wide unicode characters (like emoji and asian characters) now are properly highlighted. Patch by Batuhan Taskaya and Pablo Galindo. - gh-94438: Fix a regression that prevented jumping across is None and is not None when debugging. Patch by Savannah Ostrowski. - gh-110696: Fix incorrect error message for invalid argument unpacking. Patch by Pablo Galindo - gh-110237: Fix missing error checks for calls to PyList_Append in _PyEval_MatchClass. - gh-109216: Fix possible memory leak in BUILD_MAP. - Library - gh-112618: Fix a caching bug relating to typing.Annotated. Annotated[str, True] is no longer identical to Annotated[str, 1]. - gh-112509: Fix edge cases that could cause a key to be present in both the __required_keys__ and __optional_keys__ attributes of a typing.TypedDict. Patch by Jelle Zijlstra. - gh-94722: Fix bug where comparison between instances of DocTest fails if one of them has None as its lineno. - gh-112105: Make readline.set_completer_delims() work with libedit - gh-111942: Fix SystemError in the TextIOWrapper constructor with non-encodable “errors” argument in non-debug mode. - gh-109538: Issue warning message instead of having RuntimeError be displayed when event loop has already been closed at StreamWriter.__del__(). - gh-111942: Fix crashes in io.TextIOWrapper.reconfigure() when pass invalid arguments, e.g. non-string encoding. - gh-111804: Remove posix.fallocate() under WASI as the underlying posix_fallocate() is not available in WASI preview2. - gh-111841: Fix truncating arguments on an embedded null character in os.putenv() and os.unsetenv() on Windows. - gh-111541: Fix doctest for SyntaxError not-builtin subclasses. - gh-110894: Call loop exception handler for exceptions in client_connected_cb of asyncio.start_server() so that applications can handle it. Patch by Kumar Aditya. - gh-111531: Fix reference leaks in bind_class() and bind_all() methods of tkinter widgets. - gh-111356: Added io.text_encoding(), io.DEFAULT_BUFFER_SIZE, and io.IncrementalNewlineDecoder to io.__all__. - gh-68166: Remove mention of not supported “vsapi” element type in tkinter.ttk.Style.element_create(). Add tests for element_create() and other ttk.Style methods. Add examples for element_create() in the documentation. - gh-111251: Fix _blake2 not checking for errors when initializing. - gh-111174: Fix crash in io.BytesIO.getbuffer() called repeatedly for empty BytesIO. - gh-111187: Postpone removal version for locale.getdefaultlocale() to Python 3.15. - gh-111159: Fix doctest output comparison for exceptions with notes. - gh-110910: Fix invalid state handling in asyncio.TaskGroup and asyncio.Timeout. They now raise proper RuntimeError if they are improperly used and are left in consistent state after this. - gh-111092: Make turtledemo run without default root enabled. - gh-110590: Fix a bug in _sre.compile() where TypeError would be overwritten by OverflowError when the code argument was a list of non-ints. - gh-65052: Prevent pdb from crashing when trying to display undisplayable objects - gh-110519: Deprecation warning about non-integer number in gettext now alwais refers to the line in the user code where gettext function or method is used. Previously it could refer to a line in gettext code. - gh-110378: contextmanager() and asynccontextmanager() context managers now close an invalid underlying generator object that yields more then one value. - gh-110365: Fix termios.tcsetattr() bug that was overwritting existing errors during parsing integers from term list. - gh-110196: Add __reduce__ method to IPv6Address in order to keep scope_id - gh-109747: Improve errors for unsupported look-behind patterns. Now re.error is raised instead of OverflowError or RuntimeError for too large width of look-behind pattern. - gh-109786: Fix possible reference leaks and crash when re-enter the __next__() method of itertools.pairwise. - gh-108791: Improved error handling in pdb command line interface, making it produce more concise error messages. - gh-73561: Omit the interface scope from an IPv6 address when used as Host header by http.client. - gh-86826: zipinfo now supports the full range of values in the TZ string determined by RFC 8536 and detects all invalid formats. Both Python and C implementations now raise exceptions of the same type on invalid data. - bpo-41422: Fixed memory leaks of pickle.Pickler and pickle.Unpickler involving cyclic references via the internal memo mapping. - bpo-40262: The ssl.SSLSocket.recv_into() method no longer requires the buffer argument to implement __len__ and supports buffers with arbitrary item size. - bpo-35191: Fix unexpected integer truncation in socket.setblocking() which caused it to interpret multiples of 2**32 as False. - Documentation - gh-108826: dis module command-line interface is now mentioned in documentation. - Tests - gh-110367: Make regrtest --verbose3 option compatible with --huntrleaks -jN options. The ./python -m test -j1 -R 3:3 --verbose3 command now works as expected. Patch by Victor Stinner. - gh-111309: distutils tests can now be run via unittest. - gh-111165: Remove no longer used functions run_unittest() and run_doctest() and class BasicTestRunner from the test.support module. - gh-110932: Fix regrtest if the SOURCE_DATE_EPOCH environment variable is defined: use the variable value as the random seed. Patch by Victor Stinner. - gh-110995: test_gdb: Fix detection of gdb built without Python scripting support. Patch by Victor Stinner. - gh-110918: Test case matching patterns specified by options --match, --ignore, --matchfile and --ignorefile are now tested in the order of specification, and the last match determines whether the test case be run or ignored. - gh-110647: Fix test_stress_modifying_handlers() of test_signal. Patch by Victor Stinner. - gh-103053: Fix test_tools.test_freeze on FreeBSD: run “make distclean” instead of “make clean” in the copied source directory to remove also the “python” program. Patch by Victor Stinner. - gh-110167: Fix a deadlock in test_socket when server fails with a timeout but the client is still running in its thread. Don’t hold a lock to call cleanup functions in doCleanups(). One of the cleanup function waits until the client completes, whereas the client could deadlock if it called addCleanup() in such situation. Patch by Victor Stinner. - gh-110388: Add tests for tty. - gh-81002: Add tests for termios. - gh-110267: Add tests for pickling and copying PyStructSequence objects. Patched by Xuehai Pan. - gh-109974: Fix race conditions in test_threading lock tests. Wait until a condition is met rather than using time.sleep() with a hardcoded number of seconds. Patch by Victor Stinner. - gh-109972: Split test_gdb.py file into a test_gdb package made of multiple tests, so tests can now be run in parallel. Patch by Victor Stinner. - gh-104736: Fix test_gdb on Python built with LLVM clang 16 on Linux ppc64le (ex: Fedora 38). Search patterns in gdb “bt” command output to detect when gdb fails to retrieve the traceback. For example, skip a test if Backtrace stopped: frame did not save the PC is found. Patch by Victor Stinner. - gh-108927: Fixed order dependence in running tests in the same process when a test that has submodules (e.g. test_importlib) follows a test that imports its submodule (e.g. test_importlib.util) and precedes a test (e.g. test_unittest or test_compileall) that uses that submodule. - Build - gh-103053: “make check-clean-src” now also checks if the “python” program is found in the source directory: fail with an error if it does exist. Patch by Victor Stinner. - gh-109191: Fix compile error when building with recent versions of libedit. - IDLE - bpo-35668: Add docstrings to the IDLE debugger module. Fix two bugs: initialize Idb.botframe (should be in Bdb); in Idb.in_rpc_code, check whether prev_frame is None before trying to use it. Greatly expand test_debugger. - C API - gh-112438: Fix support of format units “es”, “et”, “es#”, and “et#” in nested tuples in PyArg_ParseTuple()-like functions. - gh-109521: PyImport_GetImporter() now sets RuntimeError if it fails to get sys.path_hooks or sys.path_importer_cache or they are not list and dict correspondingly. Previously it could return NULL without setting error in obscure cases, crash or raise SystemError if these attributes have wrong type. OBS-URL: https://build.opensuse.org/request/show/1133399 OBS-URL: https://build.opensuse.org/package/show/devel:languages:python:Factory/python311?expand=0&rev=89 --- Python-3.11.7.tar.xz | 3 + Python-3.11.7.tar.xz.asc | 16 ++++ fix_configure_rst.patch | 26 +++--- python311.changes | 197 +++++++++++++++++++++++++++++++++++++++ python311.spec | 2 +- 5 files changed, 230 insertions(+), 14 deletions(-) create mode 100644 Python-3.11.7.tar.xz create mode 100644 Python-3.11.7.tar.xz.asc diff --git a/Python-3.11.7.tar.xz b/Python-3.11.7.tar.xz new file mode 100644 index 0000000..8d3a65d --- /dev/null +++ b/Python-3.11.7.tar.xz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:18e1aa7e66ff3a58423d59ed22815a6954e53342122c45df20c96877c062b9b7 +size 20074108 diff --git a/Python-3.11.7.tar.xz.asc b/Python-3.11.7.tar.xz.asc new file mode 100644 index 0000000..3eee3d2 --- /dev/null +++ b/Python-3.11.7.tar.xz.asc @@ -0,0 +1,16 @@ +-----BEGIN PGP SIGNATURE----- + +iQIzBAABCAAdFiEEz9yiRbEEPPKl+Xhl/+h0BBaL2EcFAmVuFigACgkQ/+h0BBaL +2EeHPg/+LU5xs2ZDrQogDcH+A1v8RyursiggypdM5hXTrsFsTCIk4iekcI9xkhG1 +ltNX4UuCe5PUEbTgtaWP0ncXARrUnPCoQaQ1sHVDTYoHegancsk+sXZc1JM7qr0p +Y4Ig6mKjuHFMXCInQSI2GaH4t5r4Z1jGk/PGrecIHOPJgqfA/6Z3TBF5N+y3jEvS +2QazMB298q4RDhh9m3REe8LwFPHDlfw9eRohv0MB8xygg9KtxhLZrN7gLBQZvKGD +ihNw6EgJj5OZ0dvwKCCXnlZuwknuJW7vAOPHhYeenPdVdYCGoRSyN7JdD07L+5AG +O14l2rqZrz5Eu28by+kAUrcPYAfAXekw1PmtT3HSd9U/nqnUiTkkJcjyGG/e3cjJ +sUDKMNCSBq0G7j5DB3bB6VHkZjVuz+T+iR5QdfJ4kI2pYSuE/rUj1rhkUXApYsHl +7Wff0QbOW6QT1wCtQcMpJSzkTDVJVYxiqrko/ihlOhphDHYLdOIGOrxWAUwc06x/ +BhJD6tM1kEVZvifoJp1OsNwDzZ/Ku6CUs05E1vWxdeNVeANyKAgCZ5hOVmhnv866 +11zfgo/znRsMzMIyJuy0bhO0C6omVLzzfhipAbZM2jDorn37xxV0v/I0pceNtLrp +YR7Tjs7+Ihe6/oItjW53j9T7ANdgQ1RVDg98lKlPFNL+hxfctwY= +=0Pkd +-----END PGP SIGNATURE----- diff --git a/fix_configure_rst.patch b/fix_configure_rst.patch index bd08f1d..e45e240 100644 --- a/fix_configure_rst.patch +++ b/fix_configure_rst.patch @@ -3,37 +3,37 @@ Misc/NEWS | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) -Index: Python-3.11.6/Doc/using/configure.rst +Index: Python-3.11.7/Doc/using/configure.rst =================================================================== ---- Python-3.11.6.orig/Doc/using/configure.rst -+++ Python-3.11.6/Doc/using/configure.rst +--- Python-3.11.7.orig/Doc/using/configure.rst ++++ Python-3.11.7/Doc/using/configure.rst @@ -41,7 +41,6 @@ General Options See :data:`sys.int_info.bits_per_digit `. --.. cmdoption:: --with-cxx-main - .. cmdoption:: --with-cxx-main=COMPILER +-.. option:: --with-cxx-main + .. option:: --with-cxx-main=COMPILER Compile the Python ``main()`` function and link Python executable with C++ @@ -527,13 +526,11 @@ macOS Options See ``Mac/README.rst``. --.. cmdoption:: --enable-universalsdk - .. cmdoption:: --enable-universalsdk=SDKDIR +-.. option:: --enable-universalsdk + .. option:: --enable-universalsdk=SDKDIR Create a universal binary build. *SDKDIR* specifies which macOS SDK should be used to perform the build (default is no). --.. cmdoption:: --enable-framework - .. cmdoption:: --enable-framework=INSTALLDIR +-.. option:: --enable-framework + .. option:: --enable-framework=INSTALLDIR Create a Python.framework rather than a traditional Unix install. Optional -Index: Python-3.11.6/Misc/NEWS +Index: Python-3.11.7/Misc/NEWS =================================================================== ---- Python-3.11.6.orig/Misc/NEWS -+++ Python-3.11.6/Misc/NEWS -@@ -8708,7 +8708,7 @@ C API +--- Python-3.11.7.orig/Misc/NEWS ++++ Python-3.11.7/Misc/NEWS +@@ -9012,7 +9012,7 @@ C API - bpo-40939: Removed documentation for the removed ``PyParser_*`` C API. - bpo-43795: The list in :ref:`limited-api-list` now shows the public name diff --git a/python311.changes b/python311.changes index 944ef78..af0f2b6 100644 --- a/python311.changes +++ b/python311.changes @@ -1,3 +1,200 @@ +------------------------------------------------------------------- +Fri Dec 15 10:04:33 UTC 2023 - Daniel Garcia + +- Update patch fix_configure_rst.patch +- Update to 3.11.7: + - Core and Builtins + - gh-112625: Fixes a bug where a bytearray object could be cleared + while iterating over an argument in the bytearray.join() method + that could result in reading memory after it was freed. + - gh-112388: Fix an error that was causing the parser to try to + overwrite tokenizer errors. Patch by pablo Galindo + - gh-112387: Fix error positions for decoded strings with + backwards tokenize errors. Patch by Pablo Galindo + - gh-112266: Change docstrings of __dict__ and __weakref__. + - gh-109181: Speed up Traceback object creation by lazily compute + the line number. Patch by Pablo Galindo + - gh-102388: Fix a bug where iso2022_jp_3 and iso2022_jp_2004 + codecs read out of bounds + - gh-111366: Fix an issue in the codeop that was causing + SyntaxError exceptions raised in the presence of invalid syntax + to not contain precise error messages. Patch by Pablo Galindo + - gh-111380: Fix a bug that was causing SyntaxWarning to appear + twice when parsing if invalid syntax is encountered later. Patch + by Pablo galindo + - gh-88116: Traceback location ranges involving wide unicode + characters (like emoji and asian characters) now are properly + highlighted. Patch by Batuhan Taskaya and Pablo Galindo. + - gh-94438: Fix a regression that prevented jumping across is None + and is not None when debugging. Patch by Savannah Ostrowski. + - gh-110696: Fix incorrect error message for invalid argument + unpacking. Patch by Pablo Galindo + - gh-110237: Fix missing error checks for calls to PyList_Append + in _PyEval_MatchClass. + - gh-109216: Fix possible memory leak in BUILD_MAP. + + - Library + - gh-112618: Fix a caching bug relating to typing.Annotated. + Annotated[str, True] is no longer identical to Annotated[str, + 1]. + - gh-112509: Fix edge cases that could cause a key to be present + in both the __required_keys__ and __optional_keys__ attributes + of a typing.TypedDict. Patch by Jelle Zijlstra. + - gh-94722: Fix bug where comparison between instances of DocTest + fails if one of them has None as its lineno. + - gh-112105: Make readline.set_completer_delims() work with + libedit + - gh-111942: Fix SystemError in the TextIOWrapper constructor with + non-encodable “errors” argument in non-debug mode. + - gh-109538: Issue warning message instead of having RuntimeError + be displayed when event loop has already been closed at + StreamWriter.__del__(). + - gh-111942: Fix crashes in io.TextIOWrapper.reconfigure() when + pass invalid arguments, e.g. non-string encoding. + - gh-111804: Remove posix.fallocate() under WASI as the underlying + posix_fallocate() is not available in WASI preview2. + - gh-111841: Fix truncating arguments on an embedded null + character in os.putenv() and os.unsetenv() on Windows. + - gh-111541: Fix doctest for SyntaxError not-builtin subclasses. + - gh-110894: Call loop exception handler for exceptions in + client_connected_cb of asyncio.start_server() so that + applications can handle it. Patch by Kumar Aditya. + - gh-111531: Fix reference leaks in bind_class() and bind_all() + methods of tkinter widgets. + - gh-111356: Added io.text_encoding(), io.DEFAULT_BUFFER_SIZE, and + io.IncrementalNewlineDecoder to io.__all__. + - gh-68166: Remove mention of not supported “vsapi” element type + in tkinter.ttk.Style.element_create(). Add tests for + element_create() and other ttk.Style methods. Add examples for + element_create() in the documentation. + - gh-111251: Fix _blake2 not checking for errors when + initializing. + - gh-111174: Fix crash in io.BytesIO.getbuffer() called repeatedly + for empty BytesIO. + - gh-111187: Postpone removal version for + locale.getdefaultlocale() to Python 3.15. + - gh-111159: Fix doctest output comparison for exceptions with + notes. + - gh-110910: Fix invalid state handling in asyncio.TaskGroup and + asyncio.Timeout. They now raise proper RuntimeError if they are + improperly used and are left in consistent state after this. + - gh-111092: Make turtledemo run without default root enabled. + - gh-110590: Fix a bug in _sre.compile() where TypeError would be + overwritten by OverflowError when the code argument was a list + of non-ints. + - gh-65052: Prevent pdb from crashing when trying to display + undisplayable objects + - gh-110519: Deprecation warning about non-integer number in + gettext now alwais refers to the line in the user code where + gettext function or method is used. Previously it could refer to + a line in gettext code. + - gh-110378: contextmanager() and asynccontextmanager() context + managers now close an invalid underlying generator object that + yields more then one value. + - gh-110365: Fix termios.tcsetattr() bug that was overwritting + existing errors during parsing integers from term list. + - gh-110196: Add __reduce__ method to IPv6Address in order to keep + scope_id + - gh-109747: Improve errors for unsupported look-behind patterns. + Now re.error is raised instead of OverflowError or RuntimeError + for too large width of look-behind pattern. + - gh-109786: Fix possible reference leaks and crash when re-enter + the __next__() method of itertools.pairwise. + - gh-108791: Improved error handling in pdb command line + interface, making it produce more concise error messages. + - gh-73561: Omit the interface scope from an IPv6 address when + used as Host header by http.client. + - gh-86826: zipinfo now supports the full range of values in the + TZ string determined by RFC 8536 and detects all invalid + formats. Both Python and C implementations now raise exceptions + of the same type on invalid data. + - bpo-41422: Fixed memory leaks of pickle.Pickler and + pickle.Unpickler involving cyclic references via the internal + memo mapping. + - bpo-40262: The ssl.SSLSocket.recv_into() method no longer + requires the buffer argument to implement __len__ and supports + buffers with arbitrary item size. + - bpo-35191: Fix unexpected integer truncation in + socket.setblocking() which caused it to interpret multiples of + 2**32 as False. + + - Documentation + - gh-108826: dis module command-line interface is now mentioned in + documentation. + + - Tests + - gh-110367: Make regrtest --verbose3 option compatible with + --huntrleaks -jN options. The ./python -m test -j1 -R 3:3 + --verbose3 command now works as expected. Patch by Victor + Stinner. + - gh-111309: distutils tests can now be run via unittest. + - gh-111165: Remove no longer used functions run_unittest() and + run_doctest() and class BasicTestRunner from the test.support + module. + - gh-110932: Fix regrtest if the SOURCE_DATE_EPOCH environment + variable is defined: use the variable value as the random seed. + Patch by Victor Stinner. + - gh-110995: test_gdb: Fix detection of gdb built without Python + scripting support. Patch by Victor Stinner. + - gh-110918: Test case matching patterns specified by options + --match, --ignore, --matchfile and --ignorefile are now tested + in the order of specification, and the last match determines + whether the test case be run or ignored. + - gh-110647: Fix test_stress_modifying_handlers() of test_signal. + Patch by Victor Stinner. + - gh-103053: Fix test_tools.test_freeze on FreeBSD: run “make + distclean” instead of “make clean” in the copied source + directory to remove also the “python” program. Patch by Victor + Stinner. + - gh-110167: Fix a deadlock in test_socket when server fails with + a timeout but the client is still running in its thread. Don’t + hold a lock to call cleanup functions in doCleanups(). One of + the cleanup function waits until the client completes, whereas + the client could deadlock if it called addCleanup() in such + situation. Patch by Victor Stinner. + - gh-110388: Add tests for tty. + - gh-81002: Add tests for termios. + - gh-110267: Add tests for pickling and copying PyStructSequence + objects. Patched by Xuehai Pan. + - gh-109974: Fix race conditions in test_threading lock tests. + Wait until a condition is met rather than using time.sleep() + with a hardcoded number of seconds. Patch by Victor Stinner. + - gh-109972: Split test_gdb.py file into a test_gdb package made + of multiple tests, so tests can now be run in parallel. Patch by + Victor Stinner. + - gh-104736: Fix test_gdb on Python built with LLVM clang 16 on + Linux ppc64le (ex: Fedora 38). Search patterns in gdb “bt” + command output to detect when gdb fails to retrieve the + traceback. For example, skip a test if Backtrace stopped: frame + did not save the PC is found. Patch by Victor Stinner. + - gh-108927: Fixed order dependence in running tests in the same + process when a test that has submodules (e.g. test_importlib) + follows a test that imports its submodule (e.g. + test_importlib.util) and precedes a test (e.g. test_unittest or + test_compileall) that uses that submodule. + + - Build + - gh-103053: “make check-clean-src” now also checks if the + “python” program is found in the source directory: fail with an + error if it does exist. Patch by Victor Stinner. + - gh-109191: Fix compile error when building with recent versions + of libedit. + + - IDLE + - bpo-35668: Add docstrings to the IDLE debugger module. Fix two + bugs: initialize Idb.botframe (should be in Bdb); in + Idb.in_rpc_code, check whether prev_frame is None before trying + to use it. Greatly expand test_debugger. + + - C API + - gh-112438: Fix support of format units “es”, “et”, “es#”, and + “et#” in nested tuples in PyArg_ParseTuple()-like functions. + - gh-109521: PyImport_GetImporter() now sets RuntimeError if it + fails to get sys.path_hooks or sys.path_importer_cache or they + are not list and dict correspondingly. Previously it could + return NULL without setting error in obscure cases, crash or + raise SystemError if these attributes have wrong type. + ------------------------------------------------------------------- Wed Nov 15 09:06:16 UTC 2023 - Daniel Garcia diff --git a/python311.spec b/python311.spec index 05e3990..0e3c740 100644 --- a/python311.spec +++ b/python311.spec @@ -94,7 +94,7 @@ %define dynlib() %{sitedir}/lib-dynload/%{1}.cpython-%{abi_tag}-%{archname}-%{_os}%{?_gnu}%{?armsuffix}.so %bcond_without profileopt Name: %{python_pkg_name}%{psuffix} -Version: 3.11.6 +Version: 3.11.7 Release: 0 Summary: Python 3 Interpreter License: Python-2.0 From 8bce36d45906ab023d4cbda5e6745bf2bf431fdc2b5a2ed75b4c9891cefbaa50 Mon Sep 17 00:00:00 2001 From: Daniel Garcia Date: Mon, 18 Dec 2023 07:14:53 +0000 Subject: [PATCH 2/7] Remove leftover tarfiles OBS-URL: https://build.opensuse.org/package/show/devel:languages:python:Factory/python311?expand=0&rev=90 --- Python-3.11.6.tar.xz | 3 --- Python-3.11.6.tar.xz.asc | 16 ---------------- 2 files changed, 19 deletions(-) delete mode 100644 Python-3.11.6.tar.xz delete mode 100644 Python-3.11.6.tar.xz.asc diff --git a/Python-3.11.6.tar.xz b/Python-3.11.6.tar.xz deleted file mode 100644 index 68fb387..0000000 --- a/Python-3.11.6.tar.xz +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0fab78fa7f133f4f38210c6260d90d7c0d5c7198446419ce057ec7ac2e6f5f38 -size 20067204 diff --git a/Python-3.11.6.tar.xz.asc b/Python-3.11.6.tar.xz.asc deleted file mode 100644 index 0979fa7..0000000 --- a/Python-3.11.6.tar.xz.asc +++ /dev/null @@ -1,16 +0,0 @@ ------BEGIN PGP SIGNATURE----- - -iQIzBAABCAAdFiEEz9yiRbEEPPKl+Xhl/+h0BBaL2EcFAmUaybgACgkQ/+h0BBaL -2EdGzg//bg0KTy9DGWD56RUzCKaxdao+FVV2wS8FdghTvGJHM8uDehRESzj6Viag -bks/XuTmJ4xlaW+Ilje4VU5gkdgWt44S2ZGretn+zwrv6B5L697s1j4IOnyEywzm -rQ+a5aMRsW+m8kVoKDLsfbCcrwgxXEFln18y9Qp79QhbUpTVjp5vMwWdYyv2JnYb -G2QraFr0mQEhbVsiYpSVRWiacJi7JBuHTU/hNcN/q1ACf/CX3NSLDfL2bJEWNekg -JW3z0c81vLI9D+NkQ8xWOxgEO6G4Aav4F3t37ujuf2W/CI+NZlEknSt2ITxKeTHw -dwtIMXQc3MyG4ExnAwv1LqtL1H3Dm6SAE7sSyn4uG2uknk4xSuyoMIVMJefXmxbQ -Pd8v6/QnqT3U6MVuVlvCq4AWRotZwRpB7MdnpvA0t1tmANOA8i0MWrhCw0ccYk1a -X/3ewR0+RfQYkDvFfnJpbbsQlK+lhZgt/VmQNOHvWoc/tDnB73b2seOmGkx016TY -hozXE4FXtR+8qk9CfJTd8QRNST8KBUCB9C3eoLciruugmyrUvDLjki/u9GNOIl/b -kVJzcD6eQJC9BthJXuKaKDLKX1a1a9J8GUzVXsW1kuEMQHuv/v7Cf6MzmHWWO3QN -yRgqFXkic/U8RgSHUbtozo16GMlU+RDC6bQgZR7mfE0eFCJnD6U= -=mxrE ------END PGP SIGNATURE----- From cb3301d2cc48ebd8289d1ab4043dd4379ab06e94aa688128481a1e49b50b0c13 Mon Sep 17 00:00:00 2001 From: Matej Cepl Date: Mon, 18 Dec 2023 16:25:35 +0000 Subject: [PATCH 3/7] - Refresh CVE-2023-27043-email-parsing-errors.patch to gh#python/cpython!111116, fixing bsc#1210638 (CVE-2023-27043). - Thus we can remove Revert-gh105127-left-tests.patch, which is now useless. OBS-URL: https://build.opensuse.org/package/show/devel:languages:python:Factory/python311?expand=0&rev=91 --- CVE-2023-27043-email-parsing-errors.patch | 548 +++++++++++++++------- python311.changes | 8 + python311.spec | 4 - 3 files changed, 388 insertions(+), 172 deletions(-) diff --git a/CVE-2023-27043-email-parsing-errors.patch b/CVE-2023-27043-email-parsing-errors.patch index 7aec4a2..6e4cc64 100644 --- a/CVE-2023-27043-email-parsing-errors.patch +++ b/CVE-2023-27043-email-parsing-errors.patch @@ -1,74 +1,185 @@ --- - Doc/library/email.utils.rst | 26 +++ - Lib/email/utils.py | 63 +++++++ - Lib/test/test_email/test_email.py | 81 +++++++++- - Misc/NEWS.d/next/Security/2023-06-13-20-52-24.gh-issue-102988.Kei7Vf.rst | 4 - 4 files changed, 164 insertions(+), 10 deletions(-) + Doc/library/email.utils.rst | 19 - + Lib/email/utils.py | 151 +++++++- + Lib/test/test_email/test_email.py | 187 +++++++++- + Misc/NEWS.d/next/Library/2023-10-20-15-28-08.gh-issue-102988.dStNO7.rst | 8 + 4 files changed, 344 insertions(+), 21 deletions(-) -Index: Python-3.11.4/Doc/library/email.utils.rst -=================================================================== ---- Python-3.11.4.orig/Doc/library/email.utils.rst -+++ Python-3.11.4/Doc/library/email.utils.rst -@@ -67,6 +67,11 @@ of the new API. +--- a/Doc/library/email.utils.rst ++++ b/Doc/library/email.utils.rst +@@ -60,13 +60,18 @@ of the new API. + begins with angle brackets, they are stripped off. + + +-.. function:: parseaddr(address) ++.. function:: parseaddr(address, *, strict=True) + + Parse address -- which should be the value of some address-containing field such + as :mailheader:`To` or :mailheader:`Cc` -- into its constituent *realname* and *email address* parts. Returns a tuple of that information, unless the parse fails, in which case a 2-tuple of ``('', '')`` is returned. -+ .. versionchanged:: 3.12 -+ For security reasons, addresses that were ambiguous and could parse into -+ multiple different addresses now cause ``('', '')`` to be returned -+ instead of only one of the *potential* addresses. ++ If *strict* is true, use a strict parser which rejects malformed inputs. ++ ++ .. versionchanged:: 3.13 ++ Add *strict* optional parameter and reject malformed inputs by default. + .. function:: formataddr(pair, charset='utf-8') -@@ -89,7 +94,7 @@ of the new API. +@@ -84,12 +89,15 @@ of the new API. + Added the *charset* option. + + +-.. function:: getaddresses(fieldvalues) ++.. function:: getaddresses(fieldvalues, *, strict=True) + This method returns a list of 2-tuples of the form returned by ``parseaddr()``. *fieldvalues* is a sequence of header field values as might be returned by - :meth:`Message.get_all `. Here's a simple +- :meth:`Message.get_all `. Here's a simple - example that gets all the recipients of a message:: -+ example that gets all the recipients of a message: ++ :meth:`Message.get_all `. ++ ++ If *strict* is true, use a strict parser which rejects malformed inputs. ++ ++ Here's a simple example that gets all the recipients of a message:: from email.utils import getaddresses -@@ -99,6 +104,25 @@ of the new API. +@@ -99,6 +107,9 @@ of the new API. resent_ccs = msg.get_all('resent-cc', []) all_recipients = getaddresses(tos + ccs + resent_tos + resent_ccs) -+ When parsing fails for a single fieldvalue, a 2-tuple of ``('', '')`` -+ is returned in its place. Other errors in parsing the list of -+ addresses such as a fieldvalue seemingly parsing into multiple -+ addresses may result in a list containing a single empty 2-tuple -+ ``[('', '')]`` being returned rather than returning potentially -+ invalid output. -+ -+ Example malformed input parsing: -+ -+ .. doctest:: -+ -+ >>> from email.utils import getaddresses -+ >>> getaddresses(['alice@example.com ', 'me@example.com']) -+ [('', '')] -+ -+ .. versionchanged:: 3.12 -+ The 2-tuple of ``('', '')`` in the returned values when parsing -+ fails were added as to address a security issue. ++ .. versionchanged:: 3.13 ++ Add *strict* optional parameter and reject malformed inputs by default. + .. function:: parsedate(date) -Index: Python-3.11.4/Lib/email/utils.py -=================================================================== ---- Python-3.11.4.orig/Lib/email/utils.py -+++ Python-3.11.4/Lib/email/utils.py -@@ -106,12 +106,54 @@ def formataddr(pair, charset='utf-8'): +--- a/Lib/email/utils.py ++++ b/Lib/email/utils.py +@@ -48,6 +48,7 @@ TICK = "'" + specialsre = re.compile(r'[][\\()<>@,:;".]') + escapesre = re.compile(r'[\\"]') + ++ + def _has_surrogates(s): + """Return True if s contains surrogate-escaped binary data.""" + # This check is based on the fact that unless there are surrogates, utf8 +@@ -106,12 +107,127 @@ def formataddr(pair, charset='utf-8'): return address ++def _iter_escaped_chars(addr): ++ pos = 0 ++ escape = False ++ for pos, ch in enumerate(addr): ++ if escape: ++ yield (pos, '\\' + ch) ++ escape = False ++ elif ch == '\\': ++ escape = True ++ else: ++ yield (pos, ch) ++ if escape: ++ yield (pos, '\\') ++ ++ ++def _strip_quoted_realnames(addr): ++ """Strip real names between quotes.""" ++ if '"' not in addr: ++ # Fast path ++ return addr ++ ++ start = 0 ++ open_pos = None ++ result = [] ++ for pos, ch in _iter_escaped_chars(addr): ++ if ch == '"': ++ if open_pos is None: ++ open_pos = pos ++ else: ++ if start != open_pos: ++ result.append(addr[start:open_pos]) ++ start = pos + 1 ++ open_pos = None + +-def getaddresses(fieldvalues): +- """Return a list of (REALNAME, EMAIL) for each fieldvalue.""" +- all = COMMASPACE.join(str(v) for v in fieldvalues) +- a = _AddressList(all) +- return a.addresslist ++ if start < len(addr): ++ result.append(addr[start:]) ++ ++ return ''.join(result) ++ ++ ++supports_strict_parsing = True ++ ++def getaddresses(fieldvalues, *, strict=True): ++ """Return a list of (REALNAME, EMAIL) or ('','') for each fieldvalue. ++ ++ When parsing fails for a fieldvalue, a 2-tuple of ('', '') is returned in ++ its place. ++ ++ If strict is true, use a strict parser which rejects malformed inputs. ++ """ ++ ++ # If strict is true, if the resulting list of parsed addresses is greater ++ # than the number of fieldvalues in the input list, a parsing error has ++ # occurred and consequently a list containing a single empty 2-tuple [('', ++ # '')] is returned in its place. This is done to avoid invalid output. ++ # ++ # Malformed input: getaddresses(['alice@example.com ']) ++ # Invalid output: [('', 'alice@example.com'), ('', 'bob@example.com')] ++ # Safe output: [('', '')] ++ ++ if not strict: ++ all = COMMASPACE.join(str(v) for v in fieldvalues) ++ a = _AddressList(all) ++ return a.addresslist ++ ++ fieldvalues = [str(v) for v in fieldvalues] ++ fieldvalues = _pre_parse_validation(fieldvalues) ++ addr = COMMASPACE.join(fieldvalues) ++ a = _AddressList(addr) ++ result = _post_parse_validation(a.addresslist) ++ ++ # Treat output as invalid if the number of addresses is not equal to the ++ # expected number of addresses. ++ n = 0 ++ for v in fieldvalues: ++ # When a comma is used in the Real Name part it is not a deliminator. ++ # So strip those out before counting the commas. ++ v = _strip_quoted_realnames(v) ++ # Expected number of addresses: 1 + number of commas ++ n += 1 + v.count(',') ++ if len(result) != n: ++ return [('', '')] ++ ++ return result ++ ++ ++def _check_parenthesis(addr): ++ # Ignore parenthesis in quoted real names. ++ addr = _strip_quoted_realnames(addr) ++ ++ opens = 0 ++ for pos, ch in _iter_escaped_chars(addr): ++ if ch == '(': ++ opens += 1 ++ elif ch == ')': ++ opens -= 1 ++ if opens < 0: ++ return False ++ return (opens == 0) ++ ++ +def _pre_parse_validation(email_header_fields): + accepted_values = [] + for v in email_header_fields: -+ s = v.replace('\\(', '').replace('\\)', '') -+ if s.count('(') != s.count(')'): ++ if not _check_parenthesis(v): + v = "('', '')" + accepted_values.append(v) + @@ -85,46 +196,32 @@ Index: Python-3.11.4/Lib/email/utils.py + accepted_values.append(v) + + return accepted_values -+ - - def getaddresses(fieldvalues): -- """Return a list of (REALNAME, EMAIL) for each fieldvalue.""" -- all = COMMASPACE.join(str(v) for v in fieldvalues) -+ """Return a list of (REALNAME, EMAIL) or ('','') for each fieldvalue. -+ -+ When parsing fails for a fieldvalue, a 2-tuple of ('', '') is returned in -+ its place. -+ -+ If the resulting list of parsed address is not the same as the number of -+ fieldvalues in the input list a parsing error has occurred. A list -+ containing a single empty 2-tuple [('', '')] is returned in its place. -+ This is done to avoid invalid output. -+ """ -+ fieldvalues = [str(v) for v in fieldvalues] -+ fieldvalues = _pre_parse_validation(fieldvalues) -+ all = COMMASPACE.join(v for v in fieldvalues) - a = _AddressList(all) -- return a.addresslist -+ result = _post_parse_validation(a.addresslist) -+ -+ n = 0 -+ for v in fieldvalues: -+ n += v.count(',') + 1 -+ -+ if len(result) != n: -+ return [('', '')] -+ -+ return result def _format_timetuple_and_zone(timetuple, zone): -@@ -212,9 +254,18 @@ def parseaddr(addr): +@@ -205,16 +321,33 @@ def parsedate_to_datetime(data): + tzinfo=datetime.timezone(datetime.timedelta(seconds=tz))) + + +-def parseaddr(addr): ++def parseaddr(addr, *, strict=True): + """ + Parse addr into its constituent realname and email address parts. + Return a tuple of realname and email address, unless the parse fails, in which case return a 2-tuple of ('', ''). ++ ++ If strict is True, use a strict parser which rejects malformed inputs. """ - addrs = _AddressList(addr).addresslist - if not addrs: - return '', '' ++ if not strict: ++ addrs = _AddressList(addr).addresslist ++ if not addrs: ++ return ('', '') ++ return addrs[0] ++ + if isinstance(addr, list): + addr = addr[0] + @@ -140,110 +237,225 @@ Index: Python-3.11.4/Lib/email/utils.py return addrs[0] -Index: Python-3.11.4/Lib/test/test_email/test_email.py -=================================================================== ---- Python-3.11.4.orig/Lib/test/test_email/test_email.py -+++ Python-3.11.4/Lib/test/test_email/test_email.py -@@ -3320,15 +3320,90 @@ Foo +--- a/Lib/test/test_email/test_email.py ++++ b/Lib/test/test_email/test_email.py +@@ -17,6 +17,7 @@ from unittest.mock import patch + + import email + import email.policy ++import email.utils + + from email.charset import Charset + from email.generator import Generator, DecodedGenerator, BytesGenerator +@@ -3321,15 +3322,137 @@ Foo [('Al Person', 'aperson@dom.ain'), ('Bud Person', 'bperson@dom.ain')]) -+ def test_getaddresses_parsing_errors(self): -+ """Test for parsing errors from CVE-2023-27043""" -+ eq = self.assertEqual -+ eq(utils.getaddresses(['alice@example.org(']), -+ [('', '')]) -+ eq(utils.getaddresses(['alice@example.org)']), -+ [('', '')]) -+ eq(utils.getaddresses(['alice@example.org<']), -+ [('', '')]) -+ eq(utils.getaddresses(['alice@example.org>']), -+ [('', '')]) -+ eq(utils.getaddresses(['alice@example.org@']), -+ [('', '')]) -+ eq(utils.getaddresses(['alice@example.org,']), -+ [('', 'alice@example.org'), ('', 'bob@example.com')]) -+ eq(utils.getaddresses(['alice@example.org;']), -+ [('', '')]) -+ eq(utils.getaddresses(['alice@example.org:']), -+ [('', '')]) -+ eq(utils.getaddresses(['alice@example.org.']), -+ [('', '')]) -+ eq(utils.getaddresses(['alice@example.org"']), -+ [('', '')]) -+ eq(utils.getaddresses(['alice@example.org[']), -+ [('', '')]) -+ eq(utils.getaddresses(['alice@example.org]']), -+ [('', '')]) ++ def test_parsing_errors(self): ++ """Test for parsing errors from CVE-2023-27043 and CVE-2019-16056""" ++ alice = 'alice@example.org' ++ bob = 'bob@example.com' ++ empty = ('', '') + -+ def test_parseaddr_parsing_errors(self): -+ """Test for parsing errors from CVE-2023-27043""" -+ eq = self.assertEqual -+ eq(utils.parseaddr(['alice@example.org(']), -+ ('', '')) -+ eq(utils.parseaddr(['alice@example.org)']), -+ ('', '')) -+ eq(utils.parseaddr(['alice@example.org<']), -+ ('', '')) -+ eq(utils.parseaddr(['alice@example.org>']), -+ ('', '')) -+ eq(utils.parseaddr(['alice@example.org@']), -+ ('', '')) -+ eq(utils.parseaddr(['alice@example.org,']), -+ ('', '')) -+ eq(utils.parseaddr(['alice@example.org;']), -+ ('', '')) -+ eq(utils.parseaddr(['alice@example.org:']), -+ ('', '')) -+ eq(utils.parseaddr(['alice@example.org.']), -+ ('', '')) -+ eq(utils.parseaddr(['alice@example.org"']), -+ ('', '')) -+ eq(utils.parseaddr(['alice@example.org[']), -+ ('', '')) -+ eq(utils.parseaddr(['alice@example.org]']), -+ ('', '')) ++ # Test utils.getaddresses() and utils.parseaddr() on malformed email ++ # addresses: default behavior (strict=True) rejects malformed address, ++ # and strict=False which tolerates malformed address. ++ for invalid_separator, expected_non_strict in ( ++ ('(', [(f'<{bob}>', alice)]), ++ (')', [('', alice), empty, ('', bob)]), ++ ('<', [('', alice), empty, ('', bob), empty]), ++ ('>', [('', alice), empty, ('', bob)]), ++ ('[', [('', f'{alice}[<{bob}>]')]), ++ (']', [('', alice), empty, ('', bob)]), ++ ('@', [empty, empty, ('', bob)]), ++ (';', [('', alice), empty, ('', bob)]), ++ (':', [('', alice), ('', bob)]), ++ ('.', [('', alice + '.'), ('', bob)]), ++ ('"', [('', alice), ('', f'<{bob}>')]), ++ ): ++ address = f'{alice}{invalid_separator}<{bob}>' ++ with self.subTest(address=address): ++ self.assertEqual(utils.getaddresses([address]), ++ [empty]) ++ self.assertEqual(utils.getaddresses([address], strict=False), ++ expected_non_strict) ++ ++ self.assertEqual(utils.parseaddr([address]), ++ empty) ++ self.assertEqual(utils.parseaddr([address], strict=False), ++ ('', address)) ++ ++ # Comma (',') is treated differently depending on strict parameter. ++ # Comma without quotes. ++ address = f'{alice},<{bob}>' ++ self.assertEqual(utils.getaddresses([address]), ++ [('', alice), ('', bob)]) ++ self.assertEqual(utils.getaddresses([address], strict=False), ++ [('', alice), ('', bob)]) ++ self.assertEqual(utils.parseaddr([address]), ++ empty) ++ self.assertEqual(utils.parseaddr([address], strict=False), ++ ('', address)) ++ ++ # Real name between quotes containing comma. ++ address = '"Alice, alice@example.org" ' ++ expected_strict = ('Alice, alice@example.org', 'bob@example.com') ++ self.assertEqual(utils.getaddresses([address]), [expected_strict]) ++ self.assertEqual(utils.getaddresses([address], strict=False), [expected_strict]) ++ self.assertEqual(utils.parseaddr([address]), expected_strict) ++ self.assertEqual(utils.parseaddr([address], strict=False), ++ ('', address)) ++ ++ # Valid parenthesis in comments. ++ address = 'alice@example.org (Alice)' ++ expected_strict = ('Alice', 'alice@example.org') ++ self.assertEqual(utils.getaddresses([address]), [expected_strict]) ++ self.assertEqual(utils.getaddresses([address], strict=False), [expected_strict]) ++ self.assertEqual(utils.parseaddr([address]), expected_strict) ++ self.assertEqual(utils.parseaddr([address], strict=False), ++ ('', address)) ++ ++ # Invalid parenthesis in comments. ++ address = 'alice@example.org )Alice(' ++ self.assertEqual(utils.getaddresses([address]), [empty]) ++ self.assertEqual(utils.getaddresses([address], strict=False), ++ [('', 'alice@example.org'), ('', ''), ('', 'Alice')]) ++ self.assertEqual(utils.parseaddr([address]), empty) ++ self.assertEqual(utils.parseaddr([address], strict=False), ++ ('', address)) ++ ++ # Two addresses with quotes separated by comma. ++ address = '"Jane Doe" , "John Doe" ' ++ self.assertEqual(utils.getaddresses([address]), ++ [('Jane Doe', 'jane@example.net'), ++ ('John Doe', 'john@example.net')]) ++ self.assertEqual(utils.getaddresses([address], strict=False), ++ [('Jane Doe', 'jane@example.net'), ++ ('John Doe', 'john@example.net')]) ++ self.assertEqual(utils.parseaddr([address]), empty) ++ self.assertEqual(utils.parseaddr([address], strict=False), ++ ('', address)) ++ ++ # Test email.utils.supports_strict_parsing attribute ++ self.assertEqual(email.utils.supports_strict_parsing, True) + def test_getaddresses_nasty(self): - eq = self.assertEqual - eq(utils.getaddresses(['foo: ;']), [('', '')]) +- eq = self.assertEqual +- eq(utils.getaddresses(['foo: ;']), [('', '')]) - eq(utils.getaddresses( - ['[]*-- =~$']), - [('', ''), ('', ''), ('', '*--')]) -+ eq(utils.getaddresses(['[]*-- =~$']), [('', '')]) - eq(utils.getaddresses( - ['foo: ;', '"Jason R. Mastaler" ']), - [('', ''), ('Jason R. Mastaler', 'jason@dom.ain')]) -+ eq(utils.getaddresses( -+ [r'Pete(A nice \) chap) ']), -+ [('Pete (A nice ) chap his account his host)', 'pete@silly.test')]) -+ eq(utils.getaddresses( -+ ['(Empty list)(start)Undisclosed recipients :(nobody(I know))']), -+ [('', '')]) -+ eq(utils.getaddresses( -+ ['Mary <@machine.tld:mary@example.net>, , jdoe@test . example']), -+ [('Mary', 'mary@example.net'), ('', ''), ('', 'jdoe@test.example')]) -+ eq(utils.getaddresses( -+ ['John Doe ']), -+ [('John Doe (comment)', 'jdoe@machine.example')]) -+ eq(utils.getaddresses( -+ ['"Mary Smith: Personal Account" ']), -+ [('Mary Smith: Personal Account', 'smith@home.example')]) -+ eq(utils.getaddresses( -+ ['Undisclosed recipients:;']), -+ [('', '')]) -+ eq(utils.getaddresses( -+ [r', "Giant; \"Big\" Box" ']), -+ [('', 'boss@nil.test'), ('Giant; "Big" Box', 'bob@example.net')]) +- eq(utils.getaddresses( +- ['foo: ;', '"Jason R. Mastaler" ']), +- [('', ''), ('Jason R. Mastaler', 'jason@dom.ain')]) ++ for addresses, expected in ( ++ (['"Sürname, Firstname" '], ++ [('Sürname, Firstname', 'to@example.com')]), ++ ++ (['foo: ;'], ++ [('', '')]), ++ ++ (['foo: ;', '"Jason R. Mastaler" '], ++ [('', ''), ('Jason R. Mastaler', 'jason@dom.ain')]), ++ ++ ([r'Pete(A nice \) chap) '], ++ [('Pete (A nice ) chap his account his host)', 'pete@silly.test')]), ++ ++ (['(Empty list)(start)Undisclosed recipients :(nobody(I know))'], ++ [('', '')]), ++ ++ (['Mary <@machine.tld:mary@example.net>, , jdoe@test . example'], ++ [('Mary', 'mary@example.net'), ('', ''), ('', 'jdoe@test.example')]), ++ ++ (['John Doe '], ++ [('John Doe (comment)', 'jdoe@machine.example')]), ++ ++ (['"Mary Smith: Personal Account" '], ++ [('Mary Smith: Personal Account', 'smith@home.example')]), ++ ++ (['Undisclosed recipients:;'], ++ [('', '')]), ++ ++ ([r', "Giant; \"Big\" Box" '], ++ [('', 'boss@nil.test'), ('Giant; "Big" Box', 'bob@example.net')]), ++ ): ++ with self.subTest(addresses=addresses): ++ self.assertEqual(utils.getaddresses(addresses), ++ expected) ++ self.assertEqual(utils.getaddresses(addresses, strict=False), ++ expected) ++ ++ addresses = ['[]*-- =~$'] ++ self.assertEqual(utils.getaddresses(addresses), ++ [('', '')]) ++ self.assertEqual(utils.getaddresses(addresses, strict=False), ++ [('', ''), ('', ''), ('', '*--')]) def test_getaddresses_embedded_comment(self): """Test proper handling of a nested comment""" -Index: Python-3.11.4/Misc/NEWS.d/next/Security/2023-06-13-20-52-24.gh-issue-102988.Kei7Vf.rst -=================================================================== +@@ -3520,6 +3643,54 @@ multipart/report + m = cls(*constructor, policy=email.policy.default) + self.assertIs(m.policy, email.policy.default) + ++ def test_iter_escaped_chars(self): ++ self.assertEqual(list(utils._iter_escaped_chars(r'a\\b\"c\\"d')), ++ [(0, 'a'), ++ (2, '\\\\'), ++ (3, 'b'), ++ (5, '\\"'), ++ (6, 'c'), ++ (8, '\\\\'), ++ (9, '"'), ++ (10, 'd')]) ++ self.assertEqual(list(utils._iter_escaped_chars('a\\')), ++ [(0, 'a'), (1, '\\')]) ++ ++ def test_strip_quoted_realnames(self): ++ def check(addr, expected): ++ self.assertEqual(utils._strip_quoted_realnames(addr), expected) ++ ++ check('"Jane Doe" , "John Doe" ', ++ ' , ') ++ check(r'"Jane \"Doe\"." ', ++ ' ') ++ ++ # special cases ++ check(r'before"name"after', 'beforeafter') ++ check(r'before"name"', 'before') ++ check(r'b"name"', 'b') # single char ++ check(r'"name"after', 'after') ++ check(r'"name"a', 'a') # single char ++ check(r'"name"', '') ++ ++ # no change ++ for addr in ( ++ 'Jane Doe , John Doe ', ++ 'lone " quote', ++ ): ++ self.assertEqual(utils._strip_quoted_realnames(addr), addr) ++ ++ ++ def test_check_parenthesis(self): ++ addr = 'alice@example.net' ++ self.assertTrue(utils._check_parenthesis(f'{addr} (Alice)')) ++ self.assertFalse(utils._check_parenthesis(f'{addr} )Alice(')) ++ self.assertFalse(utils._check_parenthesis(f'{addr} (Alice))')) ++ self.assertFalse(utils._check_parenthesis(f'{addr} ((Alice)')) ++ ++ # Ignore real name between quotes ++ self.assertTrue(utils._check_parenthesis(f'")Alice((" {addr}')) ++ + + # Test the iterator/generators + class TestIterators(TestEmailBase): --- /dev/null -+++ Python-3.11.4/Misc/NEWS.d/next/Security/2023-06-13-20-52-24.gh-issue-102988.Kei7Vf.rst -@@ -0,0 +1,4 @@ -+CVE-2023-27043: Prevent :func:`email.utils.parseaddr` -+and :func:`email.utils.getaddresses` from returning the realname portion of an -+invalid RFC2822 email header in the email address portion of the 2-tuple -+returned after being parsed by :class:`email._parseaddr.AddressList`. ++++ b/Misc/NEWS.d/next/Library/2023-10-20-15-28-08.gh-issue-102988.dStNO7.rst +@@ -0,0 +1,8 @@ ++:func:`email.utils.getaddresses` and :func:`email.utils.parseaddr` now ++return ``('', '')`` 2-tuples in more situations where invalid email ++addresses are encountered instead of potentially inaccurate values. Add ++optional *strict* parameter to these two functions: use ``strict=False`` to ++get the old behavior, accept malformed inputs. ++``getattr(email.utils, 'supports_strict_parsing', False)`` can be use to check ++if the *strict* paramater is available. Patch by Thomas Dwyer and Victor ++Stinner to improve the CVE-2023-27043 fix. diff --git a/python311.changes b/python311.changes index af0f2b6..324f3d9 100644 --- a/python311.changes +++ b/python311.changes @@ -1,3 +1,11 @@ +------------------------------------------------------------------- +Mon Dec 18 16:20:58 UTC 2023 - Matej Cepl + +- Refresh CVE-2023-27043-email-parsing-errors.patch to + gh#python/cpython!111116, fixing bsc#1210638 (CVE-2023-27043). +- Thus we can remove Revert-gh105127-left-tests.patch, which is + now useless. + ------------------------------------------------------------------- Fri Dec 15 10:04:33 UTC 2023 - Daniel Garcia diff --git a/python311.spec b/python311.spec index 0e3c740..16f2357 100644 --- a/python311.spec +++ b/python311.spec @@ -165,9 +165,6 @@ Patch39: skip_if_buildbot-extend.patch # Detect email address parsing errors and return empty tuple to # indicate the parsing error (old API) Patch40: CVE-2023-27043-email-parsing-errors.patch -# PATCH-FIX-UPSTREAM Revert-gh105127-left-tests.patch bsc#1210638 mcepl@suse.com -# Partially revert previous patch -Patch41: Revert-gh105127-left-tests.patch BuildRequires: autoconf-archive BuildRequires: automake BuildRequires: fdupes @@ -428,7 +425,6 @@ other applications. %patch36 -p1 %patch39 -p1 %patch40 -p1 -%patch41 -p1 # drop Autoconf version requirement sed -i 's/^AC_PREREQ/dnl AC_PREREQ/' configure.ac From 09c88531399620c4a7e8308bbe5d983dd0b121e761ad8d1cb816df2e1f206985 Mon Sep 17 00:00:00 2001 From: Matej Cepl Date: Mon, 18 Dec 2023 16:25:59 +0000 Subject: [PATCH 4/7] Remove reverting patch OBS-URL: https://build.opensuse.org/package/show/devel:languages:python:Factory/python311?expand=0&rev=92 --- Revert-gh105127-left-tests.patch | 283 ------------------------------- 1 file changed, 283 deletions(-) delete mode 100644 Revert-gh105127-left-tests.patch diff --git a/Revert-gh105127-left-tests.patch b/Revert-gh105127-left-tests.patch deleted file mode 100644 index 87ce40a..0000000 --- a/Revert-gh105127-left-tests.patch +++ /dev/null @@ -1,283 +0,0 @@ -From 4288c623d62cf90d8e4444facb3379fb06d01140 Mon Sep 17 00:00:00 2001 -From: "Gregory P. Smith" -Date: Thu, 20 Jul 2023 20:30:52 -0700 -Subject: [PATCH] [3.12] gh-106669: Revert "gh-102988: Detect email address - parsing errors ... (GH-105127)" (GH-106733) - -This reverts commit 18dfbd035775c15533d13a98e56b1d2bf5c65f00. -Adds a regression test from the issue. - -See https://github.com/python/cpython/issues/106669.. -(cherry picked from commit a31dea1feb61793e48fa9aa5014f358352205c1d) - -Co-authored-by: Gregory P. Smith ---- - Doc/library/email.utils.rst | 26 -- - Lib/email/utils.py | 63 ------ - Lib/test/test_email/test_email.py | 96 +--------- - Misc/NEWS.d/next/Security/2023-06-13-20-52-24.gh-issue-102988.Kei7Vf.rst | 5 - 4 files changed, 31 insertions(+), 159 deletions(-) - create mode 100644 Misc/NEWS.d/next/Security/2023-06-13-20-52-24.gh-issue-102988.Kei7Vf.rst - -Index: Python-3.11.4/Doc/library/email.utils.rst -=================================================================== ---- Python-3.11.4.orig/Doc/library/email.utils.rst -+++ Python-3.11.4/Doc/library/email.utils.rst -@@ -67,11 +67,6 @@ of the new API. - *email address* parts. Returns a tuple of that information, unless the parse - fails, in which case a 2-tuple of ``('', '')`` is returned. - -- .. versionchanged:: 3.12 -- For security reasons, addresses that were ambiguous and could parse into -- multiple different addresses now cause ``('', '')`` to be returned -- instead of only one of the *potential* addresses. -- - - .. function:: formataddr(pair, charset='utf-8') - -@@ -94,7 +89,7 @@ of the new API. - This method returns a list of 2-tuples of the form returned by ``parseaddr()``. - *fieldvalues* is a sequence of header field values as might be returned by - :meth:`Message.get_all `. Here's a simple -- example that gets all the recipients of a message: -+ example that gets all the recipients of a message:: - - from email.utils import getaddresses - -@@ -104,25 +99,6 @@ of the new API. - resent_ccs = msg.get_all('resent-cc', []) - all_recipients = getaddresses(tos + ccs + resent_tos + resent_ccs) - -- When parsing fails for a single fieldvalue, a 2-tuple of ``('', '')`` -- is returned in its place. Other errors in parsing the list of -- addresses such as a fieldvalue seemingly parsing into multiple -- addresses may result in a list containing a single empty 2-tuple -- ``[('', '')]`` being returned rather than returning potentially -- invalid output. -- -- Example malformed input parsing: -- -- .. doctest:: -- -- >>> from email.utils import getaddresses -- >>> getaddresses(['alice@example.com ', 'me@example.com']) -- [('', '')] -- -- .. versionchanged:: 3.12 -- The 2-tuple of ``('', '')`` in the returned values when parsing -- fails were added as to address a security issue. -- - - .. function:: parsedate(date) - -Index: Python-3.11.4/Lib/email/utils.py -=================================================================== ---- Python-3.11.4.orig/Lib/email/utils.py -+++ Python-3.11.4/Lib/email/utils.py -@@ -106,54 +106,12 @@ def formataddr(pair, charset='utf-8'): - return address - - --def _pre_parse_validation(email_header_fields): -- accepted_values = [] -- for v in email_header_fields: -- s = v.replace('\\(', '').replace('\\)', '') -- if s.count('(') != s.count(')'): -- v = "('', '')" -- accepted_values.append(v) -- -- return accepted_values -- -- --def _post_parse_validation(parsed_email_header_tuples): -- accepted_values = [] -- # The parser would have parsed a correctly formatted domain-literal -- # The existence of an [ after parsing indicates a parsing failure -- for v in parsed_email_header_tuples: -- if '[' in v[1]: -- v = ('', '') -- accepted_values.append(v) -- -- return accepted_values -- - - def getaddresses(fieldvalues): -- """Return a list of (REALNAME, EMAIL) or ('','') for each fieldvalue. -- -- When parsing fails for a fieldvalue, a 2-tuple of ('', '') is returned in -- its place. -- -- If the resulting list of parsed address is not the same as the number of -- fieldvalues in the input list a parsing error has occurred. A list -- containing a single empty 2-tuple [('', '')] is returned in its place. -- This is done to avoid invalid output. -- """ -- fieldvalues = [str(v) for v in fieldvalues] -- fieldvalues = _pre_parse_validation(fieldvalues) -- all = COMMASPACE.join(v for v in fieldvalues) -+ """Return a list of (REALNAME, EMAIL) for each fieldvalue.""" -+ all = COMMASPACE.join(str(v) for v in fieldvalues) - a = _AddressList(all) -- result = _post_parse_validation(a.addresslist) -- -- n = 0 -- for v in fieldvalues: -- n += v.count(',') + 1 -- -- if len(result) != n: -- return [('', '')] -- -- return result -+ return a.addresslist - - - def _format_timetuple_and_zone(timetuple, zone): -@@ -254,18 +212,9 @@ def parseaddr(addr): - Return a tuple of realname and email address, unless the parse fails, in - which case return a 2-tuple of ('', ''). - """ -- if isinstance(addr, list): -- addr = addr[0] -- -- if not isinstance(addr, str): -- return ('', '') -- -- addr = _pre_parse_validation([addr])[0] -- addrs = _post_parse_validation(_AddressList(addr).addresslist) -- -- if not addrs or len(addrs) > 1: -- return ('', '') -- -+ addrs = _AddressList(addr).addresslist -+ if not addrs: -+ return '', '' - return addrs[0] - - -Index: Python-3.11.4/Lib/test/test_email/test_email.py -=================================================================== ---- Python-3.11.4.orig/Lib/test/test_email/test_email.py -+++ Python-3.11.4/Lib/test/test_email/test_email.py -@@ -3320,90 +3320,32 @@ Foo - [('Al Person', 'aperson@dom.ain'), - ('Bud Person', 'bperson@dom.ain')]) - -- def test_getaddresses_parsing_errors(self): -- """Test for parsing errors from CVE-2023-27043""" -- eq = self.assertEqual -- eq(utils.getaddresses(['alice@example.org(']), -- [('', '')]) -- eq(utils.getaddresses(['alice@example.org)']), -- [('', '')]) -- eq(utils.getaddresses(['alice@example.org<']), -- [('', '')]) -- eq(utils.getaddresses(['alice@example.org>']), -- [('', '')]) -- eq(utils.getaddresses(['alice@example.org@']), -- [('', '')]) -- eq(utils.getaddresses(['alice@example.org,']), -- [('', 'alice@example.org'), ('', 'bob@example.com')]) -- eq(utils.getaddresses(['alice@example.org;']), -- [('', '')]) -- eq(utils.getaddresses(['alice@example.org:']), -- [('', '')]) -- eq(utils.getaddresses(['alice@example.org.']), -- [('', '')]) -- eq(utils.getaddresses(['alice@example.org"']), -- [('', '')]) -- eq(utils.getaddresses(['alice@example.org[']), -- [('', '')]) -- eq(utils.getaddresses(['alice@example.org]']), -- [('', '')]) -- -- def test_parseaddr_parsing_errors(self): -- """Test for parsing errors from CVE-2023-27043""" -- eq = self.assertEqual -- eq(utils.parseaddr(['alice@example.org(']), -- ('', '')) -- eq(utils.parseaddr(['alice@example.org)']), -- ('', '')) -- eq(utils.parseaddr(['alice@example.org<']), -- ('', '')) -- eq(utils.parseaddr(['alice@example.org>']), -- ('', '')) -- eq(utils.parseaddr(['alice@example.org@']), -- ('', '')) -- eq(utils.parseaddr(['alice@example.org,']), -- ('', '')) -- eq(utils.parseaddr(['alice@example.org;']), -- ('', '')) -- eq(utils.parseaddr(['alice@example.org:']), -- ('', '')) -- eq(utils.parseaddr(['alice@example.org.']), -- ('', '')) -- eq(utils.parseaddr(['alice@example.org"']), -- ('', '')) -- eq(utils.parseaddr(['alice@example.org[']), -- ('', '')) -- eq(utils.parseaddr(['alice@example.org]']), -- ('', '')) -+ def test_getaddresses_comma_in_name(self): -+ """GH-106669 regression test.""" -+ self.assertEqual( -+ utils.getaddresses( -+ [ -+ '"Bud, Person" ', -+ 'aperson@dom.ain (Al Person)', -+ '"Mariusz Felisiak" ', -+ ] -+ ), -+ [ -+ ('Bud, Person', 'bperson@dom.ain'), -+ ('Al Person', 'aperson@dom.ain'), -+ ('Mariusz Felisiak', 'to@example.com'), -+ ], -+ ) - - def test_getaddresses_nasty(self): - eq = self.assertEqual - eq(utils.getaddresses(['foo: ;']), [('', '')]) -- eq(utils.getaddresses(['[]*-- =~$']), [('', '')]) -+ eq(utils.getaddresses( -+ ['[]*-- =~$']), -+ [('', ''), ('', ''), ('', '*--')]) - eq(utils.getaddresses( - ['foo: ;', '"Jason R. Mastaler" ']), - [('', ''), ('Jason R. Mastaler', 'jason@dom.ain')]) -- eq(utils.getaddresses( -- [r'Pete(A nice \) chap) ']), -- [('Pete (A nice ) chap his account his host)', 'pete@silly.test')]) -- eq(utils.getaddresses( -- ['(Empty list)(start)Undisclosed recipients :(nobody(I know))']), -- [('', '')]) -- eq(utils.getaddresses( -- ['Mary <@machine.tld:mary@example.net>, , jdoe@test . example']), -- [('Mary', 'mary@example.net'), ('', ''), ('', 'jdoe@test.example')]) -- eq(utils.getaddresses( -- ['John Doe ']), -- [('John Doe (comment)', 'jdoe@machine.example')]) -- eq(utils.getaddresses( -- ['"Mary Smith: Personal Account" ']), -- [('Mary Smith: Personal Account', 'smith@home.example')]) -- eq(utils.getaddresses( -- ['Undisclosed recipients:;']), -- [('', '')]) -- eq(utils.getaddresses( -- [r', "Giant; \"Big\" Box" ']), -- [('', 'boss@nil.test'), ('Giant; "Big" Box', 'bob@example.net')]) - - def test_getaddresses_embedded_comment(self): - """Test proper handling of a nested comment""" -Index: Python-3.11.4/Misc/NEWS.d/next/Security/2023-06-13-20-52-24.gh-issue-102988.Kei7Vf.rst -=================================================================== ---- Python-3.11.4.orig/Misc/NEWS.d/next/Security/2023-06-13-20-52-24.gh-issue-102988.Kei7Vf.rst -+++ Python-3.11.4/Misc/NEWS.d/next/Security/2023-06-13-20-52-24.gh-issue-102988.Kei7Vf.rst -@@ -1,3 +1,8 @@ -+Reverted the :mod:`email.utils` security improvement change released in -+3.12beta4 that unintentionally caused :mod:`email.utils.getaddresses` to fail -+to parse email addresses with a comma in the quoted name field. -+See :gh:`106669`. -+ - CVE-2023-27043: Prevent :func:`email.utils.parseaddr` - and :func:`email.utils.getaddresses` from returning the realname portion of an - invalid RFC2822 email header in the email address portion of the 2-tuple From 727f4c9b01ba0826e283dada3ef85f7aa32680ed33c6a52cc7e1b9cbd230db38 Mon Sep 17 00:00:00 2001 From: Matej Cepl Date: Tue, 19 Dec 2023 15:22:13 +0000 Subject: [PATCH 5/7] Accepting request 1134053 from devel:languages:python:Factory revert OBS-URL: https://build.opensuse.org/request/show/1134053 OBS-URL: https://build.opensuse.org/package/show/devel:languages:python:Factory/python311?expand=0&rev=93 --- CVE-2023-27043-email-parsing-errors.patch | 548 +++++++--------------- Revert-gh105127-left-tests.patch | 283 +++++++++++ python311.changes | 8 - python311.spec | 4 + 4 files changed, 455 insertions(+), 388 deletions(-) create mode 100644 Revert-gh105127-left-tests.patch diff --git a/CVE-2023-27043-email-parsing-errors.patch b/CVE-2023-27043-email-parsing-errors.patch index 6e4cc64..7aec4a2 100644 --- a/CVE-2023-27043-email-parsing-errors.patch +++ b/CVE-2023-27043-email-parsing-errors.patch @@ -1,185 +1,74 @@ --- - Doc/library/email.utils.rst | 19 - - Lib/email/utils.py | 151 +++++++- - Lib/test/test_email/test_email.py | 187 +++++++++- - Misc/NEWS.d/next/Library/2023-10-20-15-28-08.gh-issue-102988.dStNO7.rst | 8 - 4 files changed, 344 insertions(+), 21 deletions(-) + Doc/library/email.utils.rst | 26 +++ + Lib/email/utils.py | 63 +++++++ + Lib/test/test_email/test_email.py | 81 +++++++++- + Misc/NEWS.d/next/Security/2023-06-13-20-52-24.gh-issue-102988.Kei7Vf.rst | 4 + 4 files changed, 164 insertions(+), 10 deletions(-) ---- a/Doc/library/email.utils.rst -+++ b/Doc/library/email.utils.rst -@@ -60,13 +60,18 @@ of the new API. - begins with angle brackets, they are stripped off. - - --.. function:: parseaddr(address) -+.. function:: parseaddr(address, *, strict=True) - - Parse address -- which should be the value of some address-containing field such - as :mailheader:`To` or :mailheader:`Cc` -- into its constituent *realname* and +Index: Python-3.11.4/Doc/library/email.utils.rst +=================================================================== +--- Python-3.11.4.orig/Doc/library/email.utils.rst ++++ Python-3.11.4/Doc/library/email.utils.rst +@@ -67,6 +67,11 @@ of the new API. *email address* parts. Returns a tuple of that information, unless the parse fails, in which case a 2-tuple of ``('', '')`` is returned. -+ If *strict* is true, use a strict parser which rejects malformed inputs. -+ -+ .. versionchanged:: 3.13 -+ Add *strict* optional parameter and reject malformed inputs by default. ++ .. versionchanged:: 3.12 ++ For security reasons, addresses that were ambiguous and could parse into ++ multiple different addresses now cause ``('', '')`` to be returned ++ instead of only one of the *potential* addresses. + .. function:: formataddr(pair, charset='utf-8') -@@ -84,12 +89,15 @@ of the new API. - Added the *charset* option. - - --.. function:: getaddresses(fieldvalues) -+.. function:: getaddresses(fieldvalues, *, strict=True) - +@@ -89,7 +94,7 @@ of the new API. This method returns a list of 2-tuples of the form returned by ``parseaddr()``. *fieldvalues* is a sequence of header field values as might be returned by -- :meth:`Message.get_all `. Here's a simple + :meth:`Message.get_all `. Here's a simple - example that gets all the recipients of a message:: -+ :meth:`Message.get_all `. -+ -+ If *strict* is true, use a strict parser which rejects malformed inputs. -+ -+ Here's a simple example that gets all the recipients of a message:: ++ example that gets all the recipients of a message: from email.utils import getaddresses -@@ -99,6 +107,9 @@ of the new API. +@@ -99,6 +104,25 @@ of the new API. resent_ccs = msg.get_all('resent-cc', []) all_recipients = getaddresses(tos + ccs + resent_tos + resent_ccs) -+ .. versionchanged:: 3.13 -+ Add *strict* optional parameter and reject malformed inputs by default. ++ When parsing fails for a single fieldvalue, a 2-tuple of ``('', '')`` ++ is returned in its place. Other errors in parsing the list of ++ addresses such as a fieldvalue seemingly parsing into multiple ++ addresses may result in a list containing a single empty 2-tuple ++ ``[('', '')]`` being returned rather than returning potentially ++ invalid output. ++ ++ Example malformed input parsing: ++ ++ .. doctest:: ++ ++ >>> from email.utils import getaddresses ++ >>> getaddresses(['alice@example.com ', 'me@example.com']) ++ [('', '')] ++ ++ .. versionchanged:: 3.12 ++ The 2-tuple of ``('', '')`` in the returned values when parsing ++ fails were added as to address a security issue. + .. function:: parsedate(date) ---- a/Lib/email/utils.py -+++ b/Lib/email/utils.py -@@ -48,6 +48,7 @@ TICK = "'" - specialsre = re.compile(r'[][\\()<>@,:;".]') - escapesre = re.compile(r'[\\"]') - -+ - def _has_surrogates(s): - """Return True if s contains surrogate-escaped binary data.""" - # This check is based on the fact that unless there are surrogates, utf8 -@@ -106,12 +107,127 @@ def formataddr(pair, charset='utf-8'): +Index: Python-3.11.4/Lib/email/utils.py +=================================================================== +--- Python-3.11.4.orig/Lib/email/utils.py ++++ Python-3.11.4/Lib/email/utils.py +@@ -106,12 +106,54 @@ def formataddr(pair, charset='utf-8'): return address -+def _iter_escaped_chars(addr): -+ pos = 0 -+ escape = False -+ for pos, ch in enumerate(addr): -+ if escape: -+ yield (pos, '\\' + ch) -+ escape = False -+ elif ch == '\\': -+ escape = True -+ else: -+ yield (pos, ch) -+ if escape: -+ yield (pos, '\\') -+ -+ -+def _strip_quoted_realnames(addr): -+ """Strip real names between quotes.""" -+ if '"' not in addr: -+ # Fast path -+ return addr -+ -+ start = 0 -+ open_pos = None -+ result = [] -+ for pos, ch in _iter_escaped_chars(addr): -+ if ch == '"': -+ if open_pos is None: -+ open_pos = pos -+ else: -+ if start != open_pos: -+ result.append(addr[start:open_pos]) -+ start = pos + 1 -+ open_pos = None - --def getaddresses(fieldvalues): -- """Return a list of (REALNAME, EMAIL) for each fieldvalue.""" -- all = COMMASPACE.join(str(v) for v in fieldvalues) -- a = _AddressList(all) -- return a.addresslist -+ if start < len(addr): -+ result.append(addr[start:]) -+ -+ return ''.join(result) -+ -+ -+supports_strict_parsing = True -+ -+def getaddresses(fieldvalues, *, strict=True): -+ """Return a list of (REALNAME, EMAIL) or ('','') for each fieldvalue. -+ -+ When parsing fails for a fieldvalue, a 2-tuple of ('', '') is returned in -+ its place. -+ -+ If strict is true, use a strict parser which rejects malformed inputs. -+ """ -+ -+ # If strict is true, if the resulting list of parsed addresses is greater -+ # than the number of fieldvalues in the input list, a parsing error has -+ # occurred and consequently a list containing a single empty 2-tuple [('', -+ # '')] is returned in its place. This is done to avoid invalid output. -+ # -+ # Malformed input: getaddresses(['alice@example.com ']) -+ # Invalid output: [('', 'alice@example.com'), ('', 'bob@example.com')] -+ # Safe output: [('', '')] -+ -+ if not strict: -+ all = COMMASPACE.join(str(v) for v in fieldvalues) -+ a = _AddressList(all) -+ return a.addresslist -+ -+ fieldvalues = [str(v) for v in fieldvalues] -+ fieldvalues = _pre_parse_validation(fieldvalues) -+ addr = COMMASPACE.join(fieldvalues) -+ a = _AddressList(addr) -+ result = _post_parse_validation(a.addresslist) -+ -+ # Treat output as invalid if the number of addresses is not equal to the -+ # expected number of addresses. -+ n = 0 -+ for v in fieldvalues: -+ # When a comma is used in the Real Name part it is not a deliminator. -+ # So strip those out before counting the commas. -+ v = _strip_quoted_realnames(v) -+ # Expected number of addresses: 1 + number of commas -+ n += 1 + v.count(',') -+ if len(result) != n: -+ return [('', '')] -+ -+ return result -+ -+ -+def _check_parenthesis(addr): -+ # Ignore parenthesis in quoted real names. -+ addr = _strip_quoted_realnames(addr) -+ -+ opens = 0 -+ for pos, ch in _iter_escaped_chars(addr): -+ if ch == '(': -+ opens += 1 -+ elif ch == ')': -+ opens -= 1 -+ if opens < 0: -+ return False -+ return (opens == 0) -+ -+ +def _pre_parse_validation(email_header_fields): + accepted_values = [] + for v in email_header_fields: -+ if not _check_parenthesis(v): ++ s = v.replace('\\(', '').replace('\\)', '') ++ if s.count('(') != s.count(')'): + v = "('', '')" + accepted_values.append(v) + @@ -196,32 +85,46 @@ + accepted_values.append(v) + + return accepted_values ++ + + def getaddresses(fieldvalues): +- """Return a list of (REALNAME, EMAIL) for each fieldvalue.""" +- all = COMMASPACE.join(str(v) for v in fieldvalues) ++ """Return a list of (REALNAME, EMAIL) or ('','') for each fieldvalue. ++ ++ When parsing fails for a fieldvalue, a 2-tuple of ('', '') is returned in ++ its place. ++ ++ If the resulting list of parsed address is not the same as the number of ++ fieldvalues in the input list a parsing error has occurred. A list ++ containing a single empty 2-tuple [('', '')] is returned in its place. ++ This is done to avoid invalid output. ++ """ ++ fieldvalues = [str(v) for v in fieldvalues] ++ fieldvalues = _pre_parse_validation(fieldvalues) ++ all = COMMASPACE.join(v for v in fieldvalues) + a = _AddressList(all) +- return a.addresslist ++ result = _post_parse_validation(a.addresslist) ++ ++ n = 0 ++ for v in fieldvalues: ++ n += v.count(',') + 1 ++ ++ if len(result) != n: ++ return [('', '')] ++ ++ return result def _format_timetuple_and_zone(timetuple, zone): -@@ -205,16 +321,33 @@ def parsedate_to_datetime(data): - tzinfo=datetime.timezone(datetime.timedelta(seconds=tz))) - - --def parseaddr(addr): -+def parseaddr(addr, *, strict=True): - """ - Parse addr into its constituent realname and email address parts. - +@@ -212,9 +254,18 @@ def parseaddr(addr): Return a tuple of realname and email address, unless the parse fails, in which case return a 2-tuple of ('', ''). -+ -+ If strict is True, use a strict parser which rejects malformed inputs. """ - addrs = _AddressList(addr).addresslist - if not addrs: - return '', '' -+ if not strict: -+ addrs = _AddressList(addr).addresslist -+ if not addrs: -+ return ('', '') -+ return addrs[0] -+ + if isinstance(addr, list): + addr = addr[0] + @@ -237,225 +140,110 @@ return addrs[0] ---- a/Lib/test/test_email/test_email.py -+++ b/Lib/test/test_email/test_email.py -@@ -17,6 +17,7 @@ from unittest.mock import patch - - import email - import email.policy -+import email.utils - - from email.charset import Charset - from email.generator import Generator, DecodedGenerator, BytesGenerator -@@ -3321,15 +3322,137 @@ Foo +Index: Python-3.11.4/Lib/test/test_email/test_email.py +=================================================================== +--- Python-3.11.4.orig/Lib/test/test_email/test_email.py ++++ Python-3.11.4/Lib/test/test_email/test_email.py +@@ -3320,15 +3320,90 @@ Foo [('Al Person', 'aperson@dom.ain'), ('Bud Person', 'bperson@dom.ain')]) -+ def test_parsing_errors(self): -+ """Test for parsing errors from CVE-2023-27043 and CVE-2019-16056""" -+ alice = 'alice@example.org' -+ bob = 'bob@example.com' -+ empty = ('', '') ++ def test_getaddresses_parsing_errors(self): ++ """Test for parsing errors from CVE-2023-27043""" ++ eq = self.assertEqual ++ eq(utils.getaddresses(['alice@example.org(']), ++ [('', '')]) ++ eq(utils.getaddresses(['alice@example.org)']), ++ [('', '')]) ++ eq(utils.getaddresses(['alice@example.org<']), ++ [('', '')]) ++ eq(utils.getaddresses(['alice@example.org>']), ++ [('', '')]) ++ eq(utils.getaddresses(['alice@example.org@']), ++ [('', '')]) ++ eq(utils.getaddresses(['alice@example.org,']), ++ [('', 'alice@example.org'), ('', 'bob@example.com')]) ++ eq(utils.getaddresses(['alice@example.org;']), ++ [('', '')]) ++ eq(utils.getaddresses(['alice@example.org:']), ++ [('', '')]) ++ eq(utils.getaddresses(['alice@example.org.']), ++ [('', '')]) ++ eq(utils.getaddresses(['alice@example.org"']), ++ [('', '')]) ++ eq(utils.getaddresses(['alice@example.org[']), ++ [('', '')]) ++ eq(utils.getaddresses(['alice@example.org]']), ++ [('', '')]) + -+ # Test utils.getaddresses() and utils.parseaddr() on malformed email -+ # addresses: default behavior (strict=True) rejects malformed address, -+ # and strict=False which tolerates malformed address. -+ for invalid_separator, expected_non_strict in ( -+ ('(', [(f'<{bob}>', alice)]), -+ (')', [('', alice), empty, ('', bob)]), -+ ('<', [('', alice), empty, ('', bob), empty]), -+ ('>', [('', alice), empty, ('', bob)]), -+ ('[', [('', f'{alice}[<{bob}>]')]), -+ (']', [('', alice), empty, ('', bob)]), -+ ('@', [empty, empty, ('', bob)]), -+ (';', [('', alice), empty, ('', bob)]), -+ (':', [('', alice), ('', bob)]), -+ ('.', [('', alice + '.'), ('', bob)]), -+ ('"', [('', alice), ('', f'<{bob}>')]), -+ ): -+ address = f'{alice}{invalid_separator}<{bob}>' -+ with self.subTest(address=address): -+ self.assertEqual(utils.getaddresses([address]), -+ [empty]) -+ self.assertEqual(utils.getaddresses([address], strict=False), -+ expected_non_strict) -+ -+ self.assertEqual(utils.parseaddr([address]), -+ empty) -+ self.assertEqual(utils.parseaddr([address], strict=False), -+ ('', address)) -+ -+ # Comma (',') is treated differently depending on strict parameter. -+ # Comma without quotes. -+ address = f'{alice},<{bob}>' -+ self.assertEqual(utils.getaddresses([address]), -+ [('', alice), ('', bob)]) -+ self.assertEqual(utils.getaddresses([address], strict=False), -+ [('', alice), ('', bob)]) -+ self.assertEqual(utils.parseaddr([address]), -+ empty) -+ self.assertEqual(utils.parseaddr([address], strict=False), -+ ('', address)) -+ -+ # Real name between quotes containing comma. -+ address = '"Alice, alice@example.org" ' -+ expected_strict = ('Alice, alice@example.org', 'bob@example.com') -+ self.assertEqual(utils.getaddresses([address]), [expected_strict]) -+ self.assertEqual(utils.getaddresses([address], strict=False), [expected_strict]) -+ self.assertEqual(utils.parseaddr([address]), expected_strict) -+ self.assertEqual(utils.parseaddr([address], strict=False), -+ ('', address)) -+ -+ # Valid parenthesis in comments. -+ address = 'alice@example.org (Alice)' -+ expected_strict = ('Alice', 'alice@example.org') -+ self.assertEqual(utils.getaddresses([address]), [expected_strict]) -+ self.assertEqual(utils.getaddresses([address], strict=False), [expected_strict]) -+ self.assertEqual(utils.parseaddr([address]), expected_strict) -+ self.assertEqual(utils.parseaddr([address], strict=False), -+ ('', address)) -+ -+ # Invalid parenthesis in comments. -+ address = 'alice@example.org )Alice(' -+ self.assertEqual(utils.getaddresses([address]), [empty]) -+ self.assertEqual(utils.getaddresses([address], strict=False), -+ [('', 'alice@example.org'), ('', ''), ('', 'Alice')]) -+ self.assertEqual(utils.parseaddr([address]), empty) -+ self.assertEqual(utils.parseaddr([address], strict=False), -+ ('', address)) -+ -+ # Two addresses with quotes separated by comma. -+ address = '"Jane Doe" , "John Doe" ' -+ self.assertEqual(utils.getaddresses([address]), -+ [('Jane Doe', 'jane@example.net'), -+ ('John Doe', 'john@example.net')]) -+ self.assertEqual(utils.getaddresses([address], strict=False), -+ [('Jane Doe', 'jane@example.net'), -+ ('John Doe', 'john@example.net')]) -+ self.assertEqual(utils.parseaddr([address]), empty) -+ self.assertEqual(utils.parseaddr([address], strict=False), -+ ('', address)) -+ -+ # Test email.utils.supports_strict_parsing attribute -+ self.assertEqual(email.utils.supports_strict_parsing, True) ++ def test_parseaddr_parsing_errors(self): ++ """Test for parsing errors from CVE-2023-27043""" ++ eq = self.assertEqual ++ eq(utils.parseaddr(['alice@example.org(']), ++ ('', '')) ++ eq(utils.parseaddr(['alice@example.org)']), ++ ('', '')) ++ eq(utils.parseaddr(['alice@example.org<']), ++ ('', '')) ++ eq(utils.parseaddr(['alice@example.org>']), ++ ('', '')) ++ eq(utils.parseaddr(['alice@example.org@']), ++ ('', '')) ++ eq(utils.parseaddr(['alice@example.org,']), ++ ('', '')) ++ eq(utils.parseaddr(['alice@example.org;']), ++ ('', '')) ++ eq(utils.parseaddr(['alice@example.org:']), ++ ('', '')) ++ eq(utils.parseaddr(['alice@example.org.']), ++ ('', '')) ++ eq(utils.parseaddr(['alice@example.org"']), ++ ('', '')) ++ eq(utils.parseaddr(['alice@example.org[']), ++ ('', '')) ++ eq(utils.parseaddr(['alice@example.org]']), ++ ('', '')) + def test_getaddresses_nasty(self): -- eq = self.assertEqual -- eq(utils.getaddresses(['foo: ;']), [('', '')]) + eq = self.assertEqual + eq(utils.getaddresses(['foo: ;']), [('', '')]) - eq(utils.getaddresses( - ['[]*-- =~$']), - [('', ''), ('', ''), ('', '*--')]) -- eq(utils.getaddresses( -- ['foo: ;', '"Jason R. Mastaler" ']), -- [('', ''), ('Jason R. Mastaler', 'jason@dom.ain')]) -+ for addresses, expected in ( -+ (['"Sürname, Firstname" '], -+ [('Sürname, Firstname', 'to@example.com')]), -+ -+ (['foo: ;'], -+ [('', '')]), -+ -+ (['foo: ;', '"Jason R. Mastaler" '], -+ [('', ''), ('Jason R. Mastaler', 'jason@dom.ain')]), -+ -+ ([r'Pete(A nice \) chap) '], -+ [('Pete (A nice ) chap his account his host)', 'pete@silly.test')]), -+ -+ (['(Empty list)(start)Undisclosed recipients :(nobody(I know))'], -+ [('', '')]), -+ -+ (['Mary <@machine.tld:mary@example.net>, , jdoe@test . example'], -+ [('Mary', 'mary@example.net'), ('', ''), ('', 'jdoe@test.example')]), -+ -+ (['John Doe '], -+ [('John Doe (comment)', 'jdoe@machine.example')]), -+ -+ (['"Mary Smith: Personal Account" '], -+ [('Mary Smith: Personal Account', 'smith@home.example')]), -+ -+ (['Undisclosed recipients:;'], -+ [('', '')]), -+ -+ ([r', "Giant; \"Big\" Box" '], -+ [('', 'boss@nil.test'), ('Giant; "Big" Box', 'bob@example.net')]), -+ ): -+ with self.subTest(addresses=addresses): -+ self.assertEqual(utils.getaddresses(addresses), -+ expected) -+ self.assertEqual(utils.getaddresses(addresses, strict=False), -+ expected) -+ -+ addresses = ['[]*-- =~$'] -+ self.assertEqual(utils.getaddresses(addresses), -+ [('', '')]) -+ self.assertEqual(utils.getaddresses(addresses, strict=False), -+ [('', ''), ('', ''), ('', '*--')]) ++ eq(utils.getaddresses(['[]*-- =~$']), [('', '')]) + eq(utils.getaddresses( + ['foo: ;', '"Jason R. Mastaler" ']), + [('', ''), ('Jason R. Mastaler', 'jason@dom.ain')]) ++ eq(utils.getaddresses( ++ [r'Pete(A nice \) chap) ']), ++ [('Pete (A nice ) chap his account his host)', 'pete@silly.test')]) ++ eq(utils.getaddresses( ++ ['(Empty list)(start)Undisclosed recipients :(nobody(I know))']), ++ [('', '')]) ++ eq(utils.getaddresses( ++ ['Mary <@machine.tld:mary@example.net>, , jdoe@test . example']), ++ [('Mary', 'mary@example.net'), ('', ''), ('', 'jdoe@test.example')]) ++ eq(utils.getaddresses( ++ ['John Doe ']), ++ [('John Doe (comment)', 'jdoe@machine.example')]) ++ eq(utils.getaddresses( ++ ['"Mary Smith: Personal Account" ']), ++ [('Mary Smith: Personal Account', 'smith@home.example')]) ++ eq(utils.getaddresses( ++ ['Undisclosed recipients:;']), ++ [('', '')]) ++ eq(utils.getaddresses( ++ [r', "Giant; \"Big\" Box" ']), ++ [('', 'boss@nil.test'), ('Giant; "Big" Box', 'bob@example.net')]) def test_getaddresses_embedded_comment(self): """Test proper handling of a nested comment""" -@@ -3520,6 +3643,54 @@ multipart/report - m = cls(*constructor, policy=email.policy.default) - self.assertIs(m.policy, email.policy.default) - -+ def test_iter_escaped_chars(self): -+ self.assertEqual(list(utils._iter_escaped_chars(r'a\\b\"c\\"d')), -+ [(0, 'a'), -+ (2, '\\\\'), -+ (3, 'b'), -+ (5, '\\"'), -+ (6, 'c'), -+ (8, '\\\\'), -+ (9, '"'), -+ (10, 'd')]) -+ self.assertEqual(list(utils._iter_escaped_chars('a\\')), -+ [(0, 'a'), (1, '\\')]) -+ -+ def test_strip_quoted_realnames(self): -+ def check(addr, expected): -+ self.assertEqual(utils._strip_quoted_realnames(addr), expected) -+ -+ check('"Jane Doe" , "John Doe" ', -+ ' , ') -+ check(r'"Jane \"Doe\"." ', -+ ' ') -+ -+ # special cases -+ check(r'before"name"after', 'beforeafter') -+ check(r'before"name"', 'before') -+ check(r'b"name"', 'b') # single char -+ check(r'"name"after', 'after') -+ check(r'"name"a', 'a') # single char -+ check(r'"name"', '') -+ -+ # no change -+ for addr in ( -+ 'Jane Doe , John Doe ', -+ 'lone " quote', -+ ): -+ self.assertEqual(utils._strip_quoted_realnames(addr), addr) -+ -+ -+ def test_check_parenthesis(self): -+ addr = 'alice@example.net' -+ self.assertTrue(utils._check_parenthesis(f'{addr} (Alice)')) -+ self.assertFalse(utils._check_parenthesis(f'{addr} )Alice(')) -+ self.assertFalse(utils._check_parenthesis(f'{addr} (Alice))')) -+ self.assertFalse(utils._check_parenthesis(f'{addr} ((Alice)')) -+ -+ # Ignore real name between quotes -+ self.assertTrue(utils._check_parenthesis(f'")Alice((" {addr}')) -+ - - # Test the iterator/generators - class TestIterators(TestEmailBase): +Index: Python-3.11.4/Misc/NEWS.d/next/Security/2023-06-13-20-52-24.gh-issue-102988.Kei7Vf.rst +=================================================================== --- /dev/null -+++ b/Misc/NEWS.d/next/Library/2023-10-20-15-28-08.gh-issue-102988.dStNO7.rst -@@ -0,0 +1,8 @@ -+:func:`email.utils.getaddresses` and :func:`email.utils.parseaddr` now -+return ``('', '')`` 2-tuples in more situations where invalid email -+addresses are encountered instead of potentially inaccurate values. Add -+optional *strict* parameter to these two functions: use ``strict=False`` to -+get the old behavior, accept malformed inputs. -+``getattr(email.utils, 'supports_strict_parsing', False)`` can be use to check -+if the *strict* paramater is available. Patch by Thomas Dwyer and Victor -+Stinner to improve the CVE-2023-27043 fix. ++++ Python-3.11.4/Misc/NEWS.d/next/Security/2023-06-13-20-52-24.gh-issue-102988.Kei7Vf.rst +@@ -0,0 +1,4 @@ ++CVE-2023-27043: Prevent :func:`email.utils.parseaddr` ++and :func:`email.utils.getaddresses` from returning the realname portion of an ++invalid RFC2822 email header in the email address portion of the 2-tuple ++returned after being parsed by :class:`email._parseaddr.AddressList`. diff --git a/Revert-gh105127-left-tests.patch b/Revert-gh105127-left-tests.patch new file mode 100644 index 0000000..87ce40a --- /dev/null +++ b/Revert-gh105127-left-tests.patch @@ -0,0 +1,283 @@ +From 4288c623d62cf90d8e4444facb3379fb06d01140 Mon Sep 17 00:00:00 2001 +From: "Gregory P. Smith" +Date: Thu, 20 Jul 2023 20:30:52 -0700 +Subject: [PATCH] [3.12] gh-106669: Revert "gh-102988: Detect email address + parsing errors ... (GH-105127)" (GH-106733) + +This reverts commit 18dfbd035775c15533d13a98e56b1d2bf5c65f00. +Adds a regression test from the issue. + +See https://github.com/python/cpython/issues/106669.. +(cherry picked from commit a31dea1feb61793e48fa9aa5014f358352205c1d) + +Co-authored-by: Gregory P. Smith +--- + Doc/library/email.utils.rst | 26 -- + Lib/email/utils.py | 63 ------ + Lib/test/test_email/test_email.py | 96 +--------- + Misc/NEWS.d/next/Security/2023-06-13-20-52-24.gh-issue-102988.Kei7Vf.rst | 5 + 4 files changed, 31 insertions(+), 159 deletions(-) + create mode 100644 Misc/NEWS.d/next/Security/2023-06-13-20-52-24.gh-issue-102988.Kei7Vf.rst + +Index: Python-3.11.4/Doc/library/email.utils.rst +=================================================================== +--- Python-3.11.4.orig/Doc/library/email.utils.rst ++++ Python-3.11.4/Doc/library/email.utils.rst +@@ -67,11 +67,6 @@ of the new API. + *email address* parts. Returns a tuple of that information, unless the parse + fails, in which case a 2-tuple of ``('', '')`` is returned. + +- .. versionchanged:: 3.12 +- For security reasons, addresses that were ambiguous and could parse into +- multiple different addresses now cause ``('', '')`` to be returned +- instead of only one of the *potential* addresses. +- + + .. function:: formataddr(pair, charset='utf-8') + +@@ -94,7 +89,7 @@ of the new API. + This method returns a list of 2-tuples of the form returned by ``parseaddr()``. + *fieldvalues* is a sequence of header field values as might be returned by + :meth:`Message.get_all `. Here's a simple +- example that gets all the recipients of a message: ++ example that gets all the recipients of a message:: + + from email.utils import getaddresses + +@@ -104,25 +99,6 @@ of the new API. + resent_ccs = msg.get_all('resent-cc', []) + all_recipients = getaddresses(tos + ccs + resent_tos + resent_ccs) + +- When parsing fails for a single fieldvalue, a 2-tuple of ``('', '')`` +- is returned in its place. Other errors in parsing the list of +- addresses such as a fieldvalue seemingly parsing into multiple +- addresses may result in a list containing a single empty 2-tuple +- ``[('', '')]`` being returned rather than returning potentially +- invalid output. +- +- Example malformed input parsing: +- +- .. doctest:: +- +- >>> from email.utils import getaddresses +- >>> getaddresses(['alice@example.com ', 'me@example.com']) +- [('', '')] +- +- .. versionchanged:: 3.12 +- The 2-tuple of ``('', '')`` in the returned values when parsing +- fails were added as to address a security issue. +- + + .. function:: parsedate(date) + +Index: Python-3.11.4/Lib/email/utils.py +=================================================================== +--- Python-3.11.4.orig/Lib/email/utils.py ++++ Python-3.11.4/Lib/email/utils.py +@@ -106,54 +106,12 @@ def formataddr(pair, charset='utf-8'): + return address + + +-def _pre_parse_validation(email_header_fields): +- accepted_values = [] +- for v in email_header_fields: +- s = v.replace('\\(', '').replace('\\)', '') +- if s.count('(') != s.count(')'): +- v = "('', '')" +- accepted_values.append(v) +- +- return accepted_values +- +- +-def _post_parse_validation(parsed_email_header_tuples): +- accepted_values = [] +- # The parser would have parsed a correctly formatted domain-literal +- # The existence of an [ after parsing indicates a parsing failure +- for v in parsed_email_header_tuples: +- if '[' in v[1]: +- v = ('', '') +- accepted_values.append(v) +- +- return accepted_values +- + + def getaddresses(fieldvalues): +- """Return a list of (REALNAME, EMAIL) or ('','') for each fieldvalue. +- +- When parsing fails for a fieldvalue, a 2-tuple of ('', '') is returned in +- its place. +- +- If the resulting list of parsed address is not the same as the number of +- fieldvalues in the input list a parsing error has occurred. A list +- containing a single empty 2-tuple [('', '')] is returned in its place. +- This is done to avoid invalid output. +- """ +- fieldvalues = [str(v) for v in fieldvalues] +- fieldvalues = _pre_parse_validation(fieldvalues) +- all = COMMASPACE.join(v for v in fieldvalues) ++ """Return a list of (REALNAME, EMAIL) for each fieldvalue.""" ++ all = COMMASPACE.join(str(v) for v in fieldvalues) + a = _AddressList(all) +- result = _post_parse_validation(a.addresslist) +- +- n = 0 +- for v in fieldvalues: +- n += v.count(',') + 1 +- +- if len(result) != n: +- return [('', '')] +- +- return result ++ return a.addresslist + + + def _format_timetuple_and_zone(timetuple, zone): +@@ -254,18 +212,9 @@ def parseaddr(addr): + Return a tuple of realname and email address, unless the parse fails, in + which case return a 2-tuple of ('', ''). + """ +- if isinstance(addr, list): +- addr = addr[0] +- +- if not isinstance(addr, str): +- return ('', '') +- +- addr = _pre_parse_validation([addr])[0] +- addrs = _post_parse_validation(_AddressList(addr).addresslist) +- +- if not addrs or len(addrs) > 1: +- return ('', '') +- ++ addrs = _AddressList(addr).addresslist ++ if not addrs: ++ return '', '' + return addrs[0] + + +Index: Python-3.11.4/Lib/test/test_email/test_email.py +=================================================================== +--- Python-3.11.4.orig/Lib/test/test_email/test_email.py ++++ Python-3.11.4/Lib/test/test_email/test_email.py +@@ -3320,90 +3320,32 @@ Foo + [('Al Person', 'aperson@dom.ain'), + ('Bud Person', 'bperson@dom.ain')]) + +- def test_getaddresses_parsing_errors(self): +- """Test for parsing errors from CVE-2023-27043""" +- eq = self.assertEqual +- eq(utils.getaddresses(['alice@example.org(']), +- [('', '')]) +- eq(utils.getaddresses(['alice@example.org)']), +- [('', '')]) +- eq(utils.getaddresses(['alice@example.org<']), +- [('', '')]) +- eq(utils.getaddresses(['alice@example.org>']), +- [('', '')]) +- eq(utils.getaddresses(['alice@example.org@']), +- [('', '')]) +- eq(utils.getaddresses(['alice@example.org,']), +- [('', 'alice@example.org'), ('', 'bob@example.com')]) +- eq(utils.getaddresses(['alice@example.org;']), +- [('', '')]) +- eq(utils.getaddresses(['alice@example.org:']), +- [('', '')]) +- eq(utils.getaddresses(['alice@example.org.']), +- [('', '')]) +- eq(utils.getaddresses(['alice@example.org"']), +- [('', '')]) +- eq(utils.getaddresses(['alice@example.org[']), +- [('', '')]) +- eq(utils.getaddresses(['alice@example.org]']), +- [('', '')]) +- +- def test_parseaddr_parsing_errors(self): +- """Test for parsing errors from CVE-2023-27043""" +- eq = self.assertEqual +- eq(utils.parseaddr(['alice@example.org(']), +- ('', '')) +- eq(utils.parseaddr(['alice@example.org)']), +- ('', '')) +- eq(utils.parseaddr(['alice@example.org<']), +- ('', '')) +- eq(utils.parseaddr(['alice@example.org>']), +- ('', '')) +- eq(utils.parseaddr(['alice@example.org@']), +- ('', '')) +- eq(utils.parseaddr(['alice@example.org,']), +- ('', '')) +- eq(utils.parseaddr(['alice@example.org;']), +- ('', '')) +- eq(utils.parseaddr(['alice@example.org:']), +- ('', '')) +- eq(utils.parseaddr(['alice@example.org.']), +- ('', '')) +- eq(utils.parseaddr(['alice@example.org"']), +- ('', '')) +- eq(utils.parseaddr(['alice@example.org[']), +- ('', '')) +- eq(utils.parseaddr(['alice@example.org]']), +- ('', '')) ++ def test_getaddresses_comma_in_name(self): ++ """GH-106669 regression test.""" ++ self.assertEqual( ++ utils.getaddresses( ++ [ ++ '"Bud, Person" ', ++ 'aperson@dom.ain (Al Person)', ++ '"Mariusz Felisiak" ', ++ ] ++ ), ++ [ ++ ('Bud, Person', 'bperson@dom.ain'), ++ ('Al Person', 'aperson@dom.ain'), ++ ('Mariusz Felisiak', 'to@example.com'), ++ ], ++ ) + + def test_getaddresses_nasty(self): + eq = self.assertEqual + eq(utils.getaddresses(['foo: ;']), [('', '')]) +- eq(utils.getaddresses(['[]*-- =~$']), [('', '')]) ++ eq(utils.getaddresses( ++ ['[]*-- =~$']), ++ [('', ''), ('', ''), ('', '*--')]) + eq(utils.getaddresses( + ['foo: ;', '"Jason R. Mastaler" ']), + [('', ''), ('Jason R. Mastaler', 'jason@dom.ain')]) +- eq(utils.getaddresses( +- [r'Pete(A nice \) chap) ']), +- [('Pete (A nice ) chap his account his host)', 'pete@silly.test')]) +- eq(utils.getaddresses( +- ['(Empty list)(start)Undisclosed recipients :(nobody(I know))']), +- [('', '')]) +- eq(utils.getaddresses( +- ['Mary <@machine.tld:mary@example.net>, , jdoe@test . example']), +- [('Mary', 'mary@example.net'), ('', ''), ('', 'jdoe@test.example')]) +- eq(utils.getaddresses( +- ['John Doe ']), +- [('John Doe (comment)', 'jdoe@machine.example')]) +- eq(utils.getaddresses( +- ['"Mary Smith: Personal Account" ']), +- [('Mary Smith: Personal Account', 'smith@home.example')]) +- eq(utils.getaddresses( +- ['Undisclosed recipients:;']), +- [('', '')]) +- eq(utils.getaddresses( +- [r', "Giant; \"Big\" Box" ']), +- [('', 'boss@nil.test'), ('Giant; "Big" Box', 'bob@example.net')]) + + def test_getaddresses_embedded_comment(self): + """Test proper handling of a nested comment""" +Index: Python-3.11.4/Misc/NEWS.d/next/Security/2023-06-13-20-52-24.gh-issue-102988.Kei7Vf.rst +=================================================================== +--- Python-3.11.4.orig/Misc/NEWS.d/next/Security/2023-06-13-20-52-24.gh-issue-102988.Kei7Vf.rst ++++ Python-3.11.4/Misc/NEWS.d/next/Security/2023-06-13-20-52-24.gh-issue-102988.Kei7Vf.rst +@@ -1,3 +1,8 @@ ++Reverted the :mod:`email.utils` security improvement change released in ++3.12beta4 that unintentionally caused :mod:`email.utils.getaddresses` to fail ++to parse email addresses with a comma in the quoted name field. ++See :gh:`106669`. ++ + CVE-2023-27043: Prevent :func:`email.utils.parseaddr` + and :func:`email.utils.getaddresses` from returning the realname portion of an + invalid RFC2822 email header in the email address portion of the 2-tuple diff --git a/python311.changes b/python311.changes index 324f3d9..af0f2b6 100644 --- a/python311.changes +++ b/python311.changes @@ -1,11 +1,3 @@ -------------------------------------------------------------------- -Mon Dec 18 16:20:58 UTC 2023 - Matej Cepl - -- Refresh CVE-2023-27043-email-parsing-errors.patch to - gh#python/cpython!111116, fixing bsc#1210638 (CVE-2023-27043). -- Thus we can remove Revert-gh105127-left-tests.patch, which is - now useless. - ------------------------------------------------------------------- Fri Dec 15 10:04:33 UTC 2023 - Daniel Garcia diff --git a/python311.spec b/python311.spec index 16f2357..0e3c740 100644 --- a/python311.spec +++ b/python311.spec @@ -165,6 +165,9 @@ Patch39: skip_if_buildbot-extend.patch # Detect email address parsing errors and return empty tuple to # indicate the parsing error (old API) Patch40: CVE-2023-27043-email-parsing-errors.patch +# PATCH-FIX-UPSTREAM Revert-gh105127-left-tests.patch bsc#1210638 mcepl@suse.com +# Partially revert previous patch +Patch41: Revert-gh105127-left-tests.patch BuildRequires: autoconf-archive BuildRequires: automake BuildRequires: fdupes @@ -425,6 +428,7 @@ other applications. %patch36 -p1 %patch39 -p1 %patch40 -p1 +%patch41 -p1 # drop Autoconf version requirement sed -i 's/^AC_PREREQ/dnl AC_PREREQ/' configure.ac From 5fae7e4a44ef1cf0d0f9ac5ac5dbcd054fb7a5f9e76da2d95412d2ef323d7a58 Mon Sep 17 00:00:00 2001 From: Matej Cepl Date: Tue, 19 Dec 2023 15:24:17 +0000 Subject: [PATCH 6/7] Accepting request 1134054 from devel:languages:python:Factory revert OBS-URL: https://build.opensuse.org/request/show/1134054 OBS-URL: https://build.opensuse.org/package/show/devel:languages:python:Factory/python311?expand=0&rev=94 --- Python-3.11.6.tar.xz | 3 + Python-3.11.6.tar.xz.asc | 16 ++++ Python-3.11.7.tar.xz | 3 - Python-3.11.7.tar.xz.asc | 16 ---- fix_configure_rst.patch | 26 +++--- python311.changes | 197 --------------------------------------- python311.spec | 2 +- 7 files changed, 33 insertions(+), 230 deletions(-) create mode 100644 Python-3.11.6.tar.xz create mode 100644 Python-3.11.6.tar.xz.asc delete mode 100644 Python-3.11.7.tar.xz delete mode 100644 Python-3.11.7.tar.xz.asc diff --git a/Python-3.11.6.tar.xz b/Python-3.11.6.tar.xz new file mode 100644 index 0000000..68fb387 --- /dev/null +++ b/Python-3.11.6.tar.xz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0fab78fa7f133f4f38210c6260d90d7c0d5c7198446419ce057ec7ac2e6f5f38 +size 20067204 diff --git a/Python-3.11.6.tar.xz.asc b/Python-3.11.6.tar.xz.asc new file mode 100644 index 0000000..0979fa7 --- /dev/null +++ b/Python-3.11.6.tar.xz.asc @@ -0,0 +1,16 @@ +-----BEGIN PGP SIGNATURE----- + +iQIzBAABCAAdFiEEz9yiRbEEPPKl+Xhl/+h0BBaL2EcFAmUaybgACgkQ/+h0BBaL +2EdGzg//bg0KTy9DGWD56RUzCKaxdao+FVV2wS8FdghTvGJHM8uDehRESzj6Viag +bks/XuTmJ4xlaW+Ilje4VU5gkdgWt44S2ZGretn+zwrv6B5L697s1j4IOnyEywzm +rQ+a5aMRsW+m8kVoKDLsfbCcrwgxXEFln18y9Qp79QhbUpTVjp5vMwWdYyv2JnYb +G2QraFr0mQEhbVsiYpSVRWiacJi7JBuHTU/hNcN/q1ACf/CX3NSLDfL2bJEWNekg +JW3z0c81vLI9D+NkQ8xWOxgEO6G4Aav4F3t37ujuf2W/CI+NZlEknSt2ITxKeTHw +dwtIMXQc3MyG4ExnAwv1LqtL1H3Dm6SAE7sSyn4uG2uknk4xSuyoMIVMJefXmxbQ +Pd8v6/QnqT3U6MVuVlvCq4AWRotZwRpB7MdnpvA0t1tmANOA8i0MWrhCw0ccYk1a +X/3ewR0+RfQYkDvFfnJpbbsQlK+lhZgt/VmQNOHvWoc/tDnB73b2seOmGkx016TY +hozXE4FXtR+8qk9CfJTd8QRNST8KBUCB9C3eoLciruugmyrUvDLjki/u9GNOIl/b +kVJzcD6eQJC9BthJXuKaKDLKX1a1a9J8GUzVXsW1kuEMQHuv/v7Cf6MzmHWWO3QN +yRgqFXkic/U8RgSHUbtozo16GMlU+RDC6bQgZR7mfE0eFCJnD6U= +=mxrE +-----END PGP SIGNATURE----- diff --git a/Python-3.11.7.tar.xz b/Python-3.11.7.tar.xz deleted file mode 100644 index 8d3a65d..0000000 --- a/Python-3.11.7.tar.xz +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:18e1aa7e66ff3a58423d59ed22815a6954e53342122c45df20c96877c062b9b7 -size 20074108 diff --git a/Python-3.11.7.tar.xz.asc b/Python-3.11.7.tar.xz.asc deleted file mode 100644 index 3eee3d2..0000000 --- a/Python-3.11.7.tar.xz.asc +++ /dev/null @@ -1,16 +0,0 @@ ------BEGIN PGP SIGNATURE----- - -iQIzBAABCAAdFiEEz9yiRbEEPPKl+Xhl/+h0BBaL2EcFAmVuFigACgkQ/+h0BBaL -2EeHPg/+LU5xs2ZDrQogDcH+A1v8RyursiggypdM5hXTrsFsTCIk4iekcI9xkhG1 -ltNX4UuCe5PUEbTgtaWP0ncXARrUnPCoQaQ1sHVDTYoHegancsk+sXZc1JM7qr0p -Y4Ig6mKjuHFMXCInQSI2GaH4t5r4Z1jGk/PGrecIHOPJgqfA/6Z3TBF5N+y3jEvS -2QazMB298q4RDhh9m3REe8LwFPHDlfw9eRohv0MB8xygg9KtxhLZrN7gLBQZvKGD -ihNw6EgJj5OZ0dvwKCCXnlZuwknuJW7vAOPHhYeenPdVdYCGoRSyN7JdD07L+5AG -O14l2rqZrz5Eu28by+kAUrcPYAfAXekw1PmtT3HSd9U/nqnUiTkkJcjyGG/e3cjJ -sUDKMNCSBq0G7j5DB3bB6VHkZjVuz+T+iR5QdfJ4kI2pYSuE/rUj1rhkUXApYsHl -7Wff0QbOW6QT1wCtQcMpJSzkTDVJVYxiqrko/ihlOhphDHYLdOIGOrxWAUwc06x/ -BhJD6tM1kEVZvifoJp1OsNwDzZ/Ku6CUs05E1vWxdeNVeANyKAgCZ5hOVmhnv866 -11zfgo/znRsMzMIyJuy0bhO0C6omVLzzfhipAbZM2jDorn37xxV0v/I0pceNtLrp -YR7Tjs7+Ihe6/oItjW53j9T7ANdgQ1RVDg98lKlPFNL+hxfctwY= -=0Pkd ------END PGP SIGNATURE----- diff --git a/fix_configure_rst.patch b/fix_configure_rst.patch index e45e240..bd08f1d 100644 --- a/fix_configure_rst.patch +++ b/fix_configure_rst.patch @@ -3,37 +3,37 @@ Misc/NEWS | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) -Index: Python-3.11.7/Doc/using/configure.rst +Index: Python-3.11.6/Doc/using/configure.rst =================================================================== ---- Python-3.11.7.orig/Doc/using/configure.rst -+++ Python-3.11.7/Doc/using/configure.rst +--- Python-3.11.6.orig/Doc/using/configure.rst ++++ Python-3.11.6/Doc/using/configure.rst @@ -41,7 +41,6 @@ General Options See :data:`sys.int_info.bits_per_digit `. --.. option:: --with-cxx-main - .. option:: --with-cxx-main=COMPILER +-.. cmdoption:: --with-cxx-main + .. cmdoption:: --with-cxx-main=COMPILER Compile the Python ``main()`` function and link Python executable with C++ @@ -527,13 +526,11 @@ macOS Options See ``Mac/README.rst``. --.. option:: --enable-universalsdk - .. option:: --enable-universalsdk=SDKDIR +-.. cmdoption:: --enable-universalsdk + .. cmdoption:: --enable-universalsdk=SDKDIR Create a universal binary build. *SDKDIR* specifies which macOS SDK should be used to perform the build (default is no). --.. option:: --enable-framework - .. option:: --enable-framework=INSTALLDIR +-.. cmdoption:: --enable-framework + .. cmdoption:: --enable-framework=INSTALLDIR Create a Python.framework rather than a traditional Unix install. Optional -Index: Python-3.11.7/Misc/NEWS +Index: Python-3.11.6/Misc/NEWS =================================================================== ---- Python-3.11.7.orig/Misc/NEWS -+++ Python-3.11.7/Misc/NEWS -@@ -9012,7 +9012,7 @@ C API +--- Python-3.11.6.orig/Misc/NEWS ++++ Python-3.11.6/Misc/NEWS +@@ -8708,7 +8708,7 @@ C API - bpo-40939: Removed documentation for the removed ``PyParser_*`` C API. - bpo-43795: The list in :ref:`limited-api-list` now shows the public name diff --git a/python311.changes b/python311.changes index af0f2b6..944ef78 100644 --- a/python311.changes +++ b/python311.changes @@ -1,200 +1,3 @@ -------------------------------------------------------------------- -Fri Dec 15 10:04:33 UTC 2023 - Daniel Garcia - -- Update patch fix_configure_rst.patch -- Update to 3.11.7: - - Core and Builtins - - gh-112625: Fixes a bug where a bytearray object could be cleared - while iterating over an argument in the bytearray.join() method - that could result in reading memory after it was freed. - - gh-112388: Fix an error that was causing the parser to try to - overwrite tokenizer errors. Patch by pablo Galindo - - gh-112387: Fix error positions for decoded strings with - backwards tokenize errors. Patch by Pablo Galindo - - gh-112266: Change docstrings of __dict__ and __weakref__. - - gh-109181: Speed up Traceback object creation by lazily compute - the line number. Patch by Pablo Galindo - - gh-102388: Fix a bug where iso2022_jp_3 and iso2022_jp_2004 - codecs read out of bounds - - gh-111366: Fix an issue in the codeop that was causing - SyntaxError exceptions raised in the presence of invalid syntax - to not contain precise error messages. Patch by Pablo Galindo - - gh-111380: Fix a bug that was causing SyntaxWarning to appear - twice when parsing if invalid syntax is encountered later. Patch - by Pablo galindo - - gh-88116: Traceback location ranges involving wide unicode - characters (like emoji and asian characters) now are properly - highlighted. Patch by Batuhan Taskaya and Pablo Galindo. - - gh-94438: Fix a regression that prevented jumping across is None - and is not None when debugging. Patch by Savannah Ostrowski. - - gh-110696: Fix incorrect error message for invalid argument - unpacking. Patch by Pablo Galindo - - gh-110237: Fix missing error checks for calls to PyList_Append - in _PyEval_MatchClass. - - gh-109216: Fix possible memory leak in BUILD_MAP. - - - Library - - gh-112618: Fix a caching bug relating to typing.Annotated. - Annotated[str, True] is no longer identical to Annotated[str, - 1]. - - gh-112509: Fix edge cases that could cause a key to be present - in both the __required_keys__ and __optional_keys__ attributes - of a typing.TypedDict. Patch by Jelle Zijlstra. - - gh-94722: Fix bug where comparison between instances of DocTest - fails if one of them has None as its lineno. - - gh-112105: Make readline.set_completer_delims() work with - libedit - - gh-111942: Fix SystemError in the TextIOWrapper constructor with - non-encodable “errors” argument in non-debug mode. - - gh-109538: Issue warning message instead of having RuntimeError - be displayed when event loop has already been closed at - StreamWriter.__del__(). - - gh-111942: Fix crashes in io.TextIOWrapper.reconfigure() when - pass invalid arguments, e.g. non-string encoding. - - gh-111804: Remove posix.fallocate() under WASI as the underlying - posix_fallocate() is not available in WASI preview2. - - gh-111841: Fix truncating arguments on an embedded null - character in os.putenv() and os.unsetenv() on Windows. - - gh-111541: Fix doctest for SyntaxError not-builtin subclasses. - - gh-110894: Call loop exception handler for exceptions in - client_connected_cb of asyncio.start_server() so that - applications can handle it. Patch by Kumar Aditya. - - gh-111531: Fix reference leaks in bind_class() and bind_all() - methods of tkinter widgets. - - gh-111356: Added io.text_encoding(), io.DEFAULT_BUFFER_SIZE, and - io.IncrementalNewlineDecoder to io.__all__. - - gh-68166: Remove mention of not supported “vsapi” element type - in tkinter.ttk.Style.element_create(). Add tests for - element_create() and other ttk.Style methods. Add examples for - element_create() in the documentation. - - gh-111251: Fix _blake2 not checking for errors when - initializing. - - gh-111174: Fix crash in io.BytesIO.getbuffer() called repeatedly - for empty BytesIO. - - gh-111187: Postpone removal version for - locale.getdefaultlocale() to Python 3.15. - - gh-111159: Fix doctest output comparison for exceptions with - notes. - - gh-110910: Fix invalid state handling in asyncio.TaskGroup and - asyncio.Timeout. They now raise proper RuntimeError if they are - improperly used and are left in consistent state after this. - - gh-111092: Make turtledemo run without default root enabled. - - gh-110590: Fix a bug in _sre.compile() where TypeError would be - overwritten by OverflowError when the code argument was a list - of non-ints. - - gh-65052: Prevent pdb from crashing when trying to display - undisplayable objects - - gh-110519: Deprecation warning about non-integer number in - gettext now alwais refers to the line in the user code where - gettext function or method is used. Previously it could refer to - a line in gettext code. - - gh-110378: contextmanager() and asynccontextmanager() context - managers now close an invalid underlying generator object that - yields more then one value. - - gh-110365: Fix termios.tcsetattr() bug that was overwritting - existing errors during parsing integers from term list. - - gh-110196: Add __reduce__ method to IPv6Address in order to keep - scope_id - - gh-109747: Improve errors for unsupported look-behind patterns. - Now re.error is raised instead of OverflowError or RuntimeError - for too large width of look-behind pattern. - - gh-109786: Fix possible reference leaks and crash when re-enter - the __next__() method of itertools.pairwise. - - gh-108791: Improved error handling in pdb command line - interface, making it produce more concise error messages. - - gh-73561: Omit the interface scope from an IPv6 address when - used as Host header by http.client. - - gh-86826: zipinfo now supports the full range of values in the - TZ string determined by RFC 8536 and detects all invalid - formats. Both Python and C implementations now raise exceptions - of the same type on invalid data. - - bpo-41422: Fixed memory leaks of pickle.Pickler and - pickle.Unpickler involving cyclic references via the internal - memo mapping. - - bpo-40262: The ssl.SSLSocket.recv_into() method no longer - requires the buffer argument to implement __len__ and supports - buffers with arbitrary item size. - - bpo-35191: Fix unexpected integer truncation in - socket.setblocking() which caused it to interpret multiples of - 2**32 as False. - - - Documentation - - gh-108826: dis module command-line interface is now mentioned in - documentation. - - - Tests - - gh-110367: Make regrtest --verbose3 option compatible with - --huntrleaks -jN options. The ./python -m test -j1 -R 3:3 - --verbose3 command now works as expected. Patch by Victor - Stinner. - - gh-111309: distutils tests can now be run via unittest. - - gh-111165: Remove no longer used functions run_unittest() and - run_doctest() and class BasicTestRunner from the test.support - module. - - gh-110932: Fix regrtest if the SOURCE_DATE_EPOCH environment - variable is defined: use the variable value as the random seed. - Patch by Victor Stinner. - - gh-110995: test_gdb: Fix detection of gdb built without Python - scripting support. Patch by Victor Stinner. - - gh-110918: Test case matching patterns specified by options - --match, --ignore, --matchfile and --ignorefile are now tested - in the order of specification, and the last match determines - whether the test case be run or ignored. - - gh-110647: Fix test_stress_modifying_handlers() of test_signal. - Patch by Victor Stinner. - - gh-103053: Fix test_tools.test_freeze on FreeBSD: run “make - distclean” instead of “make clean” in the copied source - directory to remove also the “python” program. Patch by Victor - Stinner. - - gh-110167: Fix a deadlock in test_socket when server fails with - a timeout but the client is still running in its thread. Don’t - hold a lock to call cleanup functions in doCleanups(). One of - the cleanup function waits until the client completes, whereas - the client could deadlock if it called addCleanup() in such - situation. Patch by Victor Stinner. - - gh-110388: Add tests for tty. - - gh-81002: Add tests for termios. - - gh-110267: Add tests for pickling and copying PyStructSequence - objects. Patched by Xuehai Pan. - - gh-109974: Fix race conditions in test_threading lock tests. - Wait until a condition is met rather than using time.sleep() - with a hardcoded number of seconds. Patch by Victor Stinner. - - gh-109972: Split test_gdb.py file into a test_gdb package made - of multiple tests, so tests can now be run in parallel. Patch by - Victor Stinner. - - gh-104736: Fix test_gdb on Python built with LLVM clang 16 on - Linux ppc64le (ex: Fedora 38). Search patterns in gdb “bt” - command output to detect when gdb fails to retrieve the - traceback. For example, skip a test if Backtrace stopped: frame - did not save the PC is found. Patch by Victor Stinner. - - gh-108927: Fixed order dependence in running tests in the same - process when a test that has submodules (e.g. test_importlib) - follows a test that imports its submodule (e.g. - test_importlib.util) and precedes a test (e.g. test_unittest or - test_compileall) that uses that submodule. - - - Build - - gh-103053: “make check-clean-src” now also checks if the - “python” program is found in the source directory: fail with an - error if it does exist. Patch by Victor Stinner. - - gh-109191: Fix compile error when building with recent versions - of libedit. - - - IDLE - - bpo-35668: Add docstrings to the IDLE debugger module. Fix two - bugs: initialize Idb.botframe (should be in Bdb); in - Idb.in_rpc_code, check whether prev_frame is None before trying - to use it. Greatly expand test_debugger. - - - C API - - gh-112438: Fix support of format units “es”, “et”, “es#”, and - “et#” in nested tuples in PyArg_ParseTuple()-like functions. - - gh-109521: PyImport_GetImporter() now sets RuntimeError if it - fails to get sys.path_hooks or sys.path_importer_cache or they - are not list and dict correspondingly. Previously it could - return NULL without setting error in obscure cases, crash or - raise SystemError if these attributes have wrong type. - ------------------------------------------------------------------- Wed Nov 15 09:06:16 UTC 2023 - Daniel Garcia diff --git a/python311.spec b/python311.spec index 0e3c740..05e3990 100644 --- a/python311.spec +++ b/python311.spec @@ -94,7 +94,7 @@ %define dynlib() %{sitedir}/lib-dynload/%{1}.cpython-%{abi_tag}-%{archname}-%{_os}%{?_gnu}%{?armsuffix}.so %bcond_without profileopt Name: %{python_pkg_name}%{psuffix} -Version: 3.11.7 +Version: 3.11.6 Release: 0 Summary: Python 3 Interpreter License: Python-2.0 From ebe00d33da868105a0688d591b7d6b51965cb95d4045772c83859504ca5e3189 Mon Sep 17 00:00:00 2001 From: Matej Cepl Date: Tue, 19 Dec 2023 15:40:30 +0000 Subject: [PATCH 7/7] - Refresh CVE-2023-27043-email-parsing-errors.patch to gh#python/cpython!111116, fixing bsc#1210638 (CVE-2023-27043). - Thus we can remove Revert-gh105127-left-tests.patch, which is now useless. OBS-URL: https://build.opensuse.org/package/show/devel:languages:python:Factory/python311?expand=0&rev=95 --- CVE-2023-27043-email-parsing-errors.patch | 548 +++++++++++++++------- Revert-gh105127-left-tests.patch | 283 ----------- python311.changes | 8 + python311.spec | 4 - 4 files changed, 388 insertions(+), 455 deletions(-) delete mode 100644 Revert-gh105127-left-tests.patch diff --git a/CVE-2023-27043-email-parsing-errors.patch b/CVE-2023-27043-email-parsing-errors.patch index 7aec4a2..6e4cc64 100644 --- a/CVE-2023-27043-email-parsing-errors.patch +++ b/CVE-2023-27043-email-parsing-errors.patch @@ -1,74 +1,185 @@ --- - Doc/library/email.utils.rst | 26 +++ - Lib/email/utils.py | 63 +++++++ - Lib/test/test_email/test_email.py | 81 +++++++++- - Misc/NEWS.d/next/Security/2023-06-13-20-52-24.gh-issue-102988.Kei7Vf.rst | 4 - 4 files changed, 164 insertions(+), 10 deletions(-) + Doc/library/email.utils.rst | 19 - + Lib/email/utils.py | 151 +++++++- + Lib/test/test_email/test_email.py | 187 +++++++++- + Misc/NEWS.d/next/Library/2023-10-20-15-28-08.gh-issue-102988.dStNO7.rst | 8 + 4 files changed, 344 insertions(+), 21 deletions(-) -Index: Python-3.11.4/Doc/library/email.utils.rst -=================================================================== ---- Python-3.11.4.orig/Doc/library/email.utils.rst -+++ Python-3.11.4/Doc/library/email.utils.rst -@@ -67,6 +67,11 @@ of the new API. +--- a/Doc/library/email.utils.rst ++++ b/Doc/library/email.utils.rst +@@ -60,13 +60,18 @@ of the new API. + begins with angle brackets, they are stripped off. + + +-.. function:: parseaddr(address) ++.. function:: parseaddr(address, *, strict=True) + + Parse address -- which should be the value of some address-containing field such + as :mailheader:`To` or :mailheader:`Cc` -- into its constituent *realname* and *email address* parts. Returns a tuple of that information, unless the parse fails, in which case a 2-tuple of ``('', '')`` is returned. -+ .. versionchanged:: 3.12 -+ For security reasons, addresses that were ambiguous and could parse into -+ multiple different addresses now cause ``('', '')`` to be returned -+ instead of only one of the *potential* addresses. ++ If *strict* is true, use a strict parser which rejects malformed inputs. ++ ++ .. versionchanged:: 3.13 ++ Add *strict* optional parameter and reject malformed inputs by default. + .. function:: formataddr(pair, charset='utf-8') -@@ -89,7 +94,7 @@ of the new API. +@@ -84,12 +89,15 @@ of the new API. + Added the *charset* option. + + +-.. function:: getaddresses(fieldvalues) ++.. function:: getaddresses(fieldvalues, *, strict=True) + This method returns a list of 2-tuples of the form returned by ``parseaddr()``. *fieldvalues* is a sequence of header field values as might be returned by - :meth:`Message.get_all `. Here's a simple +- :meth:`Message.get_all `. Here's a simple - example that gets all the recipients of a message:: -+ example that gets all the recipients of a message: ++ :meth:`Message.get_all `. ++ ++ If *strict* is true, use a strict parser which rejects malformed inputs. ++ ++ Here's a simple example that gets all the recipients of a message:: from email.utils import getaddresses -@@ -99,6 +104,25 @@ of the new API. +@@ -99,6 +107,9 @@ of the new API. resent_ccs = msg.get_all('resent-cc', []) all_recipients = getaddresses(tos + ccs + resent_tos + resent_ccs) -+ When parsing fails for a single fieldvalue, a 2-tuple of ``('', '')`` -+ is returned in its place. Other errors in parsing the list of -+ addresses such as a fieldvalue seemingly parsing into multiple -+ addresses may result in a list containing a single empty 2-tuple -+ ``[('', '')]`` being returned rather than returning potentially -+ invalid output. -+ -+ Example malformed input parsing: -+ -+ .. doctest:: -+ -+ >>> from email.utils import getaddresses -+ >>> getaddresses(['alice@example.com ', 'me@example.com']) -+ [('', '')] -+ -+ .. versionchanged:: 3.12 -+ The 2-tuple of ``('', '')`` in the returned values when parsing -+ fails were added as to address a security issue. ++ .. versionchanged:: 3.13 ++ Add *strict* optional parameter and reject malformed inputs by default. + .. function:: parsedate(date) -Index: Python-3.11.4/Lib/email/utils.py -=================================================================== ---- Python-3.11.4.orig/Lib/email/utils.py -+++ Python-3.11.4/Lib/email/utils.py -@@ -106,12 +106,54 @@ def formataddr(pair, charset='utf-8'): +--- a/Lib/email/utils.py ++++ b/Lib/email/utils.py +@@ -48,6 +48,7 @@ TICK = "'" + specialsre = re.compile(r'[][\\()<>@,:;".]') + escapesre = re.compile(r'[\\"]') + ++ + def _has_surrogates(s): + """Return True if s contains surrogate-escaped binary data.""" + # This check is based on the fact that unless there are surrogates, utf8 +@@ -106,12 +107,127 @@ def formataddr(pair, charset='utf-8'): return address ++def _iter_escaped_chars(addr): ++ pos = 0 ++ escape = False ++ for pos, ch in enumerate(addr): ++ if escape: ++ yield (pos, '\\' + ch) ++ escape = False ++ elif ch == '\\': ++ escape = True ++ else: ++ yield (pos, ch) ++ if escape: ++ yield (pos, '\\') ++ ++ ++def _strip_quoted_realnames(addr): ++ """Strip real names between quotes.""" ++ if '"' not in addr: ++ # Fast path ++ return addr ++ ++ start = 0 ++ open_pos = None ++ result = [] ++ for pos, ch in _iter_escaped_chars(addr): ++ if ch == '"': ++ if open_pos is None: ++ open_pos = pos ++ else: ++ if start != open_pos: ++ result.append(addr[start:open_pos]) ++ start = pos + 1 ++ open_pos = None + +-def getaddresses(fieldvalues): +- """Return a list of (REALNAME, EMAIL) for each fieldvalue.""" +- all = COMMASPACE.join(str(v) for v in fieldvalues) +- a = _AddressList(all) +- return a.addresslist ++ if start < len(addr): ++ result.append(addr[start:]) ++ ++ return ''.join(result) ++ ++ ++supports_strict_parsing = True ++ ++def getaddresses(fieldvalues, *, strict=True): ++ """Return a list of (REALNAME, EMAIL) or ('','') for each fieldvalue. ++ ++ When parsing fails for a fieldvalue, a 2-tuple of ('', '') is returned in ++ its place. ++ ++ If strict is true, use a strict parser which rejects malformed inputs. ++ """ ++ ++ # If strict is true, if the resulting list of parsed addresses is greater ++ # than the number of fieldvalues in the input list, a parsing error has ++ # occurred and consequently a list containing a single empty 2-tuple [('', ++ # '')] is returned in its place. This is done to avoid invalid output. ++ # ++ # Malformed input: getaddresses(['alice@example.com ']) ++ # Invalid output: [('', 'alice@example.com'), ('', 'bob@example.com')] ++ # Safe output: [('', '')] ++ ++ if not strict: ++ all = COMMASPACE.join(str(v) for v in fieldvalues) ++ a = _AddressList(all) ++ return a.addresslist ++ ++ fieldvalues = [str(v) for v in fieldvalues] ++ fieldvalues = _pre_parse_validation(fieldvalues) ++ addr = COMMASPACE.join(fieldvalues) ++ a = _AddressList(addr) ++ result = _post_parse_validation(a.addresslist) ++ ++ # Treat output as invalid if the number of addresses is not equal to the ++ # expected number of addresses. ++ n = 0 ++ for v in fieldvalues: ++ # When a comma is used in the Real Name part it is not a deliminator. ++ # So strip those out before counting the commas. ++ v = _strip_quoted_realnames(v) ++ # Expected number of addresses: 1 + number of commas ++ n += 1 + v.count(',') ++ if len(result) != n: ++ return [('', '')] ++ ++ return result ++ ++ ++def _check_parenthesis(addr): ++ # Ignore parenthesis in quoted real names. ++ addr = _strip_quoted_realnames(addr) ++ ++ opens = 0 ++ for pos, ch in _iter_escaped_chars(addr): ++ if ch == '(': ++ opens += 1 ++ elif ch == ')': ++ opens -= 1 ++ if opens < 0: ++ return False ++ return (opens == 0) ++ ++ +def _pre_parse_validation(email_header_fields): + accepted_values = [] + for v in email_header_fields: -+ s = v.replace('\\(', '').replace('\\)', '') -+ if s.count('(') != s.count(')'): ++ if not _check_parenthesis(v): + v = "('', '')" + accepted_values.append(v) + @@ -85,46 +196,32 @@ Index: Python-3.11.4/Lib/email/utils.py + accepted_values.append(v) + + return accepted_values -+ - - def getaddresses(fieldvalues): -- """Return a list of (REALNAME, EMAIL) for each fieldvalue.""" -- all = COMMASPACE.join(str(v) for v in fieldvalues) -+ """Return a list of (REALNAME, EMAIL) or ('','') for each fieldvalue. -+ -+ When parsing fails for a fieldvalue, a 2-tuple of ('', '') is returned in -+ its place. -+ -+ If the resulting list of parsed address is not the same as the number of -+ fieldvalues in the input list a parsing error has occurred. A list -+ containing a single empty 2-tuple [('', '')] is returned in its place. -+ This is done to avoid invalid output. -+ """ -+ fieldvalues = [str(v) for v in fieldvalues] -+ fieldvalues = _pre_parse_validation(fieldvalues) -+ all = COMMASPACE.join(v for v in fieldvalues) - a = _AddressList(all) -- return a.addresslist -+ result = _post_parse_validation(a.addresslist) -+ -+ n = 0 -+ for v in fieldvalues: -+ n += v.count(',') + 1 -+ -+ if len(result) != n: -+ return [('', '')] -+ -+ return result def _format_timetuple_and_zone(timetuple, zone): -@@ -212,9 +254,18 @@ def parseaddr(addr): +@@ -205,16 +321,33 @@ def parsedate_to_datetime(data): + tzinfo=datetime.timezone(datetime.timedelta(seconds=tz))) + + +-def parseaddr(addr): ++def parseaddr(addr, *, strict=True): + """ + Parse addr into its constituent realname and email address parts. + Return a tuple of realname and email address, unless the parse fails, in which case return a 2-tuple of ('', ''). ++ ++ If strict is True, use a strict parser which rejects malformed inputs. """ - addrs = _AddressList(addr).addresslist - if not addrs: - return '', '' ++ if not strict: ++ addrs = _AddressList(addr).addresslist ++ if not addrs: ++ return ('', '') ++ return addrs[0] ++ + if isinstance(addr, list): + addr = addr[0] + @@ -140,110 +237,225 @@ Index: Python-3.11.4/Lib/email/utils.py return addrs[0] -Index: Python-3.11.4/Lib/test/test_email/test_email.py -=================================================================== ---- Python-3.11.4.orig/Lib/test/test_email/test_email.py -+++ Python-3.11.4/Lib/test/test_email/test_email.py -@@ -3320,15 +3320,90 @@ Foo +--- a/Lib/test/test_email/test_email.py ++++ b/Lib/test/test_email/test_email.py +@@ -17,6 +17,7 @@ from unittest.mock import patch + + import email + import email.policy ++import email.utils + + from email.charset import Charset + from email.generator import Generator, DecodedGenerator, BytesGenerator +@@ -3321,15 +3322,137 @@ Foo [('Al Person', 'aperson@dom.ain'), ('Bud Person', 'bperson@dom.ain')]) -+ def test_getaddresses_parsing_errors(self): -+ """Test for parsing errors from CVE-2023-27043""" -+ eq = self.assertEqual -+ eq(utils.getaddresses(['alice@example.org(']), -+ [('', '')]) -+ eq(utils.getaddresses(['alice@example.org)']), -+ [('', '')]) -+ eq(utils.getaddresses(['alice@example.org<']), -+ [('', '')]) -+ eq(utils.getaddresses(['alice@example.org>']), -+ [('', '')]) -+ eq(utils.getaddresses(['alice@example.org@']), -+ [('', '')]) -+ eq(utils.getaddresses(['alice@example.org,']), -+ [('', 'alice@example.org'), ('', 'bob@example.com')]) -+ eq(utils.getaddresses(['alice@example.org;']), -+ [('', '')]) -+ eq(utils.getaddresses(['alice@example.org:']), -+ [('', '')]) -+ eq(utils.getaddresses(['alice@example.org.']), -+ [('', '')]) -+ eq(utils.getaddresses(['alice@example.org"']), -+ [('', '')]) -+ eq(utils.getaddresses(['alice@example.org[']), -+ [('', '')]) -+ eq(utils.getaddresses(['alice@example.org]']), -+ [('', '')]) ++ def test_parsing_errors(self): ++ """Test for parsing errors from CVE-2023-27043 and CVE-2019-16056""" ++ alice = 'alice@example.org' ++ bob = 'bob@example.com' ++ empty = ('', '') + -+ def test_parseaddr_parsing_errors(self): -+ """Test for parsing errors from CVE-2023-27043""" -+ eq = self.assertEqual -+ eq(utils.parseaddr(['alice@example.org(']), -+ ('', '')) -+ eq(utils.parseaddr(['alice@example.org)']), -+ ('', '')) -+ eq(utils.parseaddr(['alice@example.org<']), -+ ('', '')) -+ eq(utils.parseaddr(['alice@example.org>']), -+ ('', '')) -+ eq(utils.parseaddr(['alice@example.org@']), -+ ('', '')) -+ eq(utils.parseaddr(['alice@example.org,']), -+ ('', '')) -+ eq(utils.parseaddr(['alice@example.org;']), -+ ('', '')) -+ eq(utils.parseaddr(['alice@example.org:']), -+ ('', '')) -+ eq(utils.parseaddr(['alice@example.org.']), -+ ('', '')) -+ eq(utils.parseaddr(['alice@example.org"']), -+ ('', '')) -+ eq(utils.parseaddr(['alice@example.org[']), -+ ('', '')) -+ eq(utils.parseaddr(['alice@example.org]']), -+ ('', '')) ++ # Test utils.getaddresses() and utils.parseaddr() on malformed email ++ # addresses: default behavior (strict=True) rejects malformed address, ++ # and strict=False which tolerates malformed address. ++ for invalid_separator, expected_non_strict in ( ++ ('(', [(f'<{bob}>', alice)]), ++ (')', [('', alice), empty, ('', bob)]), ++ ('<', [('', alice), empty, ('', bob), empty]), ++ ('>', [('', alice), empty, ('', bob)]), ++ ('[', [('', f'{alice}[<{bob}>]')]), ++ (']', [('', alice), empty, ('', bob)]), ++ ('@', [empty, empty, ('', bob)]), ++ (';', [('', alice), empty, ('', bob)]), ++ (':', [('', alice), ('', bob)]), ++ ('.', [('', alice + '.'), ('', bob)]), ++ ('"', [('', alice), ('', f'<{bob}>')]), ++ ): ++ address = f'{alice}{invalid_separator}<{bob}>' ++ with self.subTest(address=address): ++ self.assertEqual(utils.getaddresses([address]), ++ [empty]) ++ self.assertEqual(utils.getaddresses([address], strict=False), ++ expected_non_strict) ++ ++ self.assertEqual(utils.parseaddr([address]), ++ empty) ++ self.assertEqual(utils.parseaddr([address], strict=False), ++ ('', address)) ++ ++ # Comma (',') is treated differently depending on strict parameter. ++ # Comma without quotes. ++ address = f'{alice},<{bob}>' ++ self.assertEqual(utils.getaddresses([address]), ++ [('', alice), ('', bob)]) ++ self.assertEqual(utils.getaddresses([address], strict=False), ++ [('', alice), ('', bob)]) ++ self.assertEqual(utils.parseaddr([address]), ++ empty) ++ self.assertEqual(utils.parseaddr([address], strict=False), ++ ('', address)) ++ ++ # Real name between quotes containing comma. ++ address = '"Alice, alice@example.org" ' ++ expected_strict = ('Alice, alice@example.org', 'bob@example.com') ++ self.assertEqual(utils.getaddresses([address]), [expected_strict]) ++ self.assertEqual(utils.getaddresses([address], strict=False), [expected_strict]) ++ self.assertEqual(utils.parseaddr([address]), expected_strict) ++ self.assertEqual(utils.parseaddr([address], strict=False), ++ ('', address)) ++ ++ # Valid parenthesis in comments. ++ address = 'alice@example.org (Alice)' ++ expected_strict = ('Alice', 'alice@example.org') ++ self.assertEqual(utils.getaddresses([address]), [expected_strict]) ++ self.assertEqual(utils.getaddresses([address], strict=False), [expected_strict]) ++ self.assertEqual(utils.parseaddr([address]), expected_strict) ++ self.assertEqual(utils.parseaddr([address], strict=False), ++ ('', address)) ++ ++ # Invalid parenthesis in comments. ++ address = 'alice@example.org )Alice(' ++ self.assertEqual(utils.getaddresses([address]), [empty]) ++ self.assertEqual(utils.getaddresses([address], strict=False), ++ [('', 'alice@example.org'), ('', ''), ('', 'Alice')]) ++ self.assertEqual(utils.parseaddr([address]), empty) ++ self.assertEqual(utils.parseaddr([address], strict=False), ++ ('', address)) ++ ++ # Two addresses with quotes separated by comma. ++ address = '"Jane Doe" , "John Doe" ' ++ self.assertEqual(utils.getaddresses([address]), ++ [('Jane Doe', 'jane@example.net'), ++ ('John Doe', 'john@example.net')]) ++ self.assertEqual(utils.getaddresses([address], strict=False), ++ [('Jane Doe', 'jane@example.net'), ++ ('John Doe', 'john@example.net')]) ++ self.assertEqual(utils.parseaddr([address]), empty) ++ self.assertEqual(utils.parseaddr([address], strict=False), ++ ('', address)) ++ ++ # Test email.utils.supports_strict_parsing attribute ++ self.assertEqual(email.utils.supports_strict_parsing, True) + def test_getaddresses_nasty(self): - eq = self.assertEqual - eq(utils.getaddresses(['foo: ;']), [('', '')]) +- eq = self.assertEqual +- eq(utils.getaddresses(['foo: ;']), [('', '')]) - eq(utils.getaddresses( - ['[]*-- =~$']), - [('', ''), ('', ''), ('', '*--')]) -+ eq(utils.getaddresses(['[]*-- =~$']), [('', '')]) - eq(utils.getaddresses( - ['foo: ;', '"Jason R. Mastaler" ']), - [('', ''), ('Jason R. Mastaler', 'jason@dom.ain')]) -+ eq(utils.getaddresses( -+ [r'Pete(A nice \) chap) ']), -+ [('Pete (A nice ) chap his account his host)', 'pete@silly.test')]) -+ eq(utils.getaddresses( -+ ['(Empty list)(start)Undisclosed recipients :(nobody(I know))']), -+ [('', '')]) -+ eq(utils.getaddresses( -+ ['Mary <@machine.tld:mary@example.net>, , jdoe@test . example']), -+ [('Mary', 'mary@example.net'), ('', ''), ('', 'jdoe@test.example')]) -+ eq(utils.getaddresses( -+ ['John Doe ']), -+ [('John Doe (comment)', 'jdoe@machine.example')]) -+ eq(utils.getaddresses( -+ ['"Mary Smith: Personal Account" ']), -+ [('Mary Smith: Personal Account', 'smith@home.example')]) -+ eq(utils.getaddresses( -+ ['Undisclosed recipients:;']), -+ [('', '')]) -+ eq(utils.getaddresses( -+ [r', "Giant; \"Big\" Box" ']), -+ [('', 'boss@nil.test'), ('Giant; "Big" Box', 'bob@example.net')]) +- eq(utils.getaddresses( +- ['foo: ;', '"Jason R. Mastaler" ']), +- [('', ''), ('Jason R. Mastaler', 'jason@dom.ain')]) ++ for addresses, expected in ( ++ (['"Sürname, Firstname" '], ++ [('Sürname, Firstname', 'to@example.com')]), ++ ++ (['foo: ;'], ++ [('', '')]), ++ ++ (['foo: ;', '"Jason R. Mastaler" '], ++ [('', ''), ('Jason R. Mastaler', 'jason@dom.ain')]), ++ ++ ([r'Pete(A nice \) chap) '], ++ [('Pete (A nice ) chap his account his host)', 'pete@silly.test')]), ++ ++ (['(Empty list)(start)Undisclosed recipients :(nobody(I know))'], ++ [('', '')]), ++ ++ (['Mary <@machine.tld:mary@example.net>, , jdoe@test . example'], ++ [('Mary', 'mary@example.net'), ('', ''), ('', 'jdoe@test.example')]), ++ ++ (['John Doe '], ++ [('John Doe (comment)', 'jdoe@machine.example')]), ++ ++ (['"Mary Smith: Personal Account" '], ++ [('Mary Smith: Personal Account', 'smith@home.example')]), ++ ++ (['Undisclosed recipients:;'], ++ [('', '')]), ++ ++ ([r', "Giant; \"Big\" Box" '], ++ [('', 'boss@nil.test'), ('Giant; "Big" Box', 'bob@example.net')]), ++ ): ++ with self.subTest(addresses=addresses): ++ self.assertEqual(utils.getaddresses(addresses), ++ expected) ++ self.assertEqual(utils.getaddresses(addresses, strict=False), ++ expected) ++ ++ addresses = ['[]*-- =~$'] ++ self.assertEqual(utils.getaddresses(addresses), ++ [('', '')]) ++ self.assertEqual(utils.getaddresses(addresses, strict=False), ++ [('', ''), ('', ''), ('', '*--')]) def test_getaddresses_embedded_comment(self): """Test proper handling of a nested comment""" -Index: Python-3.11.4/Misc/NEWS.d/next/Security/2023-06-13-20-52-24.gh-issue-102988.Kei7Vf.rst -=================================================================== +@@ -3520,6 +3643,54 @@ multipart/report + m = cls(*constructor, policy=email.policy.default) + self.assertIs(m.policy, email.policy.default) + ++ def test_iter_escaped_chars(self): ++ self.assertEqual(list(utils._iter_escaped_chars(r'a\\b\"c\\"d')), ++ [(0, 'a'), ++ (2, '\\\\'), ++ (3, 'b'), ++ (5, '\\"'), ++ (6, 'c'), ++ (8, '\\\\'), ++ (9, '"'), ++ (10, 'd')]) ++ self.assertEqual(list(utils._iter_escaped_chars('a\\')), ++ [(0, 'a'), (1, '\\')]) ++ ++ def test_strip_quoted_realnames(self): ++ def check(addr, expected): ++ self.assertEqual(utils._strip_quoted_realnames(addr), expected) ++ ++ check('"Jane Doe" , "John Doe" ', ++ ' , ') ++ check(r'"Jane \"Doe\"." ', ++ ' ') ++ ++ # special cases ++ check(r'before"name"after', 'beforeafter') ++ check(r'before"name"', 'before') ++ check(r'b"name"', 'b') # single char ++ check(r'"name"after', 'after') ++ check(r'"name"a', 'a') # single char ++ check(r'"name"', '') ++ ++ # no change ++ for addr in ( ++ 'Jane Doe , John Doe ', ++ 'lone " quote', ++ ): ++ self.assertEqual(utils._strip_quoted_realnames(addr), addr) ++ ++ ++ def test_check_parenthesis(self): ++ addr = 'alice@example.net' ++ self.assertTrue(utils._check_parenthesis(f'{addr} (Alice)')) ++ self.assertFalse(utils._check_parenthesis(f'{addr} )Alice(')) ++ self.assertFalse(utils._check_parenthesis(f'{addr} (Alice))')) ++ self.assertFalse(utils._check_parenthesis(f'{addr} ((Alice)')) ++ ++ # Ignore real name between quotes ++ self.assertTrue(utils._check_parenthesis(f'")Alice((" {addr}')) ++ + + # Test the iterator/generators + class TestIterators(TestEmailBase): --- /dev/null -+++ Python-3.11.4/Misc/NEWS.d/next/Security/2023-06-13-20-52-24.gh-issue-102988.Kei7Vf.rst -@@ -0,0 +1,4 @@ -+CVE-2023-27043: Prevent :func:`email.utils.parseaddr` -+and :func:`email.utils.getaddresses` from returning the realname portion of an -+invalid RFC2822 email header in the email address portion of the 2-tuple -+returned after being parsed by :class:`email._parseaddr.AddressList`. ++++ b/Misc/NEWS.d/next/Library/2023-10-20-15-28-08.gh-issue-102988.dStNO7.rst +@@ -0,0 +1,8 @@ ++:func:`email.utils.getaddresses` and :func:`email.utils.parseaddr` now ++return ``('', '')`` 2-tuples in more situations where invalid email ++addresses are encountered instead of potentially inaccurate values. Add ++optional *strict* parameter to these two functions: use ``strict=False`` to ++get the old behavior, accept malformed inputs. ++``getattr(email.utils, 'supports_strict_parsing', False)`` can be use to check ++if the *strict* paramater is available. Patch by Thomas Dwyer and Victor ++Stinner to improve the CVE-2023-27043 fix. diff --git a/Revert-gh105127-left-tests.patch b/Revert-gh105127-left-tests.patch deleted file mode 100644 index 87ce40a..0000000 --- a/Revert-gh105127-left-tests.patch +++ /dev/null @@ -1,283 +0,0 @@ -From 4288c623d62cf90d8e4444facb3379fb06d01140 Mon Sep 17 00:00:00 2001 -From: "Gregory P. Smith" -Date: Thu, 20 Jul 2023 20:30:52 -0700 -Subject: [PATCH] [3.12] gh-106669: Revert "gh-102988: Detect email address - parsing errors ... (GH-105127)" (GH-106733) - -This reverts commit 18dfbd035775c15533d13a98e56b1d2bf5c65f00. -Adds a regression test from the issue. - -See https://github.com/python/cpython/issues/106669.. -(cherry picked from commit a31dea1feb61793e48fa9aa5014f358352205c1d) - -Co-authored-by: Gregory P. Smith ---- - Doc/library/email.utils.rst | 26 -- - Lib/email/utils.py | 63 ------ - Lib/test/test_email/test_email.py | 96 +--------- - Misc/NEWS.d/next/Security/2023-06-13-20-52-24.gh-issue-102988.Kei7Vf.rst | 5 - 4 files changed, 31 insertions(+), 159 deletions(-) - create mode 100644 Misc/NEWS.d/next/Security/2023-06-13-20-52-24.gh-issue-102988.Kei7Vf.rst - -Index: Python-3.11.4/Doc/library/email.utils.rst -=================================================================== ---- Python-3.11.4.orig/Doc/library/email.utils.rst -+++ Python-3.11.4/Doc/library/email.utils.rst -@@ -67,11 +67,6 @@ of the new API. - *email address* parts. Returns a tuple of that information, unless the parse - fails, in which case a 2-tuple of ``('', '')`` is returned. - -- .. versionchanged:: 3.12 -- For security reasons, addresses that were ambiguous and could parse into -- multiple different addresses now cause ``('', '')`` to be returned -- instead of only one of the *potential* addresses. -- - - .. function:: formataddr(pair, charset='utf-8') - -@@ -94,7 +89,7 @@ of the new API. - This method returns a list of 2-tuples of the form returned by ``parseaddr()``. - *fieldvalues* is a sequence of header field values as might be returned by - :meth:`Message.get_all `. Here's a simple -- example that gets all the recipients of a message: -+ example that gets all the recipients of a message:: - - from email.utils import getaddresses - -@@ -104,25 +99,6 @@ of the new API. - resent_ccs = msg.get_all('resent-cc', []) - all_recipients = getaddresses(tos + ccs + resent_tos + resent_ccs) - -- When parsing fails for a single fieldvalue, a 2-tuple of ``('', '')`` -- is returned in its place. Other errors in parsing the list of -- addresses such as a fieldvalue seemingly parsing into multiple -- addresses may result in a list containing a single empty 2-tuple -- ``[('', '')]`` being returned rather than returning potentially -- invalid output. -- -- Example malformed input parsing: -- -- .. doctest:: -- -- >>> from email.utils import getaddresses -- >>> getaddresses(['alice@example.com ', 'me@example.com']) -- [('', '')] -- -- .. versionchanged:: 3.12 -- The 2-tuple of ``('', '')`` in the returned values when parsing -- fails were added as to address a security issue. -- - - .. function:: parsedate(date) - -Index: Python-3.11.4/Lib/email/utils.py -=================================================================== ---- Python-3.11.4.orig/Lib/email/utils.py -+++ Python-3.11.4/Lib/email/utils.py -@@ -106,54 +106,12 @@ def formataddr(pair, charset='utf-8'): - return address - - --def _pre_parse_validation(email_header_fields): -- accepted_values = [] -- for v in email_header_fields: -- s = v.replace('\\(', '').replace('\\)', '') -- if s.count('(') != s.count(')'): -- v = "('', '')" -- accepted_values.append(v) -- -- return accepted_values -- -- --def _post_parse_validation(parsed_email_header_tuples): -- accepted_values = [] -- # The parser would have parsed a correctly formatted domain-literal -- # The existence of an [ after parsing indicates a parsing failure -- for v in parsed_email_header_tuples: -- if '[' in v[1]: -- v = ('', '') -- accepted_values.append(v) -- -- return accepted_values -- - - def getaddresses(fieldvalues): -- """Return a list of (REALNAME, EMAIL) or ('','') for each fieldvalue. -- -- When parsing fails for a fieldvalue, a 2-tuple of ('', '') is returned in -- its place. -- -- If the resulting list of parsed address is not the same as the number of -- fieldvalues in the input list a parsing error has occurred. A list -- containing a single empty 2-tuple [('', '')] is returned in its place. -- This is done to avoid invalid output. -- """ -- fieldvalues = [str(v) for v in fieldvalues] -- fieldvalues = _pre_parse_validation(fieldvalues) -- all = COMMASPACE.join(v for v in fieldvalues) -+ """Return a list of (REALNAME, EMAIL) for each fieldvalue.""" -+ all = COMMASPACE.join(str(v) for v in fieldvalues) - a = _AddressList(all) -- result = _post_parse_validation(a.addresslist) -- -- n = 0 -- for v in fieldvalues: -- n += v.count(',') + 1 -- -- if len(result) != n: -- return [('', '')] -- -- return result -+ return a.addresslist - - - def _format_timetuple_and_zone(timetuple, zone): -@@ -254,18 +212,9 @@ def parseaddr(addr): - Return a tuple of realname and email address, unless the parse fails, in - which case return a 2-tuple of ('', ''). - """ -- if isinstance(addr, list): -- addr = addr[0] -- -- if not isinstance(addr, str): -- return ('', '') -- -- addr = _pre_parse_validation([addr])[0] -- addrs = _post_parse_validation(_AddressList(addr).addresslist) -- -- if not addrs or len(addrs) > 1: -- return ('', '') -- -+ addrs = _AddressList(addr).addresslist -+ if not addrs: -+ return '', '' - return addrs[0] - - -Index: Python-3.11.4/Lib/test/test_email/test_email.py -=================================================================== ---- Python-3.11.4.orig/Lib/test/test_email/test_email.py -+++ Python-3.11.4/Lib/test/test_email/test_email.py -@@ -3320,90 +3320,32 @@ Foo - [('Al Person', 'aperson@dom.ain'), - ('Bud Person', 'bperson@dom.ain')]) - -- def test_getaddresses_parsing_errors(self): -- """Test for parsing errors from CVE-2023-27043""" -- eq = self.assertEqual -- eq(utils.getaddresses(['alice@example.org(']), -- [('', '')]) -- eq(utils.getaddresses(['alice@example.org)']), -- [('', '')]) -- eq(utils.getaddresses(['alice@example.org<']), -- [('', '')]) -- eq(utils.getaddresses(['alice@example.org>']), -- [('', '')]) -- eq(utils.getaddresses(['alice@example.org@']), -- [('', '')]) -- eq(utils.getaddresses(['alice@example.org,']), -- [('', 'alice@example.org'), ('', 'bob@example.com')]) -- eq(utils.getaddresses(['alice@example.org;']), -- [('', '')]) -- eq(utils.getaddresses(['alice@example.org:']), -- [('', '')]) -- eq(utils.getaddresses(['alice@example.org.']), -- [('', '')]) -- eq(utils.getaddresses(['alice@example.org"']), -- [('', '')]) -- eq(utils.getaddresses(['alice@example.org[']), -- [('', '')]) -- eq(utils.getaddresses(['alice@example.org]']), -- [('', '')]) -- -- def test_parseaddr_parsing_errors(self): -- """Test for parsing errors from CVE-2023-27043""" -- eq = self.assertEqual -- eq(utils.parseaddr(['alice@example.org(']), -- ('', '')) -- eq(utils.parseaddr(['alice@example.org)']), -- ('', '')) -- eq(utils.parseaddr(['alice@example.org<']), -- ('', '')) -- eq(utils.parseaddr(['alice@example.org>']), -- ('', '')) -- eq(utils.parseaddr(['alice@example.org@']), -- ('', '')) -- eq(utils.parseaddr(['alice@example.org,']), -- ('', '')) -- eq(utils.parseaddr(['alice@example.org;']), -- ('', '')) -- eq(utils.parseaddr(['alice@example.org:']), -- ('', '')) -- eq(utils.parseaddr(['alice@example.org.']), -- ('', '')) -- eq(utils.parseaddr(['alice@example.org"']), -- ('', '')) -- eq(utils.parseaddr(['alice@example.org[']), -- ('', '')) -- eq(utils.parseaddr(['alice@example.org]']), -- ('', '')) -+ def test_getaddresses_comma_in_name(self): -+ """GH-106669 regression test.""" -+ self.assertEqual( -+ utils.getaddresses( -+ [ -+ '"Bud, Person" ', -+ 'aperson@dom.ain (Al Person)', -+ '"Mariusz Felisiak" ', -+ ] -+ ), -+ [ -+ ('Bud, Person', 'bperson@dom.ain'), -+ ('Al Person', 'aperson@dom.ain'), -+ ('Mariusz Felisiak', 'to@example.com'), -+ ], -+ ) - - def test_getaddresses_nasty(self): - eq = self.assertEqual - eq(utils.getaddresses(['foo: ;']), [('', '')]) -- eq(utils.getaddresses(['[]*-- =~$']), [('', '')]) -+ eq(utils.getaddresses( -+ ['[]*-- =~$']), -+ [('', ''), ('', ''), ('', '*--')]) - eq(utils.getaddresses( - ['foo: ;', '"Jason R. Mastaler" ']), - [('', ''), ('Jason R. Mastaler', 'jason@dom.ain')]) -- eq(utils.getaddresses( -- [r'Pete(A nice \) chap) ']), -- [('Pete (A nice ) chap his account his host)', 'pete@silly.test')]) -- eq(utils.getaddresses( -- ['(Empty list)(start)Undisclosed recipients :(nobody(I know))']), -- [('', '')]) -- eq(utils.getaddresses( -- ['Mary <@machine.tld:mary@example.net>, , jdoe@test . example']), -- [('Mary', 'mary@example.net'), ('', ''), ('', 'jdoe@test.example')]) -- eq(utils.getaddresses( -- ['John Doe ']), -- [('John Doe (comment)', 'jdoe@machine.example')]) -- eq(utils.getaddresses( -- ['"Mary Smith: Personal Account" ']), -- [('Mary Smith: Personal Account', 'smith@home.example')]) -- eq(utils.getaddresses( -- ['Undisclosed recipients:;']), -- [('', '')]) -- eq(utils.getaddresses( -- [r', "Giant; \"Big\" Box" ']), -- [('', 'boss@nil.test'), ('Giant; "Big" Box', 'bob@example.net')]) - - def test_getaddresses_embedded_comment(self): - """Test proper handling of a nested comment""" -Index: Python-3.11.4/Misc/NEWS.d/next/Security/2023-06-13-20-52-24.gh-issue-102988.Kei7Vf.rst -=================================================================== ---- Python-3.11.4.orig/Misc/NEWS.d/next/Security/2023-06-13-20-52-24.gh-issue-102988.Kei7Vf.rst -+++ Python-3.11.4/Misc/NEWS.d/next/Security/2023-06-13-20-52-24.gh-issue-102988.Kei7Vf.rst -@@ -1,3 +1,8 @@ -+Reverted the :mod:`email.utils` security improvement change released in -+3.12beta4 that unintentionally caused :mod:`email.utils.getaddresses` to fail -+to parse email addresses with a comma in the quoted name field. -+See :gh:`106669`. -+ - CVE-2023-27043: Prevent :func:`email.utils.parseaddr` - and :func:`email.utils.getaddresses` from returning the realname portion of an - invalid RFC2822 email header in the email address portion of the 2-tuple diff --git a/python311.changes b/python311.changes index 944ef78..fde797a 100644 --- a/python311.changes +++ b/python311.changes @@ -1,3 +1,11 @@ +------------------------------------------------------------------- +Mon Dec 18 16:20:58 UTC 2023 - Matej Cepl + +- Refresh CVE-2023-27043-email-parsing-errors.patch to + gh#python/cpython!111116, fixing bsc#1210638 (CVE-2023-27043). +- Thus we can remove Revert-gh105127-left-tests.patch, which is + now useless. + ------------------------------------------------------------------- Wed Nov 15 09:06:16 UTC 2023 - Daniel Garcia diff --git a/python311.spec b/python311.spec index 05e3990..53b1560 100644 --- a/python311.spec +++ b/python311.spec @@ -165,9 +165,6 @@ Patch39: skip_if_buildbot-extend.patch # Detect email address parsing errors and return empty tuple to # indicate the parsing error (old API) Patch40: CVE-2023-27043-email-parsing-errors.patch -# PATCH-FIX-UPSTREAM Revert-gh105127-left-tests.patch bsc#1210638 mcepl@suse.com -# Partially revert previous patch -Patch41: Revert-gh105127-left-tests.patch BuildRequires: autoconf-archive BuildRequires: automake BuildRequires: fdupes @@ -428,7 +425,6 @@ other applications. %patch36 -p1 %patch39 -p1 %patch40 -p1 -%patch41 -p1 # drop Autoconf version requirement sed -i 's/^AC_PREREQ/dnl AC_PREREQ/' configure.ac