Skip to content

Commit bbce6ba

Browse files
[3.14] gh-137335: Fix unlikely name conflicts for named pipes in multiprocessing and asyncio on Windows (GH-137389) (GH-145170)
Since os.stat() raises an OSError for existing named pipe "\\.\pipe\...", os.path.exists() always returns False for it, and tempfile.mktemp() can return a name that matches an existing named pipe. So, tempfile.mktemp() cannot be used to generate unique names for named pipes. Instead, CreateNamedPipe() should be called in a loop with different names until it completes successfully. (cherry picked from commit d6a71f4) Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
1 parent 6dc03ef commit bbce6ba

File tree

3 files changed

+57
-26
lines changed

3 files changed

+57
-26
lines changed

Lib/asyncio/windows_utils.py

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
import msvcrt
1111
import os
1212
import subprocess
13-
import tempfile
1413
import warnings
1514

1615

@@ -24,17 +23,14 @@
2423
PIPE = subprocess.PIPE
2524
STDOUT = subprocess.STDOUT
2625
_mmap_counter = itertools.count()
26+
_MAX_PIPE_ATTEMPTS = 20
2727

2828

2929
# Replacement for os.pipe() using handles instead of fds
3030

3131

3232
def pipe(*, duplex=False, overlapped=(True, True), bufsize=BUFSIZE):
3333
"""Like os.pipe() but with overlapped support and using handles not fds."""
34-
address = tempfile.mktemp(
35-
prefix=r'\\.\pipe\python-pipe-{:d}-{:d}-'.format(
36-
os.getpid(), next(_mmap_counter)))
37-
3834
if duplex:
3935
openmode = _winapi.PIPE_ACCESS_DUPLEX
4036
access = _winapi.GENERIC_READ | _winapi.GENERIC_WRITE
@@ -56,9 +52,20 @@ def pipe(*, duplex=False, overlapped=(True, True), bufsize=BUFSIZE):
5652

5753
h1 = h2 = None
5854
try:
59-
h1 = _winapi.CreateNamedPipe(
60-
address, openmode, _winapi.PIPE_WAIT,
61-
1, obsize, ibsize, _winapi.NMPWAIT_WAIT_FOREVER, _winapi.NULL)
55+
for attempts in itertools.count():
56+
address = r'\\.\pipe\python-pipe-{:d}-{:d}-{}'.format(
57+
os.getpid(), next(_mmap_counter), os.urandom(8).hex())
58+
try:
59+
h1 = _winapi.CreateNamedPipe(
60+
address, openmode, _winapi.PIPE_WAIT,
61+
1, obsize, ibsize, _winapi.NMPWAIT_WAIT_FOREVER, _winapi.NULL)
62+
break
63+
except OSError as e:
64+
if attempts >= _MAX_PIPE_ATTEMPTS:
65+
raise
66+
if e.winerror not in (_winapi.ERROR_PIPE_BUSY,
67+
_winapi.ERROR_ACCESS_DENIED):
68+
raise
6269

