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
This commit is contained in:
Steve Dower 2020-07-06 17:32:00 +01:00 committed by GitHub
parent deb016224c
commit dcbaa1b49c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 150 additions and 137 deletions

View File

@ -32,7 +32,7 @@ API_ISOLATED = 3
def debug_build(program): def debug_build(program):
program = os.path.basename(program) program = os.path.basename(program)
name = os.path.splitext(program)[0] name = os.path.splitext(program)[0]
return name.endswith("_d") return name.casefold().endswith("_d".casefold())
def remove_python_envvars(): def remove_python_envvars():
@ -568,7 +568,7 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
if expected['stdio_errors'] is self.GET_DEFAULT_CONFIG: if expected['stdio_errors'] is self.GET_DEFAULT_CONFIG:
expected['stdio_errors'] = 'surrogateescape' expected['stdio_errors'] = 'surrogateescape'
if sys.platform == 'win32': if MS_WINDOWS:
default_executable = self.test_exe default_executable = self.test_exe
elif expected['program_name'] is not self.GET_DEFAULT_CONFIG: elif expected['program_name'] is not self.GET_DEFAULT_CONFIG:
default_executable = os.path.abspath(expected['program_name']) default_executable = os.path.abspath(expected['program_name'])
@ -603,7 +603,7 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
pre_config = dict(configs['pre_config']) pre_config = dict(configs['pre_config'])
for key, value in list(expected.items()): for key, value in list(expected.items()):
if value is self.IGNORE_CONFIG: if value is self.IGNORE_CONFIG:
del pre_config[key] pre_config.pop(key, None)
del expected[key] del expected[key]
self.assertEqual(pre_config, expected) self.assertEqual(pre_config, expected)
@ -611,7 +611,7 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
config = dict(configs['config']) config = dict(configs['config'])
for key, value in list(expected.items()): for key, value in list(expected.items()):
if value is self.IGNORE_CONFIG: if value is self.IGNORE_CONFIG:
del config[key] config.pop(key, None)
del expected[key] del expected[key]
self.assertEqual(config, expected) self.assertEqual(config, expected)
@ -686,6 +686,7 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
self.check_pre_config(configs, expected_preconfig) self.check_pre_config(configs, expected_preconfig)
self.check_config(configs, expected_config) self.check_config(configs, expected_config)
self.check_global_config(configs) self.check_global_config(configs)
return configs
def test_init_default_config(self): def test_init_default_config(self):
self.check_all_configs("test_init_initialize_config", api=API_COMPAT) 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) self.default_program_name(config)
env = {'TESTPATH': os.path.pathsep.join(paths)} env = {'TESTPATH': os.path.pathsep.join(paths)}
self.check_all_configs("test_init_setpath", config, self.check_all_configs("test_init_setpath", config,
api=API_COMPAT, env=env, api=API_COMPAT, env=env,
ignore_stderr=True) ignore_stderr=True)
@ -1121,12 +1123,18 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
# Copy pythonXY.dll (or pythonXY_d.dll) # Copy pythonXY.dll (or pythonXY_d.dll)
ver = sys.version_info ver = sys.version_info
dll = f'python{ver.major}{ver.minor}' dll = f'python{ver.major}{ver.minor}'
dll3 = f'python{ver.major}'
if debug_build(sys.executable): if debug_build(sys.executable):
dll += '_d' dll += '_d'
dll3 += '_d'
dll += '.dll' dll += '.dll'
dll3 += '.dll'
dll = os.path.join(os.path.dirname(self.test_exe), 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)) 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(dll, dll_copy)
shutil.copyfile(dll3, dll3_copy)
# Copy Python program # Copy Python program
exec_copy = os.path.join(tmpdir, os.path.basename(self.test_exe)) 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['base_prefix'] = pyvenv_home
config['prefix'] = pyvenv_home config['prefix'] = pyvenv_home
env = self.copy_paths_by_env(config) env = self.copy_paths_by_env(config)
self.check_all_configs("test_init_compat_config", config, actual = self.check_all_configs("test_init_compat_config", config,
api=API_COMPAT, env=env, api=API_COMPAT, env=env,
ignore_stderr=True, cwd=tmpdir) 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): def test_global_pathconfig(self):
# Test C API functions getting the path configuration: # Test C API functions getting the path configuration:

