Skip to content

Commit 101cfe6

Browse files
farfellazwareFFY00eric-wieser
authored
gh-78997: AttributeError if loading fails in LibraryLoader.__getattr__
Co-authored-by: Zachary Ware <zachary.ware@gmail.com> Co-authored-by: Filipe Laíns <filipe.lains@gmail.com> Co-authored-by: Eric Wieser <wieser.eric@gmail.com>
1 parent b430399 commit 101cfe6

File tree

3 files changed

+195
-1
lines changed

3 files changed

+195
-1
lines changed

Lib/ctypes/__init__.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -444,7 +444,10 @@ def __init__(self, dlltype):
444444
def __getattr__(self, name):
445445
if name[0] == '_':
446446
raise AttributeError(name)
447-
dll = self._dlltype(name)
447+
try:
448+
dll = self._dlltype(name)
449+
except OSError:
450+
raise AttributeError(name)
448451
setattr(self, name, dll)
449452
return dll
450453

Lib/ctypes/test/test_loading.py

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
from ctypes import *
2+
import os
3+
import shutil
4+
import subprocess
5+
import sys
6+
import unittest
7+
import test.support
8+
from test.support import import_helper
9+
from test.support import os_helper
10+
from ctypes.util import find_library
11+
12+
libc_name = None
13+
14+
def setUpModule():
15+
global libc_name
16+
if os.name == "nt":
17+
libc_name = find_library("c")
18+
elif sys.platform == "cygwin":
19+
libc_name = "cygwin1.dll"
20+
else:
21+
libc_name = find_library("c")
22+
23+
if test.support.verbose:
24+
print("libc_name is", libc_name)
25+
26+
class LoaderTest(unittest.TestCase):
27+
28+
unknowndll = "xxrandomnamexx"
29+
30+
def test_load(self):
31+
if libc_name is None:
32+
self.skipTest('could not find libc')
33+
CDLL(libc_name)
34+
CDLL(os.path.basename(libc_name))
35+
self.assertRaises(OSError, CDLL, self.unknowndll)
36+
37+
def test_load_version(self):
38+
if libc_name is None:
39+
self.skipTest('could not find libc')
40+
if os.path.basename(libc_name) != 'libc.so.6':
41+
self.skipTest('wrong libc path for test')
42+
cdll.LoadLibrary("libc.so.6")
43+
# linux uses version, libc 9 should not exist
44+
self.assertRaises(OSError, cdll.LoadLibrary, "libc.so.9")
45+
self.assertRaises(OSError, cdll.LoadLibrary, self.unknowndll)
46+
47+
def test_find(self):
48+
for name in ("c", "m"):
49+
lib = find_library(name)
50+
if lib:
51+
cdll.LoadLibrary(lib)
52+
CDLL(lib)
53+
54+
@unittest.skipUnless(os.name == "nt",
55+
'test specific to Windows')
56+
def test_load_library(self):
57+
# CRT is no longer directly loadable. See issue23606 for the
58+
# discussion about alternative approaches.
59+
#self.assertIsNotNone(libc_name)
60+
if test.support.verbose:
61+
print(find_library("kernel32"))
62+
print(find_library("user32"))
63+
64+
if os.name == "nt":
65+
windll.kernel32.GetModuleHandleW
66+
windll["kernel32"].GetModuleHandleW
67+
windll.LoadLibrary("kernel32").GetModuleHandleW
68+
WinDLL("kernel32").GetModuleHandleW
69+
# embedded null character
70+
self.assertRaises(ValueError, windll.LoadLibrary, "kernel32\0")
71+
72+
@unittest.skipUnless(os.name == "nt",
73+
'test specific to Windows')
74+
def test_load_ordinal_functions(self):
75+
import _ctypes_test
76+
dll = WinDLL(_ctypes_test.__file__)
77+
# We load the same function both via ordinal and name
78+
func_ord = dll[2]
79+
func_name = dll.GetString
80+
# addressof gets the address where the function pointer is stored
81+
a_ord = addressof(func_ord)
82+
a_name = addressof(func_name)
83+
f_ord_addr = c_void_p.from_address(a_ord).value
84+
f_name_addr = c_void_p.from_address(a_name).value
85+
self.assertEqual(hex(f_ord_addr), hex(f_name_addr))
86+
87+
self.assertRaises(AttributeError, dll.__getitem__, 1234)
88+
89+
@unittest.skipUnless(os.name == "nt", 'Windows-specific test')
90+
def test_1703286_A(self):
91+
from _ctypes import LoadLibrary, FreeLibrary
92+
# On winXP 64-bit, advapi32 loads at an address that does
93+
# NOT fit into a 32-bit integer. FreeLibrary must be able
94+
# to accept this address.
95+
96+
# These are tests for https://www.python.org/sf/1703286
97+
handle = LoadLibrary("advapi32")
98+
FreeLibrary(handle)
99+
100+
@unittest.skipUnless(os.name == "nt", 'Windows-specific test')
101+
def test_1703286_B(self):
102+
# Since on winXP 64-bit advapi32 loads like described
103+
# above, the (arbitrarily selected) CloseEventLog function
104+
# also has a high address. 'call_function' should accept
105+
# addresses so large.
106+
from _ctypes import call_function
107+
advapi32 = windll.advapi32
108+
# Calling CloseEventLog with a NULL argument should fail,
109+
# but the call should not segfault or so.
110+
self.assertEqual(0, advapi32.CloseEventLog(None))
111+
windll.kernel32.GetProcAddress.argtypes = c_void_p, c_char_p
112+
windll.kernel32.GetProcAddress.restype = c_void_p
113+
proc = windll.kernel32.GetProcAddress(advapi32._handle,
114+
b"CloseEventLog")
115+
self.assertTrue(proc)
116+
# This is the real test: call the function via 'call_function'
117+
self.assertEqual(0, call_function(proc, (None,)))
118+
119+
@unittest.skipUnless(os.name == "nt",
120+
'test specific to Windows')
121+
def test_load_hasattr(self):
122+
# bpo-34816: shouldn't raise OSError
123+
self.assertFalse(hasattr(windll, 'test'))
124+
125+
@unittest.skipUnless(os.name == "nt",
126+
'test specific to Windows')
127+
def test_load_dll_with_flags(self):
128+
_sqlite3 = import_helper.import_module("_sqlite3")
129+
src = _sqlite3.__file__
130+
if src.lower().endswith("_d.pyd"):
131+
ext = "_d.dll"
132+
else:
133+
ext = ".dll"
134+
135+
with os_helper.temp_dir() as tmp:
136+
# We copy two files and load _sqlite3.dll (formerly .pyd),
137+
# which has a dependency on sqlite3.dll. Then we test
138+
# loading it in subprocesses to avoid it starting in memory
139+
# for each test.
140+
target = os.path.join(tmp, "_sqlite3.dll")
141+
shutil.copy(src, target)
142+
shutil.copy(os.path.join(os.path.dirname(src), "sqlite3" + ext),
143+
os.path.join(tmp, "sqlite3" + ext))
144+
145+
def should_pass(command):
146+
with self.subTest(command):
147+
subprocess.check_output(
148+
[sys.executable, "-c",
149+
"from ctypes import *; import nt;" + command],
150+
cwd=tmp
151+
)
152+
153+
def should_fail(command):
154+
with self.subTest(command):
155+
with self.assertRaises(subprocess.CalledProcessError):
156+
subprocess.check_output(
157+
[sys.executable, "-c",
158+
"from ctypes import *; import nt;" + command],
159+
cwd=tmp, stderr=subprocess.STDOUT,
160+
)
161+
162+
# Default load should not find this in CWD
163+
should_fail("WinDLL('_sqlite3.dll')")
164+
165+
# Relative path (but not just filename) should succeed
166+
should_pass("WinDLL('./_sqlite3.dll')")
167+
168+
# Insecure load flags should succeed
169+
# Clear the DLL directory to avoid safe search settings propagating
170+
should_pass("windll.kernel32.SetDllDirectoryW(None); WinDLL('_sqlite3.dll', winmode=0)")
171+
172+
# Full path load without DLL_LOAD_DIR shouldn't find dependency
173+
should_fail("WinDLL(nt._getfullpathname('_sqlite3.dll'), " +
174+
"winmode=nt._LOAD_LIBRARY_SEARCH_SYSTEM32)")
175+
176+
# Full path load with DLL_LOAD_DIR should succeed
177+
should_pass("WinDLL(nt._getfullpathname('_sqlite3.dll'), " +
178+
"winmode=nt._LOAD_LIBRARY_SEARCH_SYSTEM32|" +
179+
"nt._LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR)")
180+
181+
# User-specified directory should succeed
182+
should_pass("import os; p = os.add_dll_directory(os.getcwd());" +
183+
"WinDLL('_sqlite3.dll'); p.close()")
184+
185+
186+
187+
if __name__ == "__main__":
188+
unittest.main()
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
``hasattr(ctypes.windll, 'nonexistant')`` now returns ``False`` instead of raising :exc:`OSError`.
2+
3+

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy