Skip to content

Commit dcbaa1b

Browse files
authored
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
1 parent deb0162 commit dcbaa1b

File tree

8 files changed

+147
-134
lines changed

8 files changed

+147
-134
lines changed

Lib/test/test_embed.py

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
def debug_build(program):
3333
program = os.path.basename(program)
3434
name = os.path.splitext(program)[0]
35-
return name.endswith("_d")
35+
return name.casefold().endswith("_d".casefold())
3636

3737

3838
def remove_python_envvars():
@@ -568,7 +568,7 @@ def get_expected_config(self, expected_preconfig, expected, env, api,
568568
if expected['stdio_errors'] is self.GET_DEFAULT_CONFIG:
569569
expected['stdio_errors'] = 'surrogateescape'
570570

571-
if sys.platform == 'win32':
571+
if MS_WINDOWS:
572572
default_executable = self.test_exe
573573
elif expected['program_name'] is not self.GET_DEFAULT_CONFIG:
574574
default_executable = os.path.abspath(expected['program_name'])
@@ -603,15 +603,15 @@ def check_pre_config(self, configs, expected):
603603
pre_config = dict(configs['pre_config'])
604604
for key, value in list(expected.items()):
605605
if value is self.IGNORE_CONFIG:
606-
del pre_config[key]
606+
pre_config.pop(key, None)
607607
del expected[key]
608608
self.assertEqual(pre_config, expected)
609609

610610
def check_config(self, configs, expected):
611611
config = dict(configs['config'])
612612
for key, value in list(expected.items()):
613613
if value is self.IGNORE_CONFIG:
614-
del config[key]
614+
config.pop(key, None)
615615
del expected[key]
616616
self.assertEqual(config, expected)
617617

@@ -686,6 +686,7 @@ def check_all_configs(self, testname, expected_config=None,
686686
self.check_pre_config(configs, expected_preconfig)
687687
self.check_config(configs, expected_config)
688688
self.check_global_config(configs)
689+
return configs
689690

690691
def test_init_default_config(self):
691692
self.check_all_configs("test_init_initialize_config", api=API_COMPAT)
@@ -1064,6 +1065,7 @@ def test_init_setpath(self):
10641065
}
10651066
self.default_program_name(config)
10661067
env = {'TESTPATH': os.path.pathsep.join(paths)}
1068+
10671069
self.check_all_configs("test_init_setpath", config,
10681070
api=API_COMPAT, env=env,
10691071
ignore_stderr=True)
@@ -1121,12 +1123,18 @@ def tmpdir_with_python(self):
11211123
# Copy pythonXY.dll (or pythonXY_d.dll)
11221124
ver = sys.version_info
11231125
dll = f'python{ver.major}{ver.minor}'
1126+
dll3 = f'python{ver.major}'
11241127
if debug_build(sys.executable):
11251128
dll += '_d'
1129+
dll3 += '_d'
11261130
dll += '.dll'
1131+
dll3 += '.dll'
11271132
dll = os.path.join(os.path.dirname(self.test_exe), dll)
1133+
dll3 = os.path.join(os.path.dirname(self.test_exe), dll3)
11281134
dll_copy = os.path.join(tmpdir, os.path.basename(dll))
1135+
dll3_copy = os.path.join(tmpdir, os.path.basename(dll3))
11291136
shutil.copyfile(dll, dll_copy)
1137+
shutil.copyfile(dll3, dll3_copy)
11301138

11311139
# Copy Python program
11321140
exec_copy = os.path.join(tmpdir, os.path.basename(self.test_exe))
@@ -1254,9 +1262,18 @@ def test_init_pyvenv_cfg(self):
12541262
config['base_prefix'] = pyvenv_home
12551263
config['prefix'] = pyvenv_home
12561264
env = self.copy_paths_by_env(config)
1257-
self.check_all_configs("test_init_compat_config", config,
1258-
api=API_COMPAT, env=env,
1259-
ignore_stderr=True, cwd=tmpdir)
1265+
actual = self.check_all_configs("test_init_compat_config", config,
1266+
api=API_COMPAT, env=env,
1267+
ignore_stderr=True, cwd=tmpdir)
1268+
if MS_WINDOWS:
1269+
self.assertEqual(
1270+
actual['windows']['python3_dll'],
1271+
os.path.join(
1272+
tmpdir,
1273+
os.path.basename(self.EXPECTED_CONFIG['windows']['python3_dll'])
1274+
)
1275+
)
1276+
12601277

12611278
def test_global_pathconfig(self):
12621279
# Test C API functions getting the path configuration:
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Ensure :file:`python3.dll` is loaded from correct locations when Python is
2+
embedded (CVE-2020-15523).

Modules/_testinternalcapi.c

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,53 @@
1818
#include "pycore_gc.h" // PyGC_Head
1919

2020

21+
#ifdef MS_WINDOWS
22+
#include <windows.h>
23+
24+
static int
25+
_add_windows_config(PyObject *configs)
26+
{
27+
HMODULE hPython3;
28+
wchar_t py3path[MAX_PATH];
29+
PyObject *dict = PyDict_New();
30+
PyObject *obj = NULL;
31+
if (!dict) {
32+
return -1;
33+
}
34+
35+
hPython3 = GetModuleHandleW(PY3_DLLNAME);
36+
if (hPython3 && GetModuleFileNameW(hPython3, py3path, MAX_PATH)) {
37+
obj = PyUnicode_FromWideChar(py3path, -1);
38+
} else {
39+
obj = Py_None;
40+
Py_INCREF(obj);
41+
}
42+
if (obj &&
43+
!PyDict_SetItemString(dict, "python3_dll", obj) &&
44+
!PyDict_SetItemString(configs, "windows", dict)) {
45+
Py_DECREF(obj);
46+
Py_DECREF(dict);
47+
return 0;
48+
}
49+
Py_DECREF(obj);
50+
Py_DECREF(dict);
51+
return -1;
52+
}
53+
#endif
54+
55+
2156
static PyObject *
2257
get_configs(PyObject *self, PyObject *Py_UNUSED(args))
2358
{
24-
return _Py_GetConfigsAsDict();
59+
PyObject *dict = _Py_GetConfigsAsDict();
60+
#ifdef MS_WINDOWS
61+
if (dict) {
62+
if (_add_windows_config(dict) < 0) {
63+
Py_CLEAR(dict);
64+
}
65+
}
66+
#endif
67+
return dict;
2568
}
2669

2770

PC/getpathp.c

Lines changed: 73 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -131,8 +131,6 @@ typedef struct {
131131
wchar_t *machine_path; /* from HKEY_LOCAL_MACHINE */
132132
wchar_t *user_path; /* from HKEY_CURRENT_USER */
133133

134-
wchar_t *dll_path;
135-
136134
const wchar_t *pythonpath_env;
137135
} PyCalculatePath;
138136

@@ -168,27 +166,37 @@ reduce(wchar_t *dir)
168166
static int
169167
change_ext(wchar_t *dest, const wchar_t *src, const wchar_t *ext)
170168
{
171-
size_t src_len = wcsnlen_s(src, MAXPATHLEN+1);
172-
size_t i = src_len;
173-
if (i >= MAXPATHLEN+1) {
174-
Py_FatalError("buffer overflow in getpathp.c's reduce()");
175-
}
169+
if (src && src != dest) {
170+
size_t src_len = wcsnlen_s(src, MAXPATHLEN+1);
171+
size_t i = src_len;
172+
if (i >= MAXPATHLEN+1) {
173+
Py_FatalError("buffer overflow in getpathp.c's reduce()");
174+
}
176175

177-
while (i > 0 && src[i] != '.' && !is_sep(src[i]))
178-
--i;
176+
while (i > 0 && src[i] != '.' && !is_sep(src[i]))
177+
--i;
179178

180-
if (i == 0) {
181-
dest[0] = '\0';
182-
return -1;
183-
}
179+
if (i == 0) {
180+
dest[0] = '\0';
181+
return -1;
182+
}
183+
184+
if (is_sep(src[i])) {
185+
i = src_len;
186+
}
184187

185-
if (is_sep(src[i])) {
186-
i = src_len;
188+
if (wcsncpy_s(dest, MAXPATHLEN+1, src, i)) {
189+
dest[0] = '\0';
190+
return -1;
191+
}
192+
} else {
193+
wchar_t *s = wcsrchr(dest, L'.');
194+
if (s) {
195+
s[0] = '\0';
196+
}
187197
}
188198

189-
if (wcsncpy_s(dest, MAXPATHLEN+1, src, i) ||
190-
wcscat_s(dest, MAXPATHLEN+1, ext))
191-
{
199+
if (wcscat_s(dest, MAXPATHLEN+1, ext)) {
192200
dest[0] = '\0';
193201
return -1;
194202
}
@@ -297,6 +305,19 @@ search_for_prefix(wchar_t *prefix, const wchar_t *argv0_path, const wchar_t *lan
297305
}
298306

299307

308+
static int
309+
get_dllpath(wchar_t *dllpath)
310+
{
311+
#ifdef Py_ENABLE_SHARED
312+
extern HANDLE PyWin_DLLhModule;
313+
if (PyWin_DLLhModule && GetModuleFileNameW(PyWin_DLLhModule, dllpath, MAXPATHLEN)) {
314+
return 0;
315+
}
316+
#endif
317+
return -1;
318+
}
319+
320+
300321
#ifdef Py_ENABLE_SHARED
301322

302323
/* a string loaded from the DLL at startup.*/
@@ -468,27 +489,6 @@ getpythonregpath(HKEY keyBase, int skipcore)
468489
#endif /* Py_ENABLE_SHARED */
469490

470491

471-
wchar_t*
472-
_Py_GetDLLPath(void)
473-
{
474-
wchar_t dll_path[MAXPATHLEN+1];
475-
memset(dll_path, 0, sizeof(dll_path));
476-
477-
#ifdef Py_ENABLE_SHARED
478-
extern HANDLE PyWin_DLLhModule;
479-
if (PyWin_DLLhModule) {
480-
if (!GetModuleFileNameW(PyWin_DLLhModule, dll_path, MAXPATHLEN)) {
481-
dll_path[0] = 0;
482-
}
483-
}
484-
#else
485-
dll_path[0] = 0;
486-
#endif
487-
488-
return _PyMem_RawWcsdup(dll_path);
489-
}
490-
491-
492492
static PyStatus
493493
get_program_full_path(_PyPathConfig *pathconfig)
494494
{
@@ -669,19 +669,17 @@ static int
669669
get_pth_filename(PyCalculatePath *calculate, wchar_t *filename,
670670
const _PyPathConfig *pathconfig)
671671
{
672-
if (calculate->dll_path[0]) {
673-
if (!change_ext(filename, calculate->dll_path, L"._pth") &&
674-
exists(filename))
675-
{
676-
return 1;
677-
}
672+
if (get_dllpath(filename) &&
673+
!change_ext(filename, filename, L"._pth") &&
674+
exists(filename))
675+
{
676+
return 1;
678677
}
679-
if (pathconfig->program_full_path[0]) {
680-
if (!change_ext(filename, pathconfig->program_full_path, L"._pth") &&
681-
exists(filename))
682-
{
683-
return 1;
684-
}
678+
if (pathconfig->program_full_path[0] &&
679+
!change_ext(filename, pathconfig->program_full_path, L"._pth") &&
680+
exists(filename))
681+
{
682+
return 1;
685683
}
686684
return 0;
687685
}
@@ -994,9 +992,12 @@ calculate_path(PyCalculatePath *calculate, _PyPathConfig *pathconfig)
994992
wchar_t zip_path[MAXPATHLEN+1];
995993
memset(zip_path, 0, sizeof(zip_path));
996994

997-
change_ext(zip_path,
998-
calculate->dll_path[0] ? calculate->dll_path : pathconfig->program_full_path,
999-
L".zip");
995+
if (get_dllpath(zip_path) || change_ext(zip_path, zip_path, L".zip"))
996+
{
997+
if (change_ext(zip_path, pathconfig->program_full_path, L".zip")) {
998+
zip_path[0] = L'\0';
999+
}
1000+
}
10001001

10011002
calculate_home_prefix(calculate, argv0_path, zip_path, prefix);
10021003

@@ -1033,11 +1034,6 @@ calculate_init(PyCalculatePath *calculate, _PyPathConfig *pathconfig,
10331034
calculate->home = pathconfig->home;
10341035
calculate->path_env = _wgetenv(L"PATH");
10351036

1036-
calculate->dll_path = _Py_GetDLLPath();
1037-
if (calculate->dll_path == NULL) {
1038-
return _PyStatus_NO_MEMORY();
1039-
}
1040-
10411037
calculate->pythonpath_env = config->pythonpath_env;
10421038

10431039
return _PyStatus_OK();
@@ -1049,7 +1045,6 @@ calculate_free(PyCalculatePath *calculate)
10491045
{
10501046
PyMem_RawFree(calculate->machine_path);
10511047
PyMem_RawFree(calculate->user_path);
1052-
PyMem_RawFree(calculate->dll_path);
10531048
}
10541049

10551050

@@ -1059,7 +1054,6 @@ calculate_free(PyCalculatePath *calculate)
10591054
10601055
- PyConfig.pythonpath_env: PYTHONPATH environment variable
10611056
- _PyPathConfig.home: Py_SetPythonHome() or PYTHONHOME environment variable
1062-
- DLL path: _Py_GetDLLPath()
10631057
- PATH environment variable
10641058
- __PYVENV_LAUNCHER__ environment variable
10651059
- GetModuleFileNameW(NULL): fully qualified path of the executable file of
@@ -1113,33 +1107,35 @@ int
11131107
_Py_CheckPython3(void)
11141108
{
11151109
wchar_t py3path[MAXPATHLEN+1];
1116-
wchar_t *s;
11171110
if (python3_checked) {
11181111
return hPython3 != NULL;
11191112
}
11201113
python3_checked = 1;
11211114

11221115
/* If there is a python3.dll next to the python3y.dll,
1123-
assume this is a build tree; use that DLL */
1124-
if (_Py_dll_path != NULL) {
1125-
wcscpy(py3path, _Py_dll_path);
1126-
}
1127-
else {
1128-
wcscpy(py3path, L"");
1129-
}
1130-
s = wcsrchr(py3path, L'\\');
1131-
if (!s) {
1132-
s = py3path;
1116+
use that DLL */
1117+
if (!get_dllpath(py3path)) {
1118+
reduce(py3path);
1119+
join(py3path, PY3_DLLNAME);
1120+
hPython3 = LoadLibraryExW(py3path, NULL, LOAD_LIBRARY_SEARCH_DEFAULT_DIRS);
1121+
if (hPython3 != NULL) {
1122+
return 1;
1123+
}
11331124
}
1134-
wcscpy(s, L"\\python3.dll");
1135-
hPython3 = LoadLibraryExW(py3path, NULL, LOAD_WITH_ALTERED_SEARCH_PATH);
1125+
1126+
/* If we can locate python3.dll in our application dir,
1127+
use that DLL */
1128+
hPython3 = LoadLibraryExW(PY3_DLLNAME, NULL, LOAD_LIBRARY_SEARCH_APPLICATION_DIR);
11361129
if (hPython3 != NULL) {
11371130
return 1;
11381131
}
11391132

1140-
/* Check sys.prefix\DLLs\python3.dll */
1133+
/* For back-compat, also search {sys.prefix}\DLLs, though
1134+
that has not been a normal install layout for a while */
11411135
wcscpy(py3path, Py_GetPrefix());
1142-
wcscat(py3path, L"\\DLLs\\python3.dll");
1143-
hPython3 = LoadLibraryExW(py3path, NULL, LOAD_WITH_ALTERED_SEARCH_PATH);
1136+
if (py3path[0]) {
1137+
join(py3path, L"DLLs\\" PY3_DLLNAME);
1138+
hPython3 = LoadLibraryExW(py3path, NULL, LOAD_LIBRARY_SEARCH_DEFAULT_DIRS);
1139+
}
11441140
return hPython3 != NULL;
11451141
}

0 commit comments

Comments
 (0)