bpo-29778: Ensure python3.dll is loaded from correct locations when Python is embedded (GH-21297)


Also enables using debug build of `python3_d.dll`
Reference: CVE-2020-15523
(cherry picked from commit dcbaa1b49cd9062fb9ba2b9d49555ac6cd8c60b5)

Co-authored-by: Steve Dower <steve.dower@python.org>
diff --git a/PC/getpathp.c b/PC/getpathp.c
index fd5cfa7..0939c5f 100644
--- a/PC/getpathp.c
+++ b/PC/getpathp.c
@@ -131,8 +131,6 @@
     wchar_t *machine_path;   /* from HKEY_LOCAL_MACHINE */
     wchar_t *user_path;      /* from HKEY_CURRENT_USER */
 
-    wchar_t *dll_path;
-
     const wchar_t *pythonpath_env;
 } PyCalculatePath;
 
@@ -168,27 +166,37 @@
 static int
 change_ext(wchar_t *dest, const wchar_t *src, const wchar_t *ext)
 {
-    size_t src_len = wcsnlen_s(src, MAXPATHLEN+1);
-    size_t i = src_len;
-    if (i >= MAXPATHLEN+1) {
-        Py_FatalError("buffer overflow in getpathp.c's reduce()");
+    if (src && src != dest) {
+        size_t src_len = wcsnlen_s(src, MAXPATHLEN+1);
+        size_t i = src_len;
+        if (i >= MAXPATHLEN+1) {
+            Py_FatalError("buffer overflow in getpathp.c's reduce()");
+        }
+
+        while (i > 0 && src[i] != '.' && !is_sep(src[i]))
+            --i;
+
+        if (i == 0) {
+            dest[0] = '\0';
+            return -1;
+        }
+
+        if (is_sep(src[i])) {
+            i = src_len;
+        }
+
+        if (wcsncpy_s(dest, MAXPATHLEN+1, src, i)) {
+            dest[0] = '\0';
+            return -1;
+        }
+    } else {
+        wchar_t *s = wcsrchr(dest, L'.');
+        if (s) {
+            s[0] = '\0';
+        }
     }
 
-    while (i > 0 && src[i] != '.' && !is_sep(src[i]))
-        --i;
-
-    if (i == 0) {
-        dest[0] = '\0';
-        return -1;
-    }
-
-    if (is_sep(src[i])) {
-        i = src_len;
-    }
-
-    if (wcsncpy_s(dest, MAXPATHLEN+1, src, i) ||
-        wcscat_s(dest, MAXPATHLEN+1, ext))
-    {
+    if (wcscat_s(dest, MAXPATHLEN+1, ext)) {
         dest[0] = '\0';
         return -1;
     }
@@ -297,6 +305,19 @@
 }
 
 
+static int
+get_dllpath(wchar_t *dllpath)
+{
+#ifdef Py_ENABLE_SHARED
+    extern HANDLE PyWin_DLLhModule;
+    if (PyWin_DLLhModule && GetModuleFileNameW(PyWin_DLLhModule, dllpath, MAXPATHLEN)) {
+        return 0;
+    }
+#endif
+    return -1;
+}
+
+
 #ifdef Py_ENABLE_SHARED
 
 /* a string loaded from the DLL at startup.*/
@@ -468,27 +489,6 @@
 #endif /* Py_ENABLE_SHARED */
 
 
-wchar_t*
-_Py_GetDLLPath(void)
-{
-    wchar_t dll_path[MAXPATHLEN+1];
-    memset(dll_path, 0, sizeof(dll_path));
-
-#ifdef Py_ENABLE_SHARED
-    extern HANDLE PyWin_DLLhModule;
-    if (PyWin_DLLhModule) {
-        if (!GetModuleFileNameW(PyWin_DLLhModule, dll_path, MAXPATHLEN)) {
-            dll_path[0] = 0;
-        }
-    }
-#else
-    dll_path[0] = 0;
-#endif
-
-    return _PyMem_RawWcsdup(dll_path);
-}
-
-
 static PyStatus
 get_program_full_path(_PyPathConfig *pathconfig)
 {
@@ -669,19 +669,17 @@
 get_pth_filename(PyCalculatePath *calculate, wchar_t *filename,
                  const _PyPathConfig *pathconfig)
 {
-    if (calculate->dll_path[0]) {
-        if (!change_ext(filename, calculate->dll_path, L"._pth") &&
-            exists(filename))
-        {
-            return 1;
-        }
+    if (get_dllpath(filename) &&
+        !change_ext(filename, filename, L"._pth") &&
+        exists(filename))
+    {
+        return 1;
     }
-    if (pathconfig->program_full_path[0]) {
-        if (!change_ext(filename, pathconfig->program_full_path, L"._pth") &&
-            exists(filename))
-        {
-            return 1;
-        }
+    if (pathconfig->program_full_path[0] &&
+        !change_ext(filename, pathconfig->program_full_path, L"._pth") &&
+        exists(filename))
+    {
+        return 1;
     }
     return 0;
 }
@@ -994,9 +992,12 @@
     wchar_t zip_path[MAXPATHLEN+1];
     memset(zip_path, 0, sizeof(zip_path));
 
-    change_ext(zip_path,
-               calculate->dll_path[0] ? calculate->dll_path : pathconfig->program_full_path,
-               L".zip");
+    if (get_dllpath(zip_path) || change_ext(zip_path, zip_path, L".zip"))
+    {
+        if (change_ext(zip_path, pathconfig->program_full_path, L".zip")) {
+            zip_path[0] = L'\0';
+        }
+    }
 
     calculate_home_prefix(calculate, argv0_path, zip_path, prefix);
 
@@ -1033,11 +1034,6 @@
     calculate->home = pathconfig->home;
     calculate->path_env = _wgetenv(L"PATH");
 
-    calculate->dll_path = _Py_GetDLLPath();
-    if (calculate->dll_path == NULL) {
-        return _PyStatus_NO_MEMORY();
-    }
-
     calculate->pythonpath_env = config->pythonpath_env;
 
     return _PyStatus_OK();
@@ -1049,7 +1045,6 @@
 {
     PyMem_RawFree(calculate->machine_path);
     PyMem_RawFree(calculate->user_path);
-    PyMem_RawFree(calculate->dll_path);
 }
 
 
@@ -1059,7 +1054,6 @@
 
    - PyConfig.pythonpath_env: PYTHONPATH environment variable
    - _PyPathConfig.home: Py_SetPythonHome() or PYTHONHOME environment variable
-   - DLL path: _Py_GetDLLPath()
    - PATH environment variable
    - __PYVENV_LAUNCHER__ environment variable
    - GetModuleFileNameW(NULL): fully qualified path of the executable file of
@@ -1113,33 +1107,35 @@
 _Py_CheckPython3(void)
 {
     wchar_t py3path[MAXPATHLEN+1];
-    wchar_t *s;
     if (python3_checked) {
         return hPython3 != NULL;
     }
     python3_checked = 1;
 
     /* If there is a python3.dll next to the python3y.dll,
-       assume this is a build tree; use that DLL */
-    if (_Py_dll_path != NULL) {
-        wcscpy(py3path, _Py_dll_path);
+       use that DLL */
+    if (!get_dllpath(py3path)) {
+        reduce(py3path);
+        join(py3path, PY3_DLLNAME);
+        hPython3 = LoadLibraryExW(py3path, NULL, LOAD_LIBRARY_SEARCH_DEFAULT_DIRS);
+        if (hPython3 != NULL) {
+            return 1;
+        }
     }
-    else {
-        wcscpy(py3path, L"");
-    }
-    s = wcsrchr(py3path, L'\\');
-    if (!s) {
-        s = py3path;
-    }
-    wcscpy(s, L"\\python3.dll");
-    hPython3 = LoadLibraryExW(py3path, NULL, LOAD_WITH_ALTERED_SEARCH_PATH);
+
+    /* If we can locate python3.dll in our application dir,
+       use that DLL */
+    hPython3 = LoadLibraryExW(PY3_DLLNAME, NULL, LOAD_LIBRARY_SEARCH_APPLICATION_DIR);
     if (hPython3 != NULL) {
         return 1;
     }
 
-    /* Check sys.prefix\DLLs\python3.dll */
+    /* For back-compat, also search {sys.prefix}\DLLs, though
+       that has not been a normal install layout for a while */
     wcscpy(py3path, Py_GetPrefix());
-    wcscat(py3path, L"\\DLLs\\python3.dll");
-    hPython3 = LoadLibraryExW(py3path, NULL, LOAD_WITH_ALTERED_SEARCH_PATH);
+    if (py3path[0]) {
+        join(py3path, L"DLLs\\" PY3_DLLNAME);
+        hPython3 = LoadLibraryExW(py3path, NULL, LOAD_LIBRARY_SEARCH_DEFAULT_DIRS);
+    }
     return hPython3 != NULL;
 }