]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.13] gh-132124: improve safety nets for creating AF_UNIX socket files (GH-134085...
authorMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>
Mon, 4 Aug 2025 14:38:49 +0000 (16:38 +0200)
committerGitHub <noreply@github.com>
Mon, 4 Aug 2025 14:38:49 +0000 (07:38 -0700)
* gh-132124: improve safety nets for creating AF_UNIX socket files (GH-134085)

* ensure that we can create AF_UNIX socket files
* emit a warning if system-wide temporary directory is used
(cherry picked from commit 1a07a01014bde23acd2684916ef38dc0cd73c2de)

Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com>
* rename warn -> _warn for the 3.13.x backport

* Update Misc/NEWS.d/next/Library/2025-05-16-12-40-37.gh-issue-132124.T_5Odx.rst

---------

Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com>
Co-authored-by: Gregory P. Smith <greg@krypto.org>
Lib/multiprocessing/connection.py
Lib/multiprocessing/util.py
Lib/tempfile.py
Misc/NEWS.d/next/Library/2025-05-16-12-40-37.gh-issue-132124.T_5Odx.rst [new file with mode: 0644]

index 8caddd204d7c98fb1b09ddf4075f1c45f7d50019..abd88adf76e700a1db016f2785b45be44dae2854 100644 (file)
@@ -74,7 +74,7 @@ def arbitrary_address(family):
     if family == 'AF_INET':
         return ('localhost', 0)
     elif family == 'AF_UNIX':
-        return tempfile.mktemp(prefix='listener-', dir=util.get_temp_dir())
+        return tempfile.mktemp(prefix='sock-', dir=util.get_temp_dir())
     elif family == 'AF_PIPE':
         return tempfile.mktemp(prefix=r'\\.\pipe\pyc-%d-%d-' %
                                (os.getpid(), next(_mmap_counter)), dir="")
index 75dde02d88c533563f43cfd464e9779cba7baf90..e7e48c65f7a4be4b231260ee67f288228e907643 100644 (file)
@@ -34,6 +34,7 @@ SUBDEBUG = 5
 DEBUG = 10
 INFO = 20
 SUBWARNING = 25
+WARNING = 30
 
 LOGGER_NAME = 'multiprocessing'
 DEFAULT_LOGGING_FORMAT = '[%(levelname)s/%(processName)s] %(message)s'
@@ -53,6 +54,10 @@ def info(msg, *args):
     if _logger:
         _logger.log(INFO, msg, *args, stacklevel=2)
 
+def _warn(msg, *args):
+    if _logger:
+        _logger.log(WARNING, msg, *args, stacklevel=2)
+
 def sub_warning(msg, *args):
     if _logger:
         _logger.log(SUBWARNING, msg, *args, stacklevel=2)
@@ -121,6 +126,21 @@ abstract_sockets_supported = _platform_supports_abstract_sockets()
 # Function returning a temp directory which will be removed on exit
 #
 
+# Maximum length of a socket file path is usually between 92 and 108 [1],
+# but Linux is known to use a size of 108 [2]. BSD-based systems usually
+# use a size of 104 or 108 and Windows does not create AF_UNIX sockets.
+#
+# [1]: https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/sys_un.h.html
+# [2]: https://man7.org/linux/man-pages/man7/unix.7.html.
+
+if sys.platform == 'linux':
+    _SUN_PATH_MAX = 108
+elif sys.platform.startswith(('openbsd', 'freebsd')):
+    _SUN_PATH_MAX = 104
+else:
+    # On Windows platforms, we do not create AF_UNIX sockets.
+    _SUN_PATH_MAX = None if os.name == 'nt' else 92
+
 def _remove_temp_dir(rmtree, tempdir):
     rmtree(tempdir)
 
@@ -130,12 +150,67 @@ def _remove_temp_dir(rmtree, tempdir):
     if current_process is not None:
         current_process._config['tempdir'] = None
 
+def _get_base_temp_dir(tempfile):
+    """Get a temporary directory where socket files will be created.
+
+    To prevent additional imports, pass a pre-imported 'tempfile' module.
+    """
+    if os.name == 'nt':
+        return None
+    # Most of the time, the default temporary directory is /tmp. Thus,
+    # listener sockets files "$TMPDIR/pymp-XXXXXXXX/sock-XXXXXXXX" do
+    # not have a path length exceeding SUN_PATH_MAX.
+    #
+    # If users specify their own temporary directory, we may be unable
+    # to create those files. Therefore, we fall back to the system-wide
+    # temporary directory /tmp, assumed to exist on POSIX systems.
+    #
+    # See https://github.com/python/cpython/issues/132124.
+    base_tempdir = tempfile.gettempdir()
+    # Files created in a temporary directory are suffixed by a string
+    # generated by tempfile._RandomNameSequence, which, by design,
+    # is 8 characters long.
+    #
+    # Thus, the length of socket filename will be:
+    #
+    #   len(base_tempdir + '/pymp-XXXXXXXX' + '/sock-XXXXXXXX')
+    sun_path_len = len(base_tempdir) + 14 + 14
+    if sun_path_len <= _SUN_PATH_MAX:
+        return base_tempdir
+    # Fallback to the default system-wide temporary directory.
+    # This ignores user-defined environment variables.
+    #
+    # On POSIX systems, /tmp MUST be writable by any application [1].
+    # We however emit a warning if this is not the case to prevent
+    # obscure errors later in the execution.
+    #
+    # On some legacy systems, /var/tmp and /usr/tmp can be present
+    # and will be used instead.
+    #
+    # [1]: https://refspecs.linuxfoundation.org/FHS_3.0/fhs/ch03s18.html
+    dirlist = ['/tmp', '/var/tmp', '/usr/tmp']
+    try:
+        base_system_tempdir = tempfile._get_default_tempdir(dirlist)
+    except FileNotFoundError:
+        _warn("Process-wide temporary directory %s will not be usable for "
+              "creating socket files and no usable system-wide temporary "
+              "directory was found in %s", base_tempdir, dirlist)
+        # At this point, the system-wide temporary directory is not usable
+        # but we may assume that the user-defined one is, even if we will
+        # not be able to write socket files out there.
+        return base_tempdir
+    _warn("Ignoring user-defined temporary directory: %s", base_tempdir)
+    # at most max(map(len, dirlist)) + 14 + 14 = 36 characters
+    assert len(base_system_tempdir) + 14 + 14 <= _SUN_PATH_MAX
+    return base_system_tempdir
+
 def get_temp_dir():
     # get name of a temp directory which will be automatically cleaned up
     tempdir = process.current_process()._config.get('tempdir')
     if tempdir is None:
         import shutil, tempfile
-        tempdir = tempfile.mkdtemp(prefix='pymp-')
+        base_tempdir = _get_base_temp_dir(tempfile)
+        tempdir = tempfile.mkdtemp(prefix='pymp-', dir=base_tempdir)
         info('created temp directory %s', tempdir)
         # keep a strong reference to shutil.rmtree(), since the finalizer
         # can be called late during Python shutdown
index 67aa13f494d27d551d86032f8f0f1a6576e7008d..8036e93cd6d7754927de3a6438aa14614e424189 100644 (file)
@@ -180,7 +180,7 @@ def _candidate_tempdir_list():
 
     return dirlist
 
-def _get_default_tempdir():
+def _get_default_tempdir(dirlist=None):
     """Calculate the default directory to use for temporary files.
     This routine should be called exactly once.
 
@@ -190,7 +190,8 @@ def _get_default_tempdir():
     service, the name of the test file must be randomized."""
 
     namer = _RandomNameSequence()
-    dirlist = _candidate_tempdir_list()
+    if dirlist is None:
+        dirlist = _candidate_tempdir_list()
 
     for dir in dirlist:
         if dir != _os.curdir:
diff --git a/Misc/NEWS.d/next/Library/2025-05-16-12-40-37.gh-issue-132124.T_5Odx.rst b/Misc/NEWS.d/next/Library/2025-05-16-12-40-37.gh-issue-132124.T_5Odx.rst
new file mode 100644 (file)
index 0000000..2a72d30
--- /dev/null
@@ -0,0 +1,4 @@
+On POSIX-compliant systems, :func:`!multiprocessing.util.get_temp_dir` now
+ignores :envvar:`TMPDIR` (and similar environment variables) if the path
+length of ``AF_UNIX`` socket files exceeds the platform-specific maximum
+length when using the *forkserver* start method. Patch by Bénédikt Tran.