8000 bpo-29778: Ensure python3.dll is loaded from correct locations when P… · python/cpython@aa7f775 · GitHub
[go: up one dir, main page]

Skip to content

Commit aa7f775

Browse filesBrowse files
bpo-29778: Ensure python3.dll is loaded from correct locations when Python is embedded (GH-21297) (GH-21352)
Also enables using debug build of `python3_d.dll` Reference: CVE-2020-15523 (cherry picked from commit dcbaa1b) Co-authored-by: Steve Dower <steve.dower@python.org>
1 parent 6790f9b commit aa7f775

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():
@@ -567,7 +567,7 @@ def get_expected_config(self, expected_preconfig, expected, env, api,
567567
if expected['stdio_errors'] is self.GET_DEFAULT_CONFIG:
568568
expected['stdio_errors'] = 'surrogateescape'
569569

570-
if sys.platform == 'win32':
570+
if MS_WINDOWS:
571571
default_executable = self.test_exe
572572
elif expected['program_name'] is not self.GET_DEFAULT_CONFIG:
573573
default_executable = os.path.abspath(expected['program_name'])
@@ -601,15 +601,15 @@ def check_pre_config(self, configs, expected):
601601
pre_config = dict(configs['pre_config'])
602602
for key, value in list(expected.items()):
603603
if value is self.IGNORE_CONFIG:
604-
del pre_config[key]
604+
pre_config.pop(key, None)
605605
del expected[key]
606606
self.assertEqual(pre_config, expected)
607607

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

@@ -684,6 +684,7 @@ def check_all_configs(self, testname, expected_config=None,
684684
self.check_pre_config(configs, expected_preconfig)
685685
self.check_config(configs, expected_config)
686686
self.check_global_config(configs)
687+
return configs
687688

688689
def test_init_default_config(self):
689690
self.check_all_configs("test_init_initialize_config", api=API_COMPAT)
@@ -1035,6 +1036,7 @@ def test_init_setpath(self):
10351036
}
10361037
self.default_program_name(config)
10371038
env = {'TESTPATH': os.path.pathsep.join(paths)}
1039+
10381040
self.check_all_configs("test_init_setpath", config,
10391041
api=API_COMPAT, env=env,
10401042
ignore_stderr=True)
@@ -1092,12 +1094,18 @@ def tmpdir_with_python(self):
10921094
# Copy pythonXY.dll (or pythonXY_d.dll)
10931095
ver = sys.version_info
10941096
dll = f'python{ver.major}{ver.minor}'
1097+
dll3 = f'python{ver.major}'
10951098
if debug_build(sys.executable):
10961099
dll += '_d'
1100+
dll3 += '_d'
10971101
dll += '.dll'
1102+
dll3 += '.dll'
10981103
dll = os.path.join(os.path.dirname(self.test_exe), dll)
1104+
dll3 = os.path.join(os.path.dirname(self.test_exe), dll3)
10991105
dll_copy = os.path.join(tmpdir, os.path.basename(dll))
1106+
dll3_copy = os.path.join(tmpdir, os.path.basename(dll3))
11001107
shutil.copyfile(dll, dll_copy)
1108+
shutil.copyfile(dll3, dll3_copy)
11011109

11021110
# Copy Python program
11031111
exec_copy = os.path.join(tmpdir, os.path.basename(self.test_exe))
@@ -1225,9 +1233,18 @@ def test_init_pyvenv_cfg(self):
12251233
config['base_prefix'] = pyvenv_home
12261234
config['prefix'] = pyvenv_home
12271235
env = self.copy_paths_by_env(config)
1228-
self.check_all_configs("test_init_compat_config", config,
1229-
api=API_COMPAT, env=env,
1230-
ignore_stderr=True, cwd=tmpdir)
1236+
actual = self.check_all_configs("test_init_compat_config", config,
1237+
api=API_COMPAT, env=env,
1238+
ignore_stderr=True, cwd=tmpdir)
1239+
if MS_WINDOWS:
1240+
self.assertEqual(
1241+
actual['windows']['python3_dll'],
1242+
os.path.join(
1243+
tmpdir,
1244+
os.path.basename(self.EXPECTED_CONFIG['windows']['python3_dll'])
1245+
)
1246+
)
1247+
12311248

12321249
def test_global_pathconfig(self):
12331250
# 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
@@ -12,10 +12,53 @@
1212
#include "pycore_initconfig.h"
1313

1414

15+
#ifdef MS_WINDOWS
16+
#include <windows.h>
17+
18+
static int
19+
_add_windows_config(PyObject *configs)
20+
{
21+
HMODULE hPython3;
22+
wchar_t py3path[MAX_PATH];
23+
PyObject *dict = PyDict_New();
24+
PyObject *obj = NULL;
25+
if (!dict) {
26+
return -1;
27+
}
28+
29+
hPython3 = GetModuleHandleW(PY3_DLLNAME);
30+
if (hPython3 && GetModuleFileNameW(hPython3, py3path, MAX_PATH)) {
31+
obj = PyUnicode_FromWideChar(py3path, -1);
32+
} else {
33+
obj = Py_None;
34+
Py_INCREF(obj);
35+
}
36+
if (obj &&
37+
!PyDict_SetItemString(dict, "python3_dll", obj) &&
38+
!PyDict_SetItemString(configs, "windows", dict)) {
39+
Py_DECREF(obj);
40+
Py_DECREF(dict);
41+
return 0;
42+
}
43+
Py_DECREF(obj);
44+
Py_DECREF(dict);
45+
return -1;
46+
}
47+
#endif
48+
49+
1550
static PyObject *
1651
get_configs(PyObject *self, PyObject *Py_UNUSED(args))
1752
{
18-
return _Py_GetConfigsAsDict();
53+
PyObject *dict = _Py_GetConfigsAsDict();
54+
#ifdef MS_WINDOWS
55+
if (dict) {
56+
if (_add_windows_config(dict) < 0) {
57+
Py_CLEAR(dict);
58+
}
59+
}
60+
#endif
61+
return dict;
1962
}
2063

2164

PC/getpathp.c

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

133-
wchar_t *dll_path;
134-
135133
const wchar_t *pythonpath_env;
136134
} PyCalculatePath;
137135

