diff --git a/.python3.11.checksum b/.python3.11.checksum
new file mode 100644
index 0000000000000000000000000000000000000000..aadcaae93b2c63a2711248b93f13f9fbc23a89f3
--- /dev/null
+++ b/.python3.11.checksum
@@ -0,0 +1 @@
+1d78875a13fabf6d44fc113db389ff8d9936f0a9d00613c60a149814f5e9357d
diff --git a/.python3.11.metadata b/.python3.11.metadata
index 0e4f954e6f6cc1a17446f8e458592d5128848df2..1b385f804ba9f5a335c5f098e4f7419783661d5f 100644
--- a/.python3.11.metadata
+++ b/.python3.11.metadata
@@ -1,2 +1,2 @@
-ae1c199ecb7a969588b15354e19e7b60cb65d1b9 SOURCES/Python-3.11.2.tar.xz
-befe131eceffa73877ba3adceca3b7b1da1d2319 SOURCES/pgp_keys.asc
+2f0e409df2ab57aa9fc4cbddfb976af44e4e55bf6f619eee6bc5c2297264a7f6 SOURCES/Python-3.11.4.tar.xz
+fb28243ffeb9725b14b60586a9a123682a89604c025b7a9d4bcdeb67078203c6 SOURCES/pgp_keys.asc
diff --git a/SOURCES/00397-tarfile-filter.patch b/SOURCES/00397-tarfile-filter.patch
new file mode 100644
index 0000000000000000000000000000000000000000..bd8f98f853aab10b17edb2a7c6c2e2e6e6c23e6f
--- /dev/null
+++ b/SOURCES/00397-tarfile-filter.patch
@@ -0,0 +1,361 @@
+From f36519078bde3cce4328c03fffccb846121fb5bc Mon Sep 17 00:00:00 2001
+From: Petr Viktorin <encukou@gmail.com>
+Date: Wed, 9 Aug 2023 20:23:03 +0200
+Subject: [PATCH] Fix symlink handling for tarfile.data_filter
+
+---
+ Doc/library/tarfile.rst  |  5 +++++
+ Lib/tarfile.py           |  9 ++++++++-
+ Lib/test/test_tarfile.py | 26 ++++++++++++++++++++++++--
+ 3 files changed, 37 insertions(+), 3 deletions(-)
+
+diff --git a/Doc/library/tarfile.rst b/Doc/library/tarfile.rst
+index 00f3070324e..e0511bfeb64 100644
+--- a/Doc/library/tarfile.rst
++++ b/Doc/library/tarfile.rst
+@@ -740,6 +740,11 @@ A ``TarInfo`` object has the following public data attributes:
+    Name of the target file name, which is only present in :class:`TarInfo` objects
+    of type :const:`LNKTYPE` and :const:`SYMTYPE`.
+ 
++   For symbolic links (``SYMTYPE``), the linkname is relative to the directory
++   that contains the link.
++   For hard links (``LNKTYPE``), the linkname is relative to the root of
++   the archive.
++
+ 
+ .. attribute:: TarInfo.uid
+    :type: int
+diff --git a/Lib/tarfile.py b/Lib/tarfile.py
+index df4e41f7a0d..d62323715b4 100755
+--- a/Lib/tarfile.py
++++ b/Lib/tarfile.py
+@@ -802,7 +802,14 @@ def _get_filtered_attrs(member, dest_path, for_data=True):
+         if member.islnk() or member.issym():
+             if os.path.isabs(member.linkname):
+                 raise AbsoluteLinkError(member)
+-            target_path = os.path.realpath(os.path.join(dest_path, member.linkname))
++            if member.issym():
++                target_path = os.path.join(dest_path,
++                                           os.path.dirname(name),
++                                           member.linkname)
++            else:
++                target_path = os.path.join(dest_path,
++                                           member.linkname)
++            target_path = os.path.realpath(target_path)
+             if os.path.commonpath([target_path, dest_path]) != dest_path:
+                 raise LinkOutsideDestinationError(member, target_path)
+     return new_attrs
+diff --git a/Lib/test/test_tarfile.py b/Lib/test/test_tarfile.py
+index 2eda7fc4cea..79fc35c2895 100644
+--- a/Lib/test/test_tarfile.py
++++ b/Lib/test/test_tarfile.py
+@@ -3337,10 +3337,12 @@ def __exit__(self, *exc):
+         self.bio = None
+ 
+     def add(self, name, *, type=None, symlink_to=None, hardlink_to=None,
+-            mode=None, **kwargs):
++            mode=None, size=None, **kwargs):
+         """Add a member to the test archive. Call within `with`."""
+         name = str(name)
+         tarinfo = tarfile.TarInfo(name).replace(**kwargs)
++        if size is not None:
++            tarinfo.size = size
+         if mode:
+             tarinfo.mode = _filemode_to_int(mode)
+         if symlink_to is not None:
+@@ -3416,7 +3418,8 @@ def check_context(self, tar, filter):
+                 raise self.raised_exception
+             self.assertEqual(self.expected_paths, set())
+ 
+-    def expect_file(self, name, type=None, symlink_to=None, mode=None):
++    def expect_file(self, name, type=None, symlink_to=None, mode=None,
++                    size=None):
+         """Check a single file. See check_context."""
+         if self.raised_exception:
+             raise self.raised_exception
+@@ -3445,6 +3448,8 @@ def expect_file(self, name, type=None, symlink_to=None, mode=None):
+             self.assertTrue(path.is_fifo())
+         else:
+             raise NotImplementedError(type)
++        if size is not None:
++            self.assertEqual(path.stat().st_size, size)
+         for parent in path.parents:
+             self.expected_paths.discard(parent)
+ 
+@@ -3649,6 +3654,22 @@ def test_sly_relative2(self):
+                     + """['"].*moo['"], which is outside the """
+                     + "destination")
+ 
++    def test_deep_symlink(self):
++        with ArchiveMaker() as arc:
++            arc.add('targetdir/target', size=3)
++            arc.add('linkdir/hardlink', hardlink_to='targetdir/target')
++            arc.add('linkdir/symlink', symlink_to='../targetdir/target')
++
++        for filter in 'tar', 'data', 'fully_trusted':
++            with self.check_context(arc.open(), filter):
++                self.expect_file('targetdir/target', size=3)
++                self.expect_file('linkdir/hardlink', size=3)
++                if os_helper.can_symlink():
++                    self.expect_file('linkdir/symlink', size=3,
++                                     symlink_to='../targetdir/target')
++                else:
++                    self.expect_file('linkdir/symlink', size=3)
++
+     def test_modes(self):
+         # Test how file modes are extracted
+         # (Note that the modes are ignored on platforms without working chmod)
+-- 
+2.41.0
+
+From 8b70605b594b3831331a9340ba764ff751871612 Mon Sep 17 00:00:00 2001
+From: Petr Viktorin <encukou@gmail.com>
+Date: Mon, 6 Mar 2023 17:24:24 +0100
+Subject: [PATCH] CVE-2007-4559, PEP-706: Add filters for tarfile extraction
+ (downstream)
+
+Add and test RHEL-specific ways of configuring the default behavior: environment
+variable and config file.
+---
+ Lib/tarfile.py           |  42 +++++++++++++
+ Lib/test/test_shutil.py  |   3 +-
+ Lib/test/test_tarfile.py | 128 ++++++++++++++++++++++++++++++++++++++-
+ 3 files changed, 169 insertions(+), 4 deletions(-)
+
+diff --git a/Lib/tarfile.py b/Lib/tarfile.py
+index 130b5e0..3b7d8d5 100755
+--- a/Lib/tarfile.py
++++ b/Lib/tarfile.py
+@@ -72,6 +72,13 @@ __all__ = ["TarFile", "TarInfo", "is_tarfile", "TarError", "ReadError",
+            "ENCODING", "USTAR_FORMAT", "GNU_FORMAT", "PAX_FORMAT",
+            "DEFAULT_FORMAT", "open"]
+ 
++# If true, use the safer (but backwards-incompatible) 'tar' extraction filter,
++# rather than 'fully_trusted', by default.
++# The emitted warning is changed to match.
++_RH_SAFER_DEFAULT = True
++
++# System-wide configuration file
++_CONFIG_FILENAME = '/etc/python/tarfile.cfg'
+ 
+ #---------------------------------------------------------
+ # tar constants
+@@ -2211,6 +2218,41 @@ class TarFile(object):
+         if filter is None:
+             filter = self.extraction_filter
+             if filter is None:
++                name = os.environ.get('PYTHON_TARFILE_EXTRACTION_FILTER')
++                if name is None:
++                    try:
++                        file = bltn_open(_CONFIG_FILENAME)
++                    except FileNotFoundError:
++                        pass
++                    else:
++                        import configparser
++                        conf = configparser.ConfigParser(
++                            interpolation=None,
++                            comment_prefixes=('#', ),
++                        )
++                        with file:
++                            conf.read_file(file)
++                        name = conf.get('tarfile',
++                                        'PYTHON_TARFILE_EXTRACTION_FILTER',
++                                        fallback='')
++                if name:
++                    try:
++                        filter = _NAMED_FILTERS[name]
++                    except KeyError:
++                        raise ValueError(f"filter {filter!r} not found") from None
++                    self.extraction_filter = filter
++                    return filter
++                if _RH_SAFER_DEFAULT:
++                    warnings.warn(
++                        'The default behavior of tarfile extraction has been '
++                        + 'changed to disallow common exploits '
++                        + '(including CVE-2007-4559). '
++                        + 'By default, absolute/parent paths are disallowed '
++                        + 'and some mode bits are cleared. '
++                        + 'See https://access.redhat.com/articles/7004769 '
++                        + 'for more details.',
++                        RuntimeWarning)
++                    return tar_filter
+                 return fully_trusted_filter
+             if isinstance(filter, str):
+                 raise TypeError(
+diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py
+index 9bf4145..f247b82 100644
+--- a/Lib/test/test_shutil.py
++++ b/Lib/test/test_shutil.py
+@@ -1665,7 +1665,8 @@ class TestArchives(BaseTest, unittest.TestCase):
+     def check_unpack_tarball(self, format):
+         self.check_unpack_archive(format, filter='fully_trusted')
+         self.check_unpack_archive(format, filter='data')
+-        with warnings_helper.check_no_warnings(self):
++        with warnings_helper.check_warnings(
++                ('.*CVE-2007-4559', RuntimeWarning)):
+             self.check_unpack_archive(format)
+ 
+     def test_unpack_archive_tar(self):
+diff --git a/Lib/test/test_tarfile.py b/Lib/test/test_tarfile.py
+index cdea033..4724285 100644
+--- a/Lib/test/test_tarfile.py
++++ b/Lib/test/test_tarfile.py
+@@ -2,7 +2,7 @@ import sys
+ import os
+ import io
+ from hashlib import sha256
+-from contextlib import contextmanager
++from contextlib import contextmanager, ExitStack
+ from random import Random
+ import pathlib
+ import shutil
+@@ -2999,7 +2999,11 @@ class NoneInfoExtractTests(ReadTest):
+         tar = tarfile.open(tarname, mode='r', encoding="iso8859-1")
+         cls.control_dir = pathlib.Path(TEMPDIR) / "extractall_ctrl"
+         tar.errorlevel = 0
+-        tar.extractall(cls.control_dir, filter=cls.extraction_filter)
++        with ExitStack() as cm:
++            if cls.extraction_filter is None:
++                cm.enter_context(warnings.catch_warnings())
++                warnings.simplefilter(action="ignore", category=RuntimeWarning)
++            tar.extractall(cls.control_dir, filter=cls.extraction_filter)
+         tar.close()
+         cls.control_paths = set(
+             p.relative_to(cls.control_dir)
+@@ -3674,7 +3678,8 @@ class TestExtractionFilters(unittest.TestCase):
+         """Ensure the default filter does not warn (like in 3.12)"""
+         with ArchiveMaker() as arc:
+             arc.add('foo')
+-        with warnings_helper.check_no_warnings(self):
++        with warnings_helper.check_warnings(
++                ('.*CVE-2007-4559', RuntimeWarning)):
+             with self.check_context(arc.open(), None):
+                 self.expect_file('foo')
+ 
+@@ -3844,6 +3849,123 @@ class TestExtractionFilters(unittest.TestCase):
+             self.expect_exception(TypeError)  # errorlevel is not int
+ 
+ 
++    @contextmanager
++    def rh_config_context(self, config_lines=None):
++        """Set up for testing various ways of overriding the default filter
++
++        return a triple with:
++        - temporary directory
++        - EnvironmentVarGuard()
++        - a test archive for use with check_* methods below
++
++        If config_lines is given, write them to the config file. Otherwise
++        the config file is missing.
++        """
++        tempdir = pathlib.Path(TEMPDIR) / 'tmp'
++        configfile = tempdir / 'tarfile.cfg'
++        with ArchiveMaker() as arc:
++            arc.add('good')
++            arc.add('ugly', symlink_to='/etc/passwd')
++            arc.add('../bad')
++        with (
++                os_helper.temp_dir(tempdir),
++                support.swap_attr(tarfile, '_CONFIG_FILENAME', str(configfile)),
++                os_helper.EnvironmentVarGuard() as env,
++                arc.open() as tar,
++        ):
++            if config_lines is not None:
++                with configfile.open('w') as f:
++                    for line in config_lines:
++                        print(line, file=f)
++            yield tempdir, env, tar
++
++    def check_rh_default_behavior(self, tar, tempdir):
++        """Check RH default: warn and refuse to extract dangerous files."""
++        with (
++                warnings_helper.check_warnings(
++                    ('.*CVE-2007-4559', RuntimeWarning)),
++                self.assertRaises(tarfile.OutsideDestinationError),
++        ):
++            tar.extractall(tempdir / 'outdir')
++
++    def check_trusted_default(self, tar, tempdir):
++        """Check 'fully_trusted' is configured as the default filter."""
++        with (
++                warnings_helper.check_no_warnings(self),
++        ):
++            tar.extractall(tempdir / 'outdir')
++            self.assertTrue((tempdir / 'outdir/good').exists())
++            self.assertEqual((tempdir / 'outdir/ugly').readlink(),
++                             pathlib.Path('/etc/passwd'))
++            self.assertTrue((tempdir / 'bad').exists())
++
++    def test_rh_default_no_conf(self):
++        with self.rh_config_context() as (tempdir, env, tar):
++            self.check_rh_default_behavior(tar, tempdir)
++
++    def test_rh_default_from_file(self):
++        lines = ['[tarfile]', 'PYTHON_TARFILE_EXTRACTION_FILTER=fully_trusted']
++        with self.rh_config_context(lines) as (tempdir, env, tar):
++            self.check_trusted_default(tar, tempdir)
++
++    def test_rh_empty_config_file(self):
++        """Empty config file -> default behavior"""
++        lines = []
++        with self.rh_config_context(lines) as (tempdir, env, tar):
++            self.check_rh_default_behavior(tar, tempdir)
++
++    def test_empty_config_section(self):
++        """Empty section in config file -> default behavior"""
++        lines = ['[tarfile]']
++        with self.rh_config_context(lines) as (tempdir, env, tar):
++            self.check_rh_default_behavior(tar, tempdir)
++
++    def test_rh_default_empty_config_option(self):
++        """Empty option value in config file -> default behavior"""
++        lines = ['[tarfile]', 'PYTHON_TARFILE_EXTRACTION_FILTER=']
++        with self.rh_config_context(lines) as (tempdir, env, tar):
++            self.check_rh_default_behavior(tar, tempdir)
++
++    def test_bad_config_option(self):
++        """Bad option value in config file -> ValueError"""
++        lines = ['[tarfile]', 'PYTHON_TARFILE_EXTRACTION_FILTER=unknown!']
++        with self.rh_config_context(lines) as (tempdir, env, tar):
++            with self.assertRaises(ValueError):
++                tar.extractall(tempdir / 'outdir')
++
++    def test_default_from_envvar(self):
++        with self.rh_config_context() as (tempdir, env, tar):
++            env['PYTHON_TARFILE_EXTRACTION_FILTER'] = 'fully_trusted'
++            self.check_trusted_default(tar, tempdir)
++
++    def test_empty_envvar(self):
++        """Empty env variable -> default behavior"""
++        with self.rh_config_context() as (tempdir, env, tar):
++            env['PYTHON_TARFILE_EXTRACTION_FILTER'] = ''
++            self.check_rh_default_behavior(tar, tempdir)
++
++    def test_bad_envvar(self):
++        with self.rh_config_context() as (tempdir, env, tar):
++            env['PYTHON_TARFILE_EXTRACTION_FILTER'] = 'unknown!'
++            with self.assertRaises(ValueError):
++                tar.extractall(tempdir / 'outdir')
++
++    def test_envvar_overrides_file(self):
++        lines = ['[tarfile]', 'PYTHON_TARFILE_EXTRACTION_FILTER=data']
++        with self.rh_config_context(lines) as (tempdir, env, tar):
++            env['PYTHON_TARFILE_EXTRACTION_FILTER'] = 'fully_trusted'
++            self.check_trusted_default(tar, tempdir)
++
++    def test_monkeypatch_overrides_envvar(self):
++        with self.rh_config_context(None) as (tempdir, env, tar):
++            env['PYTHON_TARFILE_EXTRACTION_FILTER'] = 'data'
++            with support.swap_attr(
++                    tarfile.TarFile, 'extraction_filter',
++                    staticmethod(tarfile.fully_trusted_filter)
++            ):
++                self.check_trusted_default(tar, tempdir)
++
++
+ def setUpModule():
+     os_helper.unlink(TEMPDIR)
+     os.makedirs(TEMPDIR)
+-- 
+2.41.0
+
diff --git a/SOURCES/Python-3.11.4.tar.xz.asc b/SOURCES/Python-3.11.4.tar.xz.asc
new file mode 100644
index 0000000000000000000000000000000000000000..bf39d4a75bde2e1973a0cd1edbab2c1ccefa468d
--- /dev/null
+++ b/SOURCES/Python-3.11.4.tar.xz.asc
@@ -0,0 +1,16 @@
+-----BEGIN PGP SIGNATURE-----
+
+iQIzBAABCAAdFiEEz9yiRbEEPPKl+Xhl/+h0BBaL2EcFAmR/sHIACgkQ/+h0BBaL
+2EfQDQ//eFWvcQ5ijhVd3r5lp7NTNUPK6xKR2iqzpNWlN2Z4QkGJ2+IworBaZoGA
+tzmbT0j0LB9ZQ+ba3xnqXGXD8Ky+fHLg8GV5yshPlH/bD7tPuHtfDRxNcWplEVSS
+MbMuLjAYavTIHhYEz/Rpx4jvZTI5lwplVqj9WxNI/8tNrL5M2bsCtv+IB6brohiw
+rUOUlT/KDkZbrGfB1Fe033Ep8hay5MkKjhgr7O1dU7zMuDRG+HRsCYGs7a5x6KhH
+3QNTEp+GEIAKEsip5nR7vl5KqL02lHa5sf36SV2wjRTwO+IhgV7lvtJEwOD12oE5
+c+TCQMFbmBXg2vVmNBN/Lwftw1SwT/+orFX6V4U93jq6QNUo4GvPqum6YzuayGYc
+/JM4MNziqmfdNW2YjEHPPfzti3f40eTapys97YufOrmYjM2NY0Fs+kAErvyxiWqi
+guVQtaZIYeLl/9KWqQ0F/Apy1N+fVDuWBkZlizwHrUsGips4Rp7Bh/iCrDdOj+1D
+gRCio7+KvdtzHavZPZnU5dcpUiXZgsDzOTI138IyYaEtVUS59ELkA2qxI1yCb5mk
+eLVG1L7r/J2tIaTcguQppp5Z+62UDTArlUbnRxda0buzA2r1aFiQCTMwp+kTRegw
+T9Ht/CT/D4vpMdmSQTun9MkKifcK+2uGfSsS7Lz4fSWjQLqg36k=
+=zSfJ
+-----END PGP SIGNATURE-----
diff --git a/SPECS/python3.11.spec b/SPECS/python3.11.spec
index 0cbd1ba41d6d6db84b3bb347b2c2f4b25f7b4165..e8383216f95250ba920d90b3a70dac1dab34d7c4 100644
--- a/SPECS/python3.11.spec
+++ b/SPECS/python3.11.spec
@@ -16,11 +16,11 @@ URL: https://www.python.org/
 
 #  WARNING  When rebasing to a new Python version,
 #           remember to update the python3-docs package as well
-%global general_version %{pybasever}.2
+%global general_version %{pybasever}.4
 #global prerel ...
 %global upstream_version %{general_version}%{?prerel}
 Version: %{general_version}%{?prerel:~%{prerel}}
-Release: 2%{?dist}
+Release: 3%{?dist}
 License: Python
 
 
@@ -63,7 +63,7 @@ License: Python
 # If the rpmwheels condition is disabled, we use the bundled wheel packages
 # from Python with the versions below.
 # This needs to be manually updated when we update Python.
-%global pip_version 22.3.1
+%global pip_version 23.1.2
 %global setuptools_version 65.5.0
 
 # Expensive optimizations (mainly, profile-guided optimizations)
@@ -332,6 +332,16 @@ Patch329: 00329-fips.patch
 # https://github.com/GrahamDumpleton/mod_wsgi/issues/730
 Patch371: 00371-revert-bpo-1596321-fix-threading-_shutdown-for-the-main-thread-gh-28549-gh-28589.patch
 
+# 00397 #
+# Filters for tarfile extraction (CVE-2007-4559, PEP-706)
+# First patch fixes determination of symlink targets, which were treated
+# as relative to the root of the archive,
+# rather than the directory containing the symlink.
+# Not yet upstream as of this writing.
+# The second patch is Red Hat configuration, see KB for documentation:
+# - https://access.redhat.com/articles/7004769
+Patch397: 00397-tarfile-filter.patch
+
 # (New patches go here ^^^)
 #
 # When adding new patches to "python" and "python3" in Fedora, EL, etc.,
@@ -1599,6 +1609,19 @@ CheckPython optimized
 # ======================================================
 
 %changelog
+* Wed Aug 09 2023 Petr Viktorin <pviktori@redhat.com> - 3.11.4-3
+- Fix symlink handling in the fix for CVE-2023-24329
+Resolves: rhbz#263261
+
+* Fri Jun 30 2023 Charalampos Stratakis <cstratak@redhat.com> - 3.11.4-2
+- Security fix for CVE-2007-4559
+Resolves: rhbz#263261
+
+* Mon Jun 26 2023 Charalampos Stratakis <cstratak@redhat.com> - 3.11.4-1
+- Update to 3.11.4
+- Security fix for CVE-2023-24329
+Resolves: rhbz#2173917
+
 * Thu Feb 16 2023 Charalampos Stratakis <cstratak@redhat.com> - 3.11.2-2
 - Support OpenSSL FIPS mode