[Python-checkins] bpo-46785: Fix race condition between os.stat() and unlink on Windows (GH-31858)

zooba webhook-mailer at python.org
Tue May 10 18:52:43 EDT 2022


https://github.com/python/cpython/commit/9be9b585aac111becb5b95b053360ed9b7d206e0
commit: 9be9b585aac111becb5b95b053360ed9b7d206e0
branch: 3.10
author: Itai Steinherz <itaisteinherz at gmail.com>
committer: zooba <steve.dower at microsoft.com>
date: 2022-05-10T23:52:39+01:00
summary:

bpo-46785: Fix race condition between os.stat() and unlink on Windows (GH-31858)

* [3.10] bpo-46785: Fix race condition between os.stat() and unlink on Windows (GH-31858).
(cherry picked from commit 39e6b8ae6a5b49bb23746fdcc354d148ff2d98e3)

Co-authored-by: Itai Steinherz <itaisteinherz at gmail.com>

files:
A Misc/NEWS.d/next/Windows/2022-03-13-20-35-41.bpo-46785.Pnknyl.rst
M Lib/test/test_os.py
M Misc/ACKS
M Modules/posixmodule.c

diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py
index 7f7d14ef0a095..1243b575dcafd 100644
--- a/Lib/test/test_os.py
+++ b/Lib/test/test_os.py
@@ -23,6 +23,7 @@
 import sys
 import sysconfig
 import tempfile
+import textwrap
 import threading
 import time
 import types
@@ -2859,6 +2860,48 @@ def test_getfinalpathname_handles(self):
 
         self.assertEqual(0, handle_delta)
 
+    def test_stat_unlink_race(self):
+        # bpo-46785: the implementation of os.stat() falls back to reading
+        # the parent directory if CreateFileW() fails with a permission
+        # error. If reading the parent directory fails because the file or
+        # directory are subsequently unlinked, or because the volume or
+        # share are no longer available, then the original permission error
+        # should not be restored.
+        filename =  os_helper.TESTFN
+        self.addCleanup(os_helper.unlink, filename)
+        deadline = time.time() + 5
+        command = textwrap.dedent("""\
+            import os
+            import sys
+            import time
+
+            filename = sys.argv[1]
+            deadline = float(sys.argv[2])
+
+            while time.time() < deadline:
+                try:
+                    with open(filename, "w") as f:
+                        pass
+                except OSError:
+                    pass
+                try:
+                    os.remove(filename)
+                except OSError:
+                    pass
+            """)
+
+        with subprocess.Popen([sys.executable, '-c', command, filename, str(deadline)]) as proc:
+            while time.time() < deadline:
+                try:
+                    os.stat(filename)
+                except FileNotFoundError as e:
+                    assert e.winerror == 2  # ERROR_FILE_NOT_FOUND
+            try:
+                proc.wait(1)
+            except subprocess.TimeoutExpired:
+                proc.terminate()
+
+
 @os_helper.skip_unless_symlink
 class NonLocalSymlinkTests(unittest.TestCase):
 
diff --git a/Misc/ACKS b/Misc/ACKS
index c6abfac531cbe..889bf8aeaea1a 100644
--- a/Misc/ACKS
+++ b/Misc/ACKS
@@ -1694,6 +1694,7 @@ Anthony Starks
 David Steele
 Oliver Steele
 Greg Stein
+Itai Steinherz
 Marek Stepniowski
 Baruch Sterin
 Chris Stern
diff --git a/Misc/NEWS.d/next/Windows/2022-03-13-20-35-41.bpo-46785.Pnknyl.rst b/Misc/NEWS.d/next/Windows/2022-03-13-20-35-41.bpo-46785.Pnknyl.rst
new file mode 100644
index 0000000000000..0a87abd77c8ff
--- /dev/null
+++ b/Misc/NEWS.d/next/Windows/2022-03-13-20-35-41.bpo-46785.Pnknyl.rst
@@ -0,0 +1 @@
+Fix race condition between :func:`os.stat` and unlinking a file on Windows, by using errors codes returned by ``FindFirstFileW()`` when appropriate in ``win32_xstat_impl``.
diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c
index 03de4703239a7..26c8e7bc2804d 100644
--- a/Modules/posixmodule.c
+++ b/Modules/posixmodule.c
@@ -1905,7 +1905,17 @@ win32_xstat_impl(const wchar_t *path, struct _Py_stat_struct *result,
             /* Try reading the parent directory. */
             if (!attributes_from_dir(path, &fileInfo, &tagInfo.ReparseTag)) {
                 /* Cannot read the parent directory. */
-                SetLastError(error);
+                switch (GetLastError()) {
+                case ERROR_FILE_NOT_FOUND: /* File cannot be found */
+                case ERROR_PATH_NOT_FOUND: /* File parent directory cannot be found */
+                case ERROR_NOT_READY: /* Drive exists but unavailable */
+                case ERROR_BAD_NET_NAME: /* Remote drive unavailable */
+                    break;
+                /* Restore the error from CreateFileW(). */
+                default:
+                    SetLastError(error);
+                }
+
                 return -1;
             }
             if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {



More information about the Python-checkins mailing list