Skip to content

Commit

Permalink
bpo-29778: Ensure python3.dll is loaded from correct locations when P…
Browse files Browse the repository at this point in the history
…ython is embedded (GH-21297)

Also enables using debug build of `python3_d.dll`
Reference: CVE-2020-15523
  • Loading branch information
zooba committed Jul 6, 2020
1 parent deb0162 commit dcbaa1b
Show file tree
Hide file tree
Showing 8 changed files with 147 additions and 134 deletions.
31 changes: 24 additions & 7 deletions Lib/test/test_embed.py
Expand Up @@ -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():
Expand Down Expand Up @@ -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'])
Expand Down Expand Up @@ -603,15 +603,15 @@ 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)

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)

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -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:
Expand Down
@@ -0,0 +1,2 @@
Ensure :file:`python3.dll` is loaded from correct locations when Python is
embedded (CVE-2020-15523).
45 changes: 44 additions & 1 deletion Modules/_testinternalcapi.c
Expand Up @@ -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;
}


Expand Down
150 changes: 73 additions & 77 deletions PC/getpathp.c
Expand Up @@ -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;

Expand Down Expand Up @@ -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;
while (i > 0 && src[i] != '.' && !is_sep(src[i]))
--i;

if (i == 0) {
dest[0] = '\0';
return -1;
}
if (i == 0) {
dest[0] = '\0';
return -1;
}

if (is_sep(src[i])) {
i = src_len;
}

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';
}
}

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;
}
Expand Down Expand Up @@ -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.*/
Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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();
Expand All @@ -1049,7 +1045,6 @@ calculate_free(PyCalculatePath *calculate)
{
PyMem_RawFree(calculate->machine_path);
PyMem_RawFree(calculate->user_path);
PyMem_RawFree(calculate->dll_path);
}


Expand All @@ -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
Expand Down Expand Up @@ -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);
}
else {
wcscpy(py3path, L"");
}
s = wcsrchr(py3path, L'\\');
if (!s) {
s = py3path;
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;
}
}
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;
}

0 comments on commit dcbaa1b

Please sign in to comment.