Skip to content

Commit 1844ff3

Browse files
Alexey Izbysheveryksun
andcommitted
gh-77085: Fix potential crash in os.chdir() and os.getcwd() on Windows
Both functions retry GetCurrentDirectoryW() if the initial buffer is insufficient, but don't check whether the newly allocated buffer is sufficient after the second call. This might not be the case if another thread changes the current directory concurrently, and if so, the functions will proceed to use uninitialized memory. Fix this by retrying GetCurrentDirectoryW() in a loop with larger buffers until it succeeds or we run out of memory. Co-authored-by: Eryk Sun <eryksun@gmail.com>
1 parent d8c7a11 commit 1844ff3

File tree

2 files changed

+50
-49
lines changed

2 files changed

+50
-49
lines changed
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix a potential crash in os.chdir() and os.getcwd() if another thread is
2+
calling os.chdir() concurrently.

Modules/posixmodule.c

Lines changed: 48 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1734,6 +1734,39 @@ posix_fildes_fd(int fd, int (*func)(int))
17341734

17351735

17361736
#ifdef MS_WINDOWS
1737+
static wchar_t *
1738+
win32_wgetcwd(wchar_t *buf, DWORD buf_size)
1739+
{
1740+
wchar_t *local_buf = buf;
1741+
while (1) {
1742+
wchar_t *temp;
1743+
DWORD result = GetCurrentDirectoryW(buf_size, local_buf);
1744+
if (!result)
1745+
goto fail;
1746+
1747+
/* L'\0' is not counted in result on success. */
1748+
if (result < buf_size)
1749+
break;
1750+
1751+
buf_size = result;
1752+
temp = PyMem_RawRealloc(local_buf != buf ? local_buf : NULL,
1753+
buf_size * sizeof(wchar_t));
1754+
if (!temp) {
1755+
SetLastError(ERROR_OUTOFMEMORY);
1756+
goto fail;
1757+
}
1758+
local_buf = temp;
1759+
}
1760+
1761+
return local_buf;
1762+
1763+
fail:
1764+
if (local_buf != buf) {
1765+
PyMem_RawFree(local_buf);
1766+
}
1767+
return NULL;
1768+
}
1769+
17371770
/* This is a reimplementation of the C library's chdir function,
17381771
but one that produces Win32 errors instead of DOS error codes.
17391772
chdir is essentially a wrapper around SetCurrentDirectory; however,
@@ -1742,36 +1775,28 @@ posix_fildes_fd(int fd, int (*func)(int))
17421775
static BOOL __stdcall
17431776
win32_wchdir(LPCWSTR path)
17441777
{
1745-
wchar_t path_buf[MAX_PATH], *new_path = path_buf;
1778+
wchar_t path_buf[MAX_PATH], *new_path;
17461779
int result;
17471780
wchar_t env[4] = L"=x:";
17481781

17491782
if(!SetCurrentDirectoryW(path))
17501783
return FALSE;
1751-
result = GetCurrentDirectoryW(Py_ARRAY_LENGTH(path_buf), new_path);
1752-
if (!result)
1784+
1785+
new_path = win32_wgetcwd(path_buf, (DWORD)Py_ARRAY_LENGTH(path_buf));
1786+
if (!new_path)
17531787
return FALSE;
1754-
if (result > Py_ARRAY_LENGTH(path_buf)) {
1755-
new_path = PyMem_RawMalloc(result * sizeof(wchar_t));
1756-
if (!new_path) {
1757-
SetLastError(ERROR_OUTOFMEMORY);
1758-
return FALSE;
1759-
}
1760-
result = GetCurrentDirectoryW(result, new_path);
1761-
if (!result) {
1762-
PyMem_RawFree(new_path);
1763-
return FALSE;
1764-
}
1765-
}
1788+
17661789
int is_unc_like_path = (wcsncmp(new_path, L"\\\\", 2) == 0 ||
17671790
wcsncmp(new_path, L"//", 2) == 0);
17681791
if (!is_unc_like_path) {
17691792
env[1] = new_path[0];
17701793
result = SetEnvironmentVariableW(env, new_path);
1794+
} else {
1795+
result = TRUE;
17711796
}
17721797
if (new_path != path_buf)
17731798
PyMem_RawFree(new_path);
1774-
return result ? TRUE : FALSE;
1799+
return result;
17751800
}
17761801
#endif
17771802

@@ -3736,50 +3761,24 @@ static PyObject *
37363761
posix_getcwd(int use_bytes)
37373762
{
37383763
#ifdef MS_WINDOWS
3739-
wchar_t wbuf[MAXPATHLEN];
3740-
wchar_t *wbuf2 = wbuf;
3741-
DWORD len;
3764+
wchar_t wbuf[MAX_PATH];
3765+
wchar_t *wbuf2;
37423766

37433767
Py_BEGIN_ALLOW_THREADS
3744-
len = GetCurrentDirectoryW(Py_ARRAY_LENGTH(wbuf), wbuf);
3745-
/* If the buffer is large enough, len does not include the
3746-
terminating \0. If the buffer is too small, len includes
3747-
the space needed for the terminator. */
3748-
if (len >= Py_ARRAY_LENGTH(wbuf)) {
3749-
if (len <= PY_SSIZE_T_MAX / sizeof(wchar_t)) {
3750-
wbuf2 = PyMem_RawMalloc(len * sizeof(wchar_t));
3751-
}
3752-
else {
3753-
wbuf2 = NULL;
3754-
}
3755-
if (wbuf2) {
3756-
len = GetCurrentDirectoryW(len, wbuf2);
3757-
}
3758-
}
3768+
wbuf2 = win32_wgetcwd(wbuf, (DWORD)Py_ARRAY_LENGTH(wbuf));
37593769
Py_END_ALLOW_THREADS
37603770

37613771
if (!wbuf2) {
3762-
PyErr_NoMemory();
3763-
return NULL;
3764-
}
3765-
if (!len) {
3766-
if (wbuf2 != wbuf)
3767-
PyMem_RawFree(wbuf2);
37683772
return PyErr_SetFromWindowsErr(0);
37693773
}
37703774

3771-
PyObject *resobj = PyUnicode_FromWideChar(wbuf2, len);
3775+
PyObject *resobj = PyUnicode_FromWideChar(wbuf2, -1);
3776+
if (use_bytes && resobj) {
3777+
Py_SETREF(resobj, PyUnicode_EncodeFSDefault(resobj));
3778+
}
37723779
if (wbuf2 != wbuf) {
37733780
PyMem_RawFree(wbuf2);
37743781
}
3775-
3776-
if (use_bytes) {
3777-
if (resobj == NULL) {
3778-
return NULL;
3779-
}
3780-
Py_SETREF(resobj, PyUnicode_EncodeFSDefault(resobj));
3781-
}
3782-
37833782
return resobj;
37843783
#else
37853784
const size_t chunk = 1024;

0 commit comments

Comments
 (0)