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 @@
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 @@ def get_expected_config(self, expected_preconfig, expected, env, api,
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 @@ def check_pre_config(self, configs, expected):
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 @@ def check_config(self, configs, expected):
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 @@ def check_all_configs(self, testname, expected_config=None,
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 @@ def test_init_setpath(self):
}
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 @@ def tmpdir_with_python(self):
# 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 @@ def test_init_pyvenv_cfg(self):
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:

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
#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 *
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 *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 @@ getpythonregpath(HKEY keyBase, int skipcore)
#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;
}

View file

@ -26,11 +26,12 @@
<_PlatformPreprocessorDefinition>_WIN32;</_PlatformPreprocessorDefinition>
<_PlatformPreprocessorDefinition Condition="$(Platform) == 'x64'">_WIN64;_M_X64;</_PlatformPreprocessorDefinition>
<_PydPreprocessorDefinition Condition="$(TargetExt) == '.pyd'">Py_BUILD_CORE_MODULE;</_PydPreprocessorDefinition>
<_Py3NamePreprocessorDefinition>PY3_DLLNAME=L"$(Py3DllName)";</_Py3NamePreprocessorDefinition>
</PropertyGroup>
<ItemDefinitionGroup>
<ClCompile>
<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>
<IntrinsicFunctions>true</IntrinsicFunctions>

View file

@ -203,6 +203,8 @@
<!-- The name of the resulting pythonXY.dll (without the extension) -->
<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 -->
<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;
const wchar_t *wpathname;
#ifndef _DEBUG
_Py_CheckPython3();
#endif
wpathname = _PyUnicode_AsUnicode(pathname);
if (wpathname == NULL)

View file

@ -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 @@ pathconfig_set_from_config(_PyPathConfig *pathconfig, const PyConfig *config)
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)) {