Skip to content

Commit b82bfac

Browse files
thebecwarzooba
authored andcommitted
bpo-29734: nt._getfinalpathname handle leak (GH-740)
Make sure that failure paths call CloseHandle outside of the function that failed
1 parent cb09047 commit b82bfac

File tree

3 files changed

+67
-8
lines changed

3 files changed

+67
-8
lines changed

Lib/test/test_os.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2325,6 +2325,62 @@ def test_unlink_removes_junction(self):
23252325
os.unlink(self.junction)
23262326
self.assertFalse(os.path.exists(self.junction))
23272327

2328+
@unittest.skipUnless(sys.platform == "win32", "Win32 specific tests")
2329+
class Win32NtTests(unittest.TestCase):
2330+
def setUp(self):
2331+
from test import support
2332+
self.nt = support.import_module('nt')
2333+
pass
2334+
2335+
def tearDown(self):
2336+
pass
2337+
2338+
def test_getfinalpathname_handles(self):
2339+
try:
2340+
import ctypes, ctypes.wintypes
2341+
except ImportError:
2342+
raise unittest.SkipTest('ctypes module is required for this test')
2343+
2344+
kernel = ctypes.WinDLL('Kernel32.dll', use_last_error=True)
2345+
kernel.GetCurrentProcess.restype = ctypes.wintypes.HANDLE
2346+
2347+
kernel.GetProcessHandleCount.restype = ctypes.wintypes.BOOL
2348+
kernel.GetProcessHandleCount.argtypes = (ctypes.wintypes.HANDLE,
2349+
ctypes.wintypes.LPDWORD)
2350+
2351+
# This is a pseudo-handle that doesn't need to be closed
2352+
hproc = kernel.GetCurrentProcess()
2353+
2354+
handle_count = ctypes.wintypes.DWORD()
2355+
ok = kernel.GetProcessHandleCount(hproc, ctypes.byref(handle_count))
2356+
self.assertEqual(1, ok)
2357+
2358+
before_count = handle_count.value
2359+
2360+
# The first two test the error path, __file__ tests the success path
2361+
filenames = [ r'\\?\C:',
2362+
r'\\?\NUL',
2363+
r'\\?\CONIN',
2364+
__file__ ]
2365+
2366+
for i in range(10):
2367+
for name in filenames:
2368+
try:
2369+
tmp = self.nt._getfinalpathname(name)
2370+
except:
2371+
# Failure is expected
2372+
pass
2373+
try:
2374+
tmp = os.stat(name)
2375+
except:
2376+
pass
2377+
2378+
ok = kernel.GetProcessHandleCount(hproc, ctypes.byref(handle_count))
2379+
self.assertEqual(1, ok)
2380+
2381+
handle_delta = handle_count.value - before_count
2382+
2383+
self.assertEqual(0, handle_delta)
23282384

23292385
@support.skip_unless_symlink
23302386
class NonLocalSymlinkTests(unittest.TestCase):
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix handle leaks in os.stat on Windows.

Modules/posixmodule.c

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1640,11 +1640,6 @@ get_target_path(HANDLE hdl, wchar_t **target_path)
16401640
return FALSE;
16411641
}
16421642

1643-
if(!CloseHandle(hdl)) {
1644-
PyMem_RawFree(buf);
1645-
return FALSE;
1646-
}
1647-
16481643
buf[result_length] = 0;
16491644

16501645
*target_path = buf;
@@ -1702,9 +1697,10 @@ win32_xstat_impl(const wchar_t *path, struct _Py_stat_struct *result,
17021697
return -1;
17031698
}
17041699
if (info.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {
1705-
if (!win32_get_reparse_tag(hFile, &reparse_tag))
1700+
if (!win32_get_reparse_tag(hFile, &reparse_tag)) {
1701+
CloseHandle(hFile);
17061702
return -1;
1707-
1703+
}
17081704
/* Close the outer open file handle now that we're about to
17091705
reopen it with different flags. */
17101706
if (!CloseHandle(hFile))
@@ -1721,8 +1717,14 @@ win32_xstat_impl(const wchar_t *path, struct _Py_stat_struct *result,
17211717
if (hFile2 == INVALID_HANDLE_VALUE)
17221718
return -1;
17231719

1724-
if (!get_target_path(hFile2, &target_path))
1720+
if (!get_target_path(hFile2, &target_path)) {
1721+
CloseHandle(hFile2);
17251722
return -1;
1723+
}
1724+
1725+
if (!CloseHandle(hFile2)) {
1726+
return -1;
1727+
}
17261728

17271729
code = win32_xstat_impl(target_path, result, FALSE);
17281730
PyMem_RawFree(target_path);

0 commit comments

Comments
 (0)