Skip to content

Commit 1f7838b

Browse files
committed
Detect conflicting Python DLL on module import under Windows - as per [ Patch #101676 ]
1 parent 85788ed commit 1f7838b

File tree

1 file changed

+152
-1
lines changed

1 file changed

+152
-1
lines changed

Python/dynload_win.c

+152-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
#include <windows.h>
55
#include <direct.h>
6+
#include <ctype.h>
67

78
#include "Python.h"
89
#include "importdl.h"
@@ -19,11 +20,144 @@ const struct filedescr _PyImport_DynLoadFiletab[] = {
1920
};
2021

2122

23+
#ifdef MS_WIN32
24+
25+
/* Case insensitive string compare, to avoid any dependencies on particular
26+
C RTL implementations */
27+
28+
static int strcasecmp (char *string1, char *string2)
29+
{
30+
int first, second;
31+
32+
do {
33+
first = tolower(*string1);
34+
second = tolower(*string2);
35+
string1++;
36+
string2++;
37+
} while (first && first == second);
38+
39+
return (first - second);
40+
}
41+
42+
43+
/* Function to return the name of the "python" DLL that the supplied module
44+
directly imports. Looks through the list of imported modules and
45+
returns the first entry that starts with "python" (case sensitive) and
46+
is followed by nothing but numbers until the separator (period).
47+
48+
Returns a pointer to the import name, or NULL if no matching name was
49+
located.
50+
51+
This function parses through the PE header for the module as loaded in
52+
memory by the system loader. The PE header is accessed as documented by
53+
Microsoft in the MSDN PE and COFF specification (2/99), and handles
54+
both PE32 and PE32+. It only worries about the direct import table and
55+
not the delay load import table since it's unlikely an extension is
56+
going to be delay loading Python (after all, it's already loaded).
57+
58+
If any magic values are not found (e.g., the PE header or optional
59+
header magic), then this function simply returns NULL. */
60+
61+
#define DWORD_AT(mem) (*(DWORD *)(mem))
62+
#define WORD_AT(mem) (*(WORD *)(mem))
63+
64+
static char *GetPythonImport (HINSTANCE hModule)
65+
{
66+
unsigned char *dllbase, *import_data, *import_name;
67+
DWORD pe_offset, opt_offset;
68+
WORD opt_magic;
69+
int num_dict_off, import_off;
70+
71+
/* Safety check input */
72+
if (hModule == NULL) {
73+
return NULL;
74+
}
75+
76+
/* Module instance is also the base load address. First portion of
77+
memory is the MS-DOS loader, which holds the offset to the PE
78+
header (from the load base) at 0x3C */
79+
dllbase = (unsigned char *)hModule;
80+
pe_offset = DWORD_AT(dllbase + 0x3C);
81+
82+
/* The PE signature must be "PE\0\0" */
83+
if (memcmp(dllbase+pe_offset,"PE\0\0",4)) {
84+
return NULL;
85+
}
86+
87+
/* Following the PE signature is the standard COFF header (20
88+
bytes) and then the optional header. The optional header starts
89+
with a magic value of 0x10B for PE32 or 0x20B for PE32+ (PE32+
90+
uses 64-bits for some fields). It might also be 0x107 for a ROM
91+
image, but we don't process that here.
92+
93+
The optional header ends with a data dictionary that directly
94+
points to certain types of data, among them the import entries
95+
(in the second table entry). Based on the header type, we
96+
determine offsets for the data dictionary count and the entry
97+
within the dictionary pointing to the imports. */
98+
99+
opt_offset = pe_offset + 4 + 20;
100+
opt_magic = WORD_AT(dllbase+opt_offset);
101+
if (opt_magic == 0x10B) {
102+
/* PE32 */
103+
num_dict_off = 92;
104+
import_off = 104;
105+
} else if (opt_magic == 0x20B) {
106+
/* PE32+ */
107+
num_dict_off = 108;
108+
import_off = 120;
109+
} else {
110+
/* Unsupported */
111+
return NULL;
112+
}
113+
114+
/* Now if an import table exists, offset to it and walk the list of
115+
imports. The import table is an array (ending when an entry has
116+
empty values) of structures (20 bytes each), which contains (at
117+
offset 12) a relative address (to the module base) at which a
118+
string constant holding the import name is located. */
119+
120+
if (DWORD_AT(dllbase + opt_offset + num_dict_off) >= 2) {
121+
import_data = dllbase + DWORD_AT(dllbase +
122+
opt_offset +
123+
import_off);
124+
while (DWORD_AT(import_data)) {
125+
import_name = dllbase + DWORD_AT(import_data+12);
126+
if (strlen(import_name) >= 6 &&
127+
!strncmp(import_name,"python",6)) {
128+
char *pch;
129+
130+
/* Ensure python prefix is followed only
131+
by numbers to the end of the basename */
132+
pch = import_name + 6;
133+
while (*pch && *pch != '.') {
134+
if (*pch >= '0' && *pch <= '9') {
135+
pch++;
136+
} else {
137+
pch = NULL;
138+
break;
139+
}
140+
}
141+
142+
if (pch) {
143+
/* Found it - return the name */
144+
return import_name;
145+
}
146+
}
147+
import_data += 20;
148+
}
149+
}
150+
151+
return NULL;
152+
}
153+
#endif /* MS_WIN32 */
154+
155+
22156
dl_funcptr _PyImport_GetDynLoadFunc(const char *fqname, const char *shortname,
23157
const char *pathname, FILE *fp)
24158
{
25159
dl_funcptr p;
26-
char funcname[258];
160+
char funcname[258], *import_python;
27161

28162
sprintf(funcname, "init%.200s", shortname);
29163

@@ -91,6 +225,23 @@ dl_funcptr _PyImport_GetDynLoadFunc(const char *fqname, const char *shortname,
91225
}
92226
PyErr_SetString(PyExc_ImportError, errBuf);
93227
return NULL;
228+
} else {
229+
char buffer[256];
230+
231+
sprintf(buffer,"python%d%d.dll",
232+
PY_MAJOR_VERSION,PY_MINOR_VERSION);
233+
import_python = GetPythonImport(hDLL);
234+
235+
if (import_python &&
236+
strcasecmp(buffer,import_python)) {
237+
sprintf(buffer,
238+
"Module use of %s conflicts "
239+
"with this version of Python.",
240+
import_python);
241+
PyErr_SetString(PyExc_ImportError,buffer);
242+
FreeLibrary(hDLL);
243+
return NULL;
244+
}
94245
}
95246
p = GetProcAddress(hDLL, funcname);
96247
}

0 commit comments

Comments
 (0)