From 1844ff3cc3e03838a95e80ba50facafdc44792d3 Mon Sep 17 00:00:00 2001 From: Alexey Izbyshev Date: Sun, 21 Aug 2022 22:20:38 +0300 Subject: [PATCH 1/3] 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 --- ...2-08-21-22-32-54.gh-issue-77085.SFcofz.rst | 2 + Modules/posixmodule.c | 97 +++++++++---------- 2 files changed, 50 insertions(+), 49 deletions(-) create mode 100644 Misc/NEWS.d/next/Windows/2022-08-21-22-32-54.gh-issue-77085.SFcofz.rst diff --git a/Misc/NEWS.d/next/Windows/2022-08-21-22-32-54.gh-issue-77085.SFcofz.rst b/Misc/NEWS.d/next/Windows/2022-08-21-22-32-54.gh-issue-77085.SFcofz.rst new file mode 100644 index 00000000000000..ce8d0b2a66af44 --- /dev/null +++ b/Misc/NEWS.d/next/Windows/2022-08-21-22-32-54.gh-issue-77085.SFcofz.rst @@ -0,0 +1,2 @@ +Fix a potential crash in os.chdir() and os.getcwd() if another thread is +calling os.chdir() concurrently. diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index d45fa231ae5e2a..38da9044009494 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -1734,6 +1734,39 @@ posix_fildes_fd(int fd, int (*func)(int)) #ifdef MS_WINDOWS +static wchar_t * +win32_wgetcwd(wchar_t *buf, DWORD buf_size) +{ + wchar_t *local_buf = buf; + while (1) { + wchar_t *temp; + DWORD result = GetCurrentDirectoryW(buf_size, local_buf); + if (!result) + goto fail; + + /* L'\0' is not counted in result on success. */ + if (result < buf_size) + break; + + buf_size = result; + temp = PyMem_RawRealloc(local_buf != buf ? local_buf : NULL, + buf_size * sizeof(wchar_t)); + if (!temp) { + SetLastError(ERROR_OUTOFMEMORY); + goto fail; + } + local_buf = temp; + } + + return local_buf; + +fail: + if (local_buf != buf) { + PyMem_RawFree(local_buf); + } + return NULL; +} + /* This is a reimplementation of the C library's chdir function, but one that produces Win32 errors instead of DOS error codes. chdir is essentially a wrapper around SetCurrentDirectory; however, @@ -1742,36 +1775,28 @@ posix_fildes_fd(int fd, int (*func)(int)) static BOOL __stdcall win32_wchdir(LPCWSTR path) { - wchar_t path_buf[MAX_PATH], *new_path = path_buf; + wchar_t path_buf[MAX_PATH], *new_path; int result; wchar_t env[4] = L"=x:"; if(!SetCurrentDirectoryW(path)) return FALSE; - result = GetCurrentDirectoryW(Py_ARRAY_LENGTH(path_buf), new_path); - if (!result) + + new_path = win32_wgetcwd(path_buf, (DWORD)Py_ARRAY_LENGTH(path_buf)); + if (!new_path) return FALSE; - if (result > Py_ARRAY_LENGTH(path_buf)) { - new_path = PyMem_RawMalloc(result * sizeof(wchar_t)); - if (!new_path) { - SetLastError(ERROR_OUTOFMEMORY); - return FALSE; - } - result = GetCurrentDirectoryW(result, new_path); - if (!result) { - PyMem_RawFree(new_path); - return FALSE; - } - } + int is_unc_like_path = (wcsncmp(new_path, L"\\\\", 2) == 0 || wcsncmp(new_path, L"//", 2) == 0); if (!is_unc_like_path) { env[1] = new_path[0]; result = SetEnvironmentVariableW(env, new_path); + } else { + result = TRUE; } if (new_path != path_buf) PyMem_RawFree(new_path); - return result ? TRUE : FALSE; + return result; } #endif @@ -3736,50 +3761,24 @@ static PyObject * posix_getcwd(int use_bytes) { #ifdef MS_WINDOWS - wchar_t wbuf[MAXPATHLEN]; - wchar_t *wbuf2 = wbuf; - DWORD len; + wchar_t wbuf[MAX_PATH]; + wchar_t *wbuf2; Py_BEGIN_ALLOW_THREADS - len = GetCurrentDirectoryW(Py_ARRAY_LENGTH(wbuf), wbuf); - /* If the buffer is large enough, len does not include the - terminating \0. If the buffer is too small, len includes - the space needed for the terminator. */ - if (len >= Py_ARRAY_LENGTH(wbuf)) { - if (len <= PY_SSIZE_T_MAX / sizeof(wchar_t)) { - wbuf2 = PyMem_RawMalloc(len * sizeof(wchar_t)); - } - else { - wbuf2 = NULL; - } - if (wbuf2) { - len = GetCurrentDirectoryW(len, wbuf2); - } - } + wbuf2 = win32_wgetcwd(wbuf, (DWORD)Py_ARRAY_LENGTH(wbuf)); Py_END_ALLOW_THREADS if (!wbuf2) { - PyErr_NoMemory(); - return NULL; - } - if (!len) { - if (wbuf2 != wbuf) - PyMem_RawFree(wbuf2); return PyErr_SetFromWindowsErr(0); } - PyObject *resobj = PyUnicode_FromWideChar(wbuf2, len); + PyObject *resobj = PyUnicode_FromWideChar(wbuf2, -1); + if (use_bytes && resobj) { + Py_SETREF(resobj, PyUnicode_EncodeFSDefault(resobj)); + } if (wbuf2 != wbuf) { PyMem_RawFree(wbuf2); } - - if (use_bytes) { - if (resobj == NULL) { - return NULL; - } - Py_SETREF(resobj, PyUnicode_EncodeFSDefault(resobj)); - } - return resobj; #else const size_t chunk = 1024; From d8c9b7d720ce462f84ccb9f694d5c082b39e7b62 Mon Sep 17 00:00:00 2001 From: Alexey Izbyshev Date: Sun, 21 Aug 2022 23:35:34 +0300 Subject: [PATCH 2/3] Add braces per PEP 7 --- Modules/posixmodule.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 38da9044009494..e9f3fb1ba5f62e 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -1741,12 +1741,14 @@ win32_wgetcwd(wchar_t *buf, DWORD buf_size) while (1) { wchar_t *temp; DWORD result = GetCurrentDirectoryW(buf_size, local_buf); - if (!result) + if (!result) { goto fail; + } /* L'\0' is not counted in result on success. */ - if (result < buf_size) + if (result < buf_size) { break; + } buf_size = result; temp = PyMem_RawRealloc(local_buf != buf ? local_buf : NULL, From 3b91207404bb3b3272f225be7ddd65eb82bd6f59 Mon Sep 17 00:00:00 2001 From: Alexey Izbyshev Date: Sun, 21 Aug 2022 23:49:52 +0300 Subject: [PATCH 3/3] ERROR_OUTOFMEMORY -> ERROR_NOT_ENOUGH_MEMORY --- Modules/posixmodule.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index e9f3fb1ba5f62e..6900ef2b7d3097 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -1754,7 +1754,7 @@ win32_wgetcwd(wchar_t *buf, DWORD buf_size) temp = PyMem_RawRealloc(local_buf != buf ? local_buf : NULL, buf_size * sizeof(wchar_t)); if (!temp) { - SetLastError(ERROR_OUTOFMEMORY); + SetLastError(ERROR_NOT_ENOUGH_MEMORY); goto fail; } local_buf = temp;