@@ -167,27 +165,37 @@ reduce(wchar_t *dir)
167165
static int
168166
change_ext(wchar_t *dest, const wchar_t *src, const wchar_t *ext)
169167
{
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-
}
168+
if (src && src != dest) {
169+
size_t src_len = wcsnlen_s(src, MAXPATHLEN+1);
170+
size_t i = src_len;
171+
if (i >= MAXPATHLEN+1) {
172+
Py_FatalError("buffer overflow in getpathp.c's reduce()");
173+
}
175174

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

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

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

188-
if (wcsncpy_s(dest, MAXPATHLEN+1, src, i) ||
189-
wcscat_s(dest, MAXPATHLEN+1, ext))
190-
{
198+
if (wcscat_s(dest, MAXPATHLEN+1, ext)) {
191199
dest[0] = '\0';
192200
return -1;
193201
}
@@ -344,6 +352,19 @@ search_for_prefix(wchar_t *prefix, const wchar_t *argv0_path, const wchar_t *lan
344352
}
345353

346354

355+
static int
356+
get_dllpath(wchar_t *dllpath)
357+
{
358+
#ifdef Py_ENABLE_SHARED
359+
extern HANDLE PyWin_DLLhModule;
360+
if (PyWin_DLLhModule && GetModuleFileNameW(PyWin_DLLhModule, dllpath, MAXPATHLEN)) {
361+
return 0;
362+
}
363+
#endif
364+
return -1;
365+
}
366+
367+
347368
#ifdef Py_ENABLE_SHARED
348369

349370
/* a string loaded from the DLL at startup.*/
@@ -516,27 +537,6 @@ getpythonregpath(HKEY keyBase, int skipcore)
516537
#endif /* Py_ENABLE_SHARED */
517538

518539

519-
wchar_t*
520-
_Py_GetDLLPath(void)
521-
{
522-
wchar_t dll_path[MAXPATHLEN+1];
523-
memset(dll_path, 0, sizeof(dll_path));
524-
525-
#ifdef Py_ENABLE_SHARED
526-
extern HANDLE PyWin_DLLhModule;
527-
if (PyWin_DLLhModule) {
528-
if (!GetModuleFileNameW(PyWin_DLLhModule, dll_path, MAXPATHLEN)) {
529-
dll_path[0] = 0;
530-
}
531-
}
532-
#else
533-
dll_path[0] = 0;
534-
#endif
535-
536-
return _PyMem_RawWcsdup(dll_path);
537-
}
538-
539-
540540
static PyStatus
541541
get_program_full_path(_PyPathConfig *pathconfig)
542542
{
@@ -717,19 +717,17 @@ static int
717717
get_pth_filename(PyCalculatePath *calculate, wchar_t *filename,
718718
const _PyPathConfig *pathconfig)
719719
{
720-
if (calculate->dll_path[0]) {
721-
if (!change_ext(filename, calculate->dll_path, L"._pth") &&
722-
exists(filename))
723-
{
724-
return 1;
725-
}
720+
if (get_dllpath(filename) &&
721+
!change_ext(filename, filename, L"._pth") &&
722+
exists(filename))
723+
{
724+
return 1;
726725
}
727-
if (pathconfig->program_full_path[0]) {
728-
if (!change_ext(filename, pathconfig->program_full_path, L"._pth") &&
729-
exists(filename))
730-
{
731-
return 1;
732-
}
726+
if (pathconfig->program_full_path[0] &&
727+
!change_ext(filename, pathconfig->program_full_path, L"._pth") &&
728+
exists(filename))
729+
{
730+
return 1;
733731
}
734732
return 0;
735733
}
@@ -1029,9 +1027,12 @@ calculate_path(PyCalculatePath *calculate, _PyPathConfig *pathconfig)
10291027
wchar_t zip_path[MAXPATHLEN+1];
10301028
memset(zip_path, 0, sizeof(zip_path));
10311029

1032-
change_ext(zip_path,
1033-
calculate->dll_path[0] ? calculate->dll_path : pathconfig->program_full_path,
1034-
L".zip");
1030+
if (get_dllpath(zip_path) || change_ext(zip_path, zip_path, L".zip"))
1031+
{
1032+
if (change_ext(zip_path, pathconfig->program_full_path, L".zip")) {
1033+
zip_path[0] = L'\0';
1034+
}
1035+
}
10351036

10361037
calculate_home_prefix(calculate, argv0_path, zip_path, prefix);
10371038

@@ -1068,11 +1069,6 @@ calculate_init(PyCalculatePath *calculate, _PyPathConfig *pathconfig,
10681069
calculate->home = pathconfig->home;
10691070
calculate->path_env = _wgetenv(L"PATH");
10701071

1071-
calculate->dll_path = _Py_GetDLLPath();
1072-
if (calculate->dll_path == NULL) {
1073-
return _PyStatus_NO_MEMORY();
1074-
}
1075-
10761072
calculate->pythonpath_env = config->pythonpath_env;
10771073

10781074
return _PyStatus_OK();
@@ -1084,7 +1080,6 @@ calculate_free(PyCalculatePath *calculate)
10841080
{
10851081
PyMem_RawFree(calculate->machine_path);
10861082
PyMem_RawFree(calculate->user_path);
1087-
PyMem_RawFree(calculate->dll_path);
10881083
}
10891084

10901085

@@ -1094,7 +1089,6 @@ calculate_free(PyCalculatePath *calculate)
10941089
10951090
- PyConfig.pythonpath_env: PYTHONPATH environment variable
10961091
- _PyPathConfig.home: Py_SetPythonHome() or PYTHONHOME environment variable
1097-
- DLL path: _Py_GetDLLPath()
10981092
- PATH environment variable
10991093
- __PYVENV_LAUNCHER__ environment variable
11001094
- GetModuleFileNameW(NULL): fully qualified path of the executable file of
@@ -1148,33 +1142,35 @@ int
11481142
_Py_CheckPython3(void)
11491143
{
11501144
wchar_t py3path[MAXPATHLEN+1];
1151-
wchar_t *s;
11521145
if (python3_checked) {
11531146
return hPython3 != NULL;
11541147
}
11551148
python3_checked = 1;
11561149

11571150
/* If there is a python3.dll next to the python3y.dll,
1158-
assume this is a build tree; use that DLL */
1159-
if (_Py_dll_path != NULL) {
1160-
wcscpy(py3path, _Py_dll_path);
1161-
}
1162-
else {
1163-
wcscpy(py3path, L"");
1164-
}
1165-
s = wcsrchr(py3path, L'\\');
1166-
if (!s) {
1167-
s = py3path;
1151+
use that DLL */
1152+
if (!get_dllpath(py3path)) {
1153+
reduce(py3path);
1154+
join(py3path, PY3_DLLNAME);
1155+
hPython3 = LoadLibraryExW(py3path, NULL, LOAD_LIBRARY_SEARCH_DEFAULT_DIRS);
1156+
if (hPython3 != NULL) {
1157+
return 1;
1158+
}
11681159
}
1169-
wcscpy(s, L"\\python3.dll");
1170-
hPython3 = LoadLibraryExW(py3path, NULL, LOAD_WITH_ALTERED_SEARCH_PATH);
1160+
1161+
/* If we can locate python3.dll in our application dir,
1162+
use that DLL */
1163+
hPython3 = LoadLibraryExW(PY3_DLLNAME, NULL, LOAD_LIBRARY_SEARCH_APPLICATION_DIR);
11711164
if (hPython3 != NULL) {
11721165
return 1;
11731166
}
11741167

1175-
/* Check sys.prefix\DLLs\python3.dll */
1168+
/* For back-compat, also search {sys.prefix}\DLLs, though
1169+
that has not been a normal install layout for a while */
11761170
wcscpy(py3path, Py_GetPrefix());
1177-
wcscat(py3path, L"\\DLLs\\python3.dll");
1178-
hPython3 = LoadLibraryExW(py3path, NULL, LOAD_WITH_ALTERED_SEARCH_PATH);
1171+
if (py3path[0]) {
1172+
join(py3path, L"DLLs\\" PY3_DLLNAME);
1173+
hPython3 = LoadLibraryExW(py3path, NULL, LOAD_LIBRARY_SEARCH_DEFAULT_DIRS);
1174+
}
11791175
return hPython3 != NULL;
11801176
}

0 commit comments

Comments
 (0)
0