diff --git a/Lib/test/test_embed.py b/Lib/test/test_embed.py index 174892a22b4..44d2596d9eb 100644 --- a/Lib/test/test_embed.py +++ b/Lib/test/test_embed.py @@ -32,7 +32,7 @@ API_ISOLATED = 3 def debug_build(program): program = os.path.basename(program) name = os.path.splitext(program)[0] - return name.endswith("_d") + return name.casefold().endswith("_d".casefold()) def remove_python_envvars(): @@ -568,7 +568,7 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase): if expected['stdio_errors'] is self.GET_DEFAULT_CONFIG: expected['stdio_errors'] = 'surrogateescape' - if sys.platform == 'win32': + if MS_WINDOWS: default_executable = self.test_exe elif expected['program_name'] is not self.GET_DEFAULT_CONFIG: default_executable = os.path.abspath(expected['program_name']) @@ -603,7 +603,7 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase): pre_config = dict(configs['pre_config']) for key, value in list(expected.items()): if value is self.IGNORE_CONFIG: - del pre_config[key] + pre_config.pop(key, None) del expected[key] self.assertEqual(pre_config, expected) @@ -611,7 +611,7 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase): config = dict(configs['config']) for key, value in list(expected.items()): if value is self.IGNORE_CONFIG: - del config[key] + config.pop(key, None) del expected[key] self.assertEqual(config, expected) @@ -686,6 +686,7 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase): self.check_pre_config(configs, expected_preconfig) self.check_config(configs, expected_config) self.check_global_config(configs) + return configs def test_init_default_config(self): self.check_all_configs("test_init_initialize_config", api=API_COMPAT) @@ -1064,6 +1065,7 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase): } self.default_program_name(config) env = {'TESTPATH': os.path.pathsep.join(paths)} + self.check_all_configs("test_init_setpath", config, api=API_COMPAT, env=env, ignore_stderr=True) @@ -1121,12 +1123,18 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase): # Copy pythonXY.dll (or pythonXY_d.dll) ver = sys.version_info dll = f'python{ver.major}{ver.minor}' + dll3 = f'python{ver.major}' if debug_build(sys.executable): dll += '_d' + dll3 += '_d' dll += '.dll' + dll3 += '.dll' dll = os.path.join(os.path.dirname(self.test_exe), dll) + dll3 = os.path.join(os.path.dirname(self.test_exe), dll3) dll_copy = os.path.join(tmpdir, os.path.basename(dll)) + dll3_copy = os.path.join(tmpdir, os.path.basename(dll3)) shutil.copyfile(dll, dll_copy) + shutil.copyfile(dll3, dll3_copy) # Copy Python program exec_copy = os.path.join(tmpdir, os.path.basename(self.test_exe)) @@ -1254,9 +1262,18 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase): config['base_prefix'] = pyvenv_home config['prefix'] = pyvenv_home env = self.copy_paths_by_env(config) - self.check_all_configs("test_init_compat_config", config, - api=API_COMPAT, env=env, - ignore_stderr=True, cwd=tmpdir) + actual = self.check_all_configs("test_init_compat_config", config, + api=API_COMPAT, env=env, + ignore_stderr=True, cwd=tmpdir) + if MS_WINDOWS: + self.assertEqual( + actual['windows']['python3_dll'], + os.path.join( + tmpdir, + os.path.basename(self.EXPECTED_CONFIG['windows']['python3_dll']) + ) + ) + def test_global_pathconfig(self): # Test C API functions getting the path configuration: diff --git a/Misc/NEWS.d/next/Security/2020-07-03-17-21-37.bpo-29778.cR_fGS.rst b/Misc/NEWS.d/next/Security/2020-07-03-17-21-37.bpo-29778.cR_fGS.rst new file mode 100644 index 00000000000..998ffb1ee66 --- /dev/null +++ b/Misc/NEWS.d/next/Security/2020-07-03-17-21-37.bpo-29778.cR_fGS.rst @@ -0,0 +1,2 @@ +Ensure :file:`python3.dll` is loaded from correct locations when Python is +embedded (CVE-2020-15523). diff --git a/Modules/_testinternalcapi.c b/Modules/_testinternalcapi.c index ad74af8363e..f08bf8d83d4 100644 --- a/Modules/_testinternalcapi.c +++ b/Modules/_testinternalcapi.c @@ -18,10 +18,53 @@ #include "pycore_gc.h" // PyGC_Head +#ifdef MS_WINDOWS +#include + +static int +_add_windows_config(PyObject *configs) +{ + HMODULE hPython3; + wchar_t py3path[MAX_PATH]; + PyObject *dict = PyDict_New(); + PyObject *obj = NULL; + if (!dict) { + return -1; + } + + hPython3 = GetModuleHandleW(PY3_DLLNAME); + if (hPython3 && GetModuleFileNameW(hPython3, py3path, MAX_PATH)) { + obj = PyUnicode_FromWideChar(py3path, -1); + } else { + obj = Py_None; + Py_INCREF(obj); + } + if (obj && + !PyDict_SetItemString(dict, "python3_dll", obj) && + !PyDict_SetItemString(configs, "windows", dict)) { + Py_DECREF(obj); + Py_DECREF(dict); + return 0; + } + Py_DECREF(obj); + Py_DECREF(dict); + return -1; +} +#endif + + static PyObject * get_configs(PyObject *self, PyObject *Py_UNUSED(args)) { - return _Py_GetConfigsAsDict(); + PyObject *dict = _Py_GetConfigsAsDict(); +#ifdef MS_WINDOWS + if (dict) { + if (_add_windows_config(dict) < 0) { + Py_CLEAR(dict); + } + } +#endif + return dict; } diff --git a/PC/getpathp.c b/PC/getpathp.c index fd5cfa7e1a8..0939c5fa984 100644 --- a/PC/getpathp.c +++ b/PC/getpathp.c @@ -131,8 +131,6 @@ typedef struct { 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 @@ reduce(wchar_t *dir) 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 @@ search_for_prefix(wchar_t *prefix, const wchar_t *argv0_path, const wchar_t *lan } +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 @@ done: #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 @@ static int 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 @@ calculate_path(PyCalculatePath *calculate, _PyPathConfig *pathconfig) 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_init(PyCalculatePath *calculate, _PyPathConfig *pathconfig, 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 @@ calculate_free(PyCalculatePath *calculate) { PyMem_RawFree(calculate->machine_path); PyMem_RawFree(calculate->user_path); - PyMem_RawFree(calculate->dll_path); } @@ -1059,7 +1054,6 @@ calculate_free(PyCalculatePath *calculate) - 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 @@ int _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; } diff --git a/PCbuild/pyproject.props b/PCbuild/pyproject.props index 94a01ff5ca8..c659d14ff8d 100644 --- a/PCbuild/pyproject.props +++ b/PCbuild/pyproject.props @@ -26,11 +26,12 @@ <_PlatformPreprocessorDefinition>_WIN32; <_PlatformPreprocessorDefinition Condition="$(Platform) == 'x64'">_WIN64;_M_X64; <_PydPreprocessorDefinition Condition="$(TargetExt) == '.pyd'">Py_BUILD_CORE_MODULE; + <_Py3NamePreprocessorDefinition>PY3_DLLNAME=L"$(Py3DllName)"; $(PySourcePath)Include;$(PySourcePath)Include\internal;$(PySourcePath)PC;$(IntDir);%(AdditionalIncludeDirectories) - WIN32;$(_PlatformPreprocessorDefinition)$(_DebugPreprocessorDefinition)$(_PydPreprocessorDefinition)%(PreprocessorDefinitions) + WIN32;$(_Py3NamePreprocessorDefinition);$(_PlatformPreprocessorDefinition)$(_DebugPreprocessorDefinition)$(_PydPreprocessorDefinition)%(PreprocessorDefinitions) MaxSpeed true diff --git a/PCbuild/python.props b/PCbuild/python.props index 6388d1b6426..bf6f3716ba5 100644 --- a/PCbuild/python.props +++ b/PCbuild/python.props @@ -203,6 +203,8 @@ python$(MajorVersionNumber)$(MinorVersionNumber)$(PyDebugExt) + + python3$(PyDebugExt) .cp$(MajorVersionNumber)$(MinorVersionNumber)-win32 diff --git a/Python/dynload_win.c b/Python/dynload_win.c index 2bf3384b9eb..8431c5b3b2f 100644 --- a/Python/dynload_win.c +++ b/Python/dynload_win.c @@ -168,9 +168,7 @@ dl_funcptr _PyImport_FindSharedFuncptrWindows(const char *prefix, char funcname[258], *import_python; const wchar_t *wpathname; -#ifndef _DEBUG _Py_CheckPython3(); -#endif wpathname = _PyUnicode_AsUnicode(pathname); if (wpathname == NULL) diff --git a/Python/pathconfig.c b/Python/pathconfig.c index 5c38041d766..9a302213e77 100644 --- a/Python/pathconfig.c +++ b/Python/pathconfig.c @@ -17,9 +17,6 @@ extern "C" { _PyPathConfig _Py_path_config = _PyPathConfig_INIT; -#ifdef MS_WINDOWS -wchar_t *_Py_dll_path = NULL; -#endif static int @@ -107,10 +104,6 @@ _PyPathConfig_ClearGlobal(void) _PyMem_SetDefaultAllocator(PYMEM_DOMAIN_RAW, &old_alloc); pathconfig_clear(&_Py_path_config); -#ifdef MS_WINDOWS - PyMem_RawFree(_Py_dll_path); - _Py_dll_path = NULL; -#endif PyMem_SetAllocator(PYMEM_DOMAIN_RAW, &old_alloc); } @@ -147,31 +140,6 @@ _PyWideStringList_Join(const PyWideStringList *list, wchar_t sep) } -#ifdef MS_WINDOWS -/* Initialize _Py_dll_path on Windows. Do nothing on other platforms. */ -static PyStatus -_PyPathConfig_InitDLLPath(void) -{ - if (_Py_dll_path != NULL) { - /* Already set: nothing to do */ - return _PyStatus_OK(); - } - - PyMemAllocatorEx old_alloc; - _PyMem_SetDefaultAllocator(PYMEM_DOMAIN_RAW, &old_alloc); - - _Py_dll_path = _Py_GetDLLPath(); - - PyMem_SetAllocator(PYMEM_DOMAIN_RAW, &old_alloc); - - if (_Py_dll_path == NULL) { - return _PyStatus_NO_MEMORY(); - } - return _PyStatus_OK(); -} -#endif - - static PyStatus pathconfig_set_from_config(_PyPathConfig *pathconfig, const PyConfig *config) { @@ -222,13 +190,6 @@ done: PyStatus _PyConfig_WritePathConfig(const PyConfig *config) { -#ifdef MS_WINDOWS - PyStatus status = _PyPathConfig_InitDLLPath(); - if (_PyStatus_EXCEPTION(status)) { - return status; - } -#endif - return pathconfig_set_from_config(&_Py_path_config, config); } @@ -455,13 +416,6 @@ pathconfig_global_init(void) { PyStatus status; -#ifdef MS_WINDOWS - status = _PyPathConfig_InitDLLPath(); - if (_PyStatus_EXCEPTION(status)) { - Py_ExitStatusException(status); - } -#endif - if (_Py_path_config.module_search_path == NULL) { status = pathconfig_global_read(&_Py_path_config); if (_PyStatus_EXCEPTION(status)) {