Skip to content

Commit 6f1e0c4

Browse files
[3.13] gh-137335: Fix unlikely name conflicts for named pipes in multiprocessing and asyncio on Windows (GH-137389) (GH-145171)
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 f153430 commit 6f1e0c4

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
@@ -44,6 +44,7 @@
4444
CONNECTION_TIMEOUT = 20.
4545

4646
_mmap_counter = itertools.count()
47+
_MAX_PIPE_ATTEMPTS = 100
4748

4849
default_family = 'AF_INET'
4950
families = ['AF_INET']
@@ -76,8 +77,8 @@ def arbitrary_address(family):
7677
elif family == 'AF_UNIX':
7778
return tempfile.mktemp(prefix='sock-', dir=util.get_temp_dir())
7879
elif family == 'AF_PIPE':
79-
return tempfile.mktemp(prefix=r'\\.\pipe\pyc-%d-%d-' %
80-
(os.getpid(), next(_mmap_counter)), dir="")
80+
return (r'\\.\pipe\pyc-%d-%d-%s' %
81+
(os.getpid(), next(_mmap_counter), os.urandom(8).hex()))
8182
else:
8283
raise ValueError('unrecognized family')
8384

@@ -455,17 +456,29 @@ class Listener(object):
455456
def __init__(self, address=None, family=None, backlog=1, authkey=None):
456457
family = family or (address and address_type(address)) \
457458
or default_family
458-
address = address or arbitrary_address(family)
459-
460459
_validate_family(family)
460+
if authkey is not None and not isinstance(authkey, bytes):
461+
raise TypeError('authkey should be a byte string')
462+
461463
if family == 'AF_PIPE':
462-
self._listener = PipeListener(address, backlog)
464+
if address:
465+
self._listener = PipeListener(address, backlog)
466+
else:
467+
for attempts in itertools.count():
468+
address = arbitrary_address(family)
469+
try:
470+
self._listener = PipeListener(address, backlog)
471+
break
472+
except OSError as e:
473+
if attempts >= _MAX_PIPE_ATTEMPTS:
474+
raise
475+
if e.winerror not in (_winapi.ERROR_PIPE_BUSY,
476+
_winapi.ERROR_ACCESS_DENIED):
477+
raise
463478
else:
479+
address = address or arbitrary_address(family)
464480
self._listener = SocketListener(address, family, backlog)
465481

466-
if authkey is not None and not isinstance(authkey, bytes):
467-
raise TypeError('authkey should be a byte string')
468-
469482
self._authkey = authkey
470483

471484
def accept(self):
@@ -553,7 +566,6 @@ def Pipe(duplex=True):
553566
'''
554567
Returns pair of connection objects at either end of a pipe
555568
'''
556-
address = arbitrary_address('AF_PIPE')
557569
if duplex:
558570
openmode = _winapi.PIPE_ACCESS_DUPLEX
559571
access = _winapi.GENERIC_READ | _winapi.GENERIC_WRITE
@@ -563,15 +575,25 @@ def Pipe(duplex=True):
563575
access = _winapi.GENERIC_WRITE
564576
obsize, ibsize = 0, BUFSIZE
565577

566-
h1 = _winapi.CreateNamedPipe(
567-
address, openmode | _winapi.FILE_FLAG_OVERLAPPED |
568-
_winapi.FILE_FLAG_FIRST_PIPE_INSTANCE,
569-
_winapi.PIPE_TYPE_MESSAGE | _winapi.PIPE_READMODE_MESSAGE |
570-
_winapi.PIPE_WAIT,
571-
1, obsize, ibsize, _winapi.NMPWAIT_WAIT_FOREVER,
572-
# default security descriptor: the handle cannot be inherited
573-
_winapi.NULL
574-
)
578+
for attempts in itertools.count():
579+
address = arbitrary_address('AF_PIPE')
580+
try:
581+
h1 = _winapi.CreateNamedPipe(
582+
address, openmode | _winapi.FILE_FLAG_OVERLAPPED |
583+
_winapi.FILE_FLAG_FIRST_PIPE_INSTANCE,
584+
_winapi.PIPE_TYPE_MESSAGE | _winapi.PIPE_READMODE_MESSAGE |
585+
_winapi.PIPE_WAIT,
586+
1, obsize, ibsize, _winapi.NMPWAIT_WAIT_FOREVER,
587+
# default security descriptor: the handle cannot be inherited
588+
_winapi.NULL
589+
)
590+
break
591+
except OSError as e:
592+
if attempts >= _MAX_PIPE_ATTEMPTS:
593+
raise
594+
if e.winerror not in (_winapi.ERROR_PIPE_BUSY,
595+
_winapi.ERROR_ACCESS_DENIED):
596+
raise
575597
h2 = _winapi.CreateFile(
576598
address, access, 0, _winapi.NULL, _winapi.OPEN_EXISTING,
577599
_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)