View File

@ -0,0 +1,2 @@
Ensure :file:`python3.dll` is loaded from correct locations when Python is
embedded (CVE-2020-15523).

View File

@ -18,10 +18,53 @@
#include "pycore_gc.h" // PyGC_Head #include "pycore_gc.h" // PyGC_Head
#ifdef MS_WINDOWS
#include <windows.h>
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 * static PyObject *
get_configs(PyObject *self, PyObject *Py_UNUSED(args)) 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;
} }

View File

@ -131,8 +131,6 @@ typedef struct {
wchar_t *machine_path; /* from HKEY_LOCAL_MACHINE */ wchar_t *machine_path; /* from HKEY_LOCAL_MACHINE */
wchar_t *user_path; /* from HKEY_CURRENT_USER */ wchar_t *user_path; /* from HKEY_CURRENT_USER */
wchar_t *dll_path;
const wchar_t *pythonpath_env; const wchar_t *pythonpath_env;
} PyCalculatePath; } PyCalculatePath;
@ -168,27 +166,37 @@ reduce(wchar_t *dir)
static int static int
change_ext(wchar_t *dest, const wchar_t *src, const wchar_t *ext) change_ext(wchar_t *dest, const wchar_t *src, const wchar_t *ext)
{ {
size_t src_len = wcsnlen_s(src, MAXPATHLEN+1); if (src && src != dest) {
size_t i = src_len; size_t src_len = wcsnlen_s(src, MAXPATHLEN+1);
if (i >= MAXPATHLEN+1) { size_t i = src_len;
Py_FatalError("buffer overflow in getpathp.c's reduce()"); 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])) if (wcscat_s(dest, MAXPATHLEN+1, ext)) {
--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))
{
dest[0] = '\0'; dest[0] = '\0';
return -1; 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 #ifdef Py_ENABLE_SHARED
/* a string loaded from the DLL at startup.*/ /* a string loaded from the DLL at startup.*/
@ -468,27 +489,6 @@ done:
#endif /* Py_ENABLE_SHARED */ #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 static PyStatus
get_program_full_path(_PyPathConfig *pathconfig) get_program_full_path(_PyPathConfig *pathconfig)
{ {
@ -669,19 +669,17 @@ static int
get_pth_filename(PyCalculatePath *calculate, wchar_t *filename, get_pth_filename(PyCalculatePath *calculate, wchar_t *filename,
const _PyPathConfig *pathconfig) const _PyPathConfig *pathconfig)
{ {
if (calculate->dll_path[0]) { if (get_dllpath(filename) &&
if (!change_ext(filename, calculate->dll_path, L"._pth") && !change_ext(filename, filename, L"._pth") &&
exists(filename)) exists(filename))
{ {
return 1; return 1;
}
} }
if (pathconfig->program_full_path[0]) { if (pathconfig->program_full_path[0] &&
if (!change_ext(filename, pathconfig->program_full_path, L"._pth") && !change_ext(filename, pathconfig->program_full_path, L"._pth") &&
exists(filename)) exists(filename))
{ {
return 1; return 1;
}
} }
return 0; return 0;
} }
@ -994,9 +992,12 @@ calculate_path(PyCalculatePath *calculate, _PyPathConfig *pathconfig)
wchar_t zip_path[MAXPATHLEN+1]; wchar_t zip_path[MAXPATHLEN+1];
memset(zip_path, 0, sizeof(zip_path)); memset(zip_path, 0, sizeof(zip_path));
change_ext(zip_path, if (get_dllpath(zip_path) || change_ext(zip_path, zip_path, L".zip"))
calculate->dll_path[0] ? calculate->dll_path : pathconfig->program_full_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); calculate_home_prefix(calculate, argv0_path, zip_path, prefix);
@ -1033,11 +1034,6 @@ calculate_init(PyCalculatePath *calculate, _PyPathConfig *pathconfig,
calculate->home = pathconfig->home; calculate->home = pathconfig->home;
calculate->path_env = _wgetenv(L"PATH"); 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; calculate->pythonpath_env = config->pythonpath_env;
return _PyStatus_OK(); return _PyStatus_OK();
@ -1049,7 +1045,6 @@ calculate_free(PyCalculatePath *calculate)
{ {
PyMem_RawFree(calculate->machine_path); PyMem_RawFree(calculate->machine_path);
PyMem_RawFree(calculate->user_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 - PyConfig.pythonpath_env: PYTHONPATH environment variable
- _PyPathConfig.home: Py_SetPythonHome() or PYTHONHOME environment variable - _PyPathConfig.home: Py_SetPythonHome() or PYTHONHOME environment variable
- DLL path: _Py_GetDLLPath()
- PATH environment variable - PATH environment variable
- __PYVENV_LAUNCHER__ environment variable - __PYVENV_LAUNCHER__ environment variable
- GetModuleFileNameW(NULL): fully qualified path of the executable file of - GetModuleFileNameW(NULL): fully qualified path of the executable file of
@ -1113,33 +1107,35 @@ int
_Py_CheckPython3(void) _Py_CheckPython3(void)
{ {
wchar_t py3path[MAXPATHLEN+1]; wchar_t py3path[MAXPATHLEN+1];
wchar_t *s;
if (python3_checked) { if (python3_checked) {
return hPython3 != NULL; return hPython3 != NULL;
} }
python3_checked = 1; python3_checked = 1;
/* If there is a python3.dll next to the python3y.dll, /* If there is a python3.dll next to the python3y.dll,
assume this is a build tree; use that DLL */ use that DLL */
if (_Py_dll_path != NULL) { if (!get_dllpath(py3path)) {
wcscpy(py3path, _Py_dll_path); reduce(py3path);
join(py3path, PY3_DLLNAME);
hPython3 = LoadLibraryExW(py3path, NULL, LOAD_LIBRARY_SEARCH_DEFAULT_DIRS);
if (hPython3 != NULL) {
return 1;
}
} }
else {
wcscpy(py3path, L""); /* If we can locate python3.dll in our application dir,
} use that DLL */
s = wcsrchr(py3path, L'\\'); hPython3 = LoadLibraryExW(PY3_DLLNAME, NULL, LOAD_LIBRARY_SEARCH_APPLICATION_DIR);
if (!s) {
s = py3path;
}
wcscpy(s, L"\\python3.dll");
hPython3 = LoadLibraryExW(py3path, NULL, LOAD_WITH_ALTERED_SEARCH_PATH);
if (hPython3 != NULL) { if (hPython3 != NULL) {
return 1; 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()); wcscpy(py3path, Py_GetPrefix());
wcscat(py3path, L"\\DLLs\\python3.dll"); if (py3path[0]) {
hPython3 = LoadLibraryExW(py3path, NULL, LOAD_WITH_ALTERED_SEARCH_PATH); join(py3path, L"DLLs\\" PY3_DLLNAME);
hPython3 = LoadLibraryExW(py3path, NULL, LOAD_LIBRARY_SEARCH_DEFAULT_DIRS);
}
return hPython3 != NULL; return hPython3 != NULL;
} }

View File

@ -26,11 +26,12 @@
<_PlatformPreprocessorDefinition>_WIN32;</_PlatformPreprocessorDefinition> <_PlatformPreprocessorDefinition>_WIN32;</_PlatformPreprocessorDefinition>
<_PlatformPreprocessorDefinition Condition="$(Platform) == 'x64'">_WIN64;_M_X64;</_PlatformPreprocessorDefinition> <_PlatformPreprocessorDefinition Condition="$(Platform) == 'x64'">_WIN64;_M_X64;</_PlatformPreprocessorDefinition>
<_PydPreprocessorDefinition Condition="$(TargetExt) == '.pyd'">Py_BUILD_CORE_MODULE;</_PydPreprocessorDefinition> <_PydPreprocessorDefinition Condition="$(TargetExt) == '.pyd'">Py_BUILD_CORE_MODULE;</_PydPreprocessorDefinition>
<_Py3NamePreprocessorDefinition>PY3_DLLNAME=L"$(Py3DllName)";</_Py3NamePreprocessorDefinition>
</PropertyGroup> </PropertyGroup>
<ItemDefinitionGroup> <ItemDefinitionGroup>
<ClCompile> <ClCompile>
<AdditionalIncludeDirectories>$(PySourcePath)Include;$(PySourcePath)Include\internal;$(PySourcePath)PC;$(IntDir);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> <AdditionalIncludeDirectories>$(PySourcePath)Include;$(PySourcePath)Include\internal;$(PySourcePath)PC;$(IntDir);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>WIN32;$(_PlatformPreprocessorDefinition)$(_DebugPreprocessorDefinition)$(_PydPreprocessorDefinition)%(PreprocessorDefinitions)</PreprocessorDefinitions> <PreprocessorDefinitions>WIN32;$(_Py3NamePreprocessorDefinition);$(_PlatformPreprocessorDefinition)$(_DebugPreprocessorDefinition)$(_PydPreprocessorDefinition)%(PreprocessorDefinitions)</PreprocessorDefinitions>
<Optimization>MaxSpeed</Optimization> <Optimization>MaxSpeed</Optimization>
<IntrinsicFunctions>true</IntrinsicFunctions> <IntrinsicFunctions>true</IntrinsicFunctions>

View File

@ -203,6 +203,8 @@
<!-- The name of the resulting pythonXY.dll (without the extension) --> <!-- The name of the resulting pythonXY.dll (without the extension) -->
<PyDllName>python$(MajorVersionNumber)$(MinorVersionNumber)$(PyDebugExt)</PyDllName> <PyDllName>python$(MajorVersionNumber)$(MinorVersionNumber)$(PyDebugExt)</PyDllName>
<!-- The name of the resulting pythonX.dll (without the extension) -->
<Py3DllName>python3$(PyDebugExt)</Py3DllName>
<!-- The version and platform tag to include in .pyd filenames --> <!-- The version and platform tag to include in .pyd filenames -->
<PydTag Condition="$(ArchName) == 'win32'">.cp$(MajorVersionNumber)$(MinorVersionNumber)-win32</PydTag> <PydTag Condition="$(ArchName) == 'win32'">.cp$(MajorVersionNumber)$(MinorVersionNumber)-win32</PydTag>

View File

@ -168,9 +168,7 @@ dl_funcptr _PyImport_FindSharedFuncptrWindows(const char *prefix,
char funcname[258], *import_python; char funcname[258], *import_python;
const wchar_t *wpathname; const wchar_t *wpathname;
#ifndef _DEBUG
_Py_CheckPython3(); _Py_CheckPython3();
#endif
wpathname = _PyUnicode_AsUnicode(pathname); wpathname = _PyUnicode_AsUnicode(pathname);
if (wpathname == NULL) if (wpathname == NULL)

View File

@ -17,9 +17,6 @@ extern "C" {
_PyPathConfig _Py_path_config = _PyPathConfig_INIT; _PyPathConfig _Py_path_config = _PyPathConfig_INIT;
#ifdef MS_WINDOWS
wchar_t *_Py_dll_path = NULL;
#endif
static int static int
@ -107,10 +104,6 @@ _PyPathConfig_ClearGlobal(void)
_PyMem_SetDefaultAllocator(PYMEM_DOMAIN_RAW, &old_alloc); _PyMem_SetDefaultAllocator(PYMEM_DOMAIN_RAW, &old_alloc);
pathconfig_clear(&_Py_path_config); 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); 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 static PyStatus
pathconfig_set_from_config(_PyPathConfig *pathconfig, const PyConfig *config) pathconfig_set_from_config(_PyPathConfig *pathconfig, const PyConfig *config)
{ {
@ -222,13 +190,6 @@ done:
PyStatus PyStatus
_PyConfig_WritePathConfig(const PyConfig *config) _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); return pathconfig_set_from_config(&_Py_path_config, config);
} }
@ -455,13 +416,6 @@ pathconfig_global_init(void)
{ {
PyStatus status; PyStatus status;
#ifdef MS_WINDOWS
status = _PyPathConfig_InitDLLPath();
if (_PyStatus_EXCEPTION(status)) {
Py_ExitStatusException(status);
}
#endif
if (_Py_path_config.module_search_path == NULL) { if (_Py_path_config.module_search_path == NULL) {
status = pathconfig_global_read(&_Py_path_config); status = pathconfig_global_read(&_Py_path_config);
if (_PyStatus_EXCEPTION(status)) { if (_PyStatus_EXCEPTION(status)) {