6370
h2 = _winapi.CreateFile(
6471
address, access, 0, _winapi.NULL, _winapi.OPEN_EXISTING,

Lib/multiprocessing/connection.py

Lines changed: 40 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
CONNECTION_TIMEOUT = 20.
4747

4848
_mmap_counter = itertools.count()
49+
_MAX_PIPE_ATTEMPTS = 100
4950

5051
default_family = 'AF_INET'
5152
families = ['AF_INET']
@@ -78,8 +79,8 @@ def arbitrary_address(family):
7879
elif family == 'AF_UNIX':
7980
return tempfile.mktemp(prefix='sock-', dir=util.get_temp_dir())
8081
elif family == 'AF_PIPE':
81-
return tempfile.mktemp(prefix=r'\\.\pipe\pyc-%d-%d-' %
82-
(os.getpid(), next(_mmap_counter)), dir="")
82+
return (r'\\.\pipe\pyc-%d-%d-%s' %
83+
(os.getpid(), next(_mmap_counter), os.urandom(8).hex()))
8384
else:
8485
raise ValueError('unrecognized family')
8586

@@ -472,17 +473,29 @@ class Listener(object):
472473
def __init__(self, address=None, family=None, backlog=1, authkey=None):
473474
family = family or (address and address_type(address)) \
474475
or default_family
475-
address = address or arbitrary_address(family)
476-
477476
_validate_family(family)
477+
if authkey is not None and not isinstance(authkey, bytes):
478+
raise TypeError('authkey should be a byte string')
479+
478480
if family == 'AF_PIPE':
479-
self._listener = PipeListener(address, backlog)
481+
if address:
482+
self._listener = PipeListener(address, backlog)
483+
else:
484+
for attempts in itertools.count():
485+
address = arbitrary_address(family)
486+
try:
487+
self._listener = PipeListener(address, backlog)
488+
break
489+
except OSError as e:
490+
if attempts >= _MAX_PIPE_ATTEMPTS:
491+
raise
492+
if e.winerror not in (_winapi.ERROR_PIPE_BUSY,
493+
_winapi.ERROR_ACCESS_DENIED):
494+
raise
480495
else:
496+
address = address or arbitrary_address(family)
481497
self._listener = SocketListener(address, family, backlog)
482498

483-
if authkey is not None and not isinstance(authkey, bytes):
484-
raise TypeError('authkey should be a byte string')
485-
486499
self._authkey = authkey
487500

488501
def accept(self):
@@ -570,7 +583,6 @@ def Pipe(duplex=True):
570583
'''
571584
Returns pair of connection objects at either end of a pipe
572585
'''
573-
address = arbitrary_address('AF_PIPE')
574586
if duplex:
575587
openmode = _winapi.PIPE_ACCESS_DUPLEX
576588
access = _winapi.GENERIC_READ | _winapi.GENERIC_WRITE
@@ -580,15 +592,25 @@ def Pipe(duplex=True):
580592
access = _winapi.GENERIC_WRITE
581593
obsize, ibsize = 0, BUFSIZE
582594

583-
h1 = _winapi.CreateNamedPipe(
584-
address, openmode | _winapi.FILE_FLAG_OVERLAPPED |
585-
_winapi.FILE_FLAG_FIRST_PIPE_INSTANCE,
586-
_winapi.PIPE_TYPE_MESSAGE | _winapi.PIPE_READMODE_MESSAGE |
587-
_winapi.PIPE_WAIT,
588-
1, obsize, ibsize, _winapi.NMPWAIT_WAIT_FOREVER,
589-
# default security descriptor: the handle cannot be inherited
590-
_winapi.NULL
591-
)
595+
for attempts in itertools.count():
596+
address = arbitrary_address('AF_PIPE')
597+
try:
598+
h1 = _winapi.CreateNamedPipe(
599+
address, openmode | _winapi.FILE_FLAG_OVERLAPPED |
600+
_winapi.FILE_FLAG_FIRST_PIPE_INSTANCE,
601+
_winapi.PIPE_TYPE_MESSAGE | _winapi.PIPE_READMODE_MESSAGE |
602+
_winapi.PIPE_WAIT,
603+
1, obsize, ibsize, _winapi.NMPWAIT_WAIT_FOREVER,
604+
# default security descriptor: the handle cannot be inherited
605+
_winapi.NULL
606+
)
607+
break
608+
except OSError as e:
609+
if attempts >= _MAX_PIPE_ATTEMPTS:
610+
raise
611+
if e.winerror not in (_winapi.ERROR_PIPE_BUSY,
612+
_winapi.ERROR_ACCESS_DENIED):
613+
raise
592614
h2 = _winapi.CreateFile(
593615
address, access, 0, _winapi.NULL, _winapi.OPEN_EXISTING,
594616
_winapi.FILE_FLAG_OVERLAPPED, _winapi.NULL
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Get rid of any possibility of a name conflict for named pipes in
2+
:mod:`multiprocessing` and :mod:`asyncio` on Windows, no matter how small.

0 commit comments

Comments
 (0)