-
-
Notifications
You must be signed in to change notification settings - Fork 30.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
For dlsym(), a return value of NULL does not necessarily indicate an error [1]. Therefore, to avoid using stale (or NULL) dlerror() values, we must: 1. clear the previous error state by calling dlerror() 2. call dlsym() 3. call dlerror() If the return value of dlerror() is not NULL, an error occured. In ctypes we choose to treat a NULL return value from dlsym() as a "not found" error. This is the same as the fallback message we use on Windows, Cygwin or when getting/formatting the error reason fails. [1]: https://man7.org/linux/man-pages/man3/dlsym.3.html (cherry picked from commit 8717f79) Co-authored-by: George Alexopoulos <[email protected]> Signed-off-by: Georgios Alexopoulos <[email protected]> Signed-off-by: Georgios Alexopoulos <[email protected]> Co-authored-by: Peter Bierma <[email protected]> Co-authored-by: Bénédikt Tran <[email protected]> Co-authored-by: Petr Viktorin <[email protected]>
- Loading branch information
1 parent
cb07c44
commit 5b4116e
Showing
4 changed files
with
220 additions
and
31 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
import os | ||
import sys | ||
import unittest | ||
import platform | ||
|
||
FOO_C = r""" | ||
#include <unistd.h> | ||
/* This is a 'GNU indirect function' (IFUNC) that will be called by | ||
dlsym() to resolve the symbol "foo" to an address. Typically, such | ||
a function would return the address of an actual function, but it | ||
can also just return NULL. For some background on IFUNCs, see | ||
https://willnewton.name/uncategorized/using-gnu-indirect-functions. | ||
Adapted from Michael Kerrisk's answer: https://stackoverflow.com/a/53590014. | ||
*/ | ||
asm (".type foo STT_GNU_IFUNC"); | ||
void *foo(void) | ||
{ | ||
write($DESCRIPTOR, "OK", 2); | ||
return NULL; | ||
} | ||
""" | ||
|
||
|
||
@unittest.skipUnless(sys.platform.startswith('linux'), | ||
'Test only valid for Linux') | ||
class TestNullDlsym(unittest.TestCase): | ||
"""GH-126554: Ensure that we catch NULL dlsym return values | ||
In rare cases, such as when using GNU IFUNCs, dlsym(), | ||
the C function that ctypes' CDLL uses to get the address | ||
of symbols, can return NULL. | ||
The objective way of telling if an error during symbol | ||
lookup happened is to call glibc's dlerror() and check | ||
for a non-NULL return value. | ||
However, there can be cases where dlsym() returns NULL | ||
and dlerror() is also NULL, meaning that glibc did not | ||
encounter any error. | ||
In the case of ctypes, we subjectively treat that as | ||
an error, and throw a relevant exception. | ||
This test case ensures that we correctly enforce | ||
this 'dlsym returned NULL -> throw Error' rule. | ||
""" | ||
|
||
def test_null_dlsym(self): | ||
import subprocess | ||
import tempfile | ||
|
||
# To avoid ImportErrors on Windows, where _ctypes does not have | ||
# dlopen and dlsym, | ||
# import here, i.e., inside the test function. | ||
# The skipUnless('linux') decorator ensures that we're on linux | ||
# if we're executing these statements. | ||
from ctypes import CDLL, c_int | ||
from _ctypes import dlopen, dlsym | ||
|
||
retcode = subprocess.call(["gcc", "--version"], | ||
stdout=subprocess.DEVNULL, | ||
stderr=subprocess.DEVNULL) | ||
if retcode != 0: | ||
self.skipTest("gcc is missing") | ||
|
||
pipe_r, pipe_w = os.pipe() | ||
self.addCleanup(os.close, pipe_r) | ||
self.addCleanup(os.close, pipe_w) | ||
|
||
with tempfile.TemporaryDirectory() as d: | ||
# Create a C file with a GNU Indirect Function (FOO_C) | ||
# and compile it into a shared library. | ||
srcname = os.path.join(d, 'foo.c') | ||
dstname = os.path.join(d, 'libfoo.so') | ||
with open(srcname, 'w') as f: | ||
f.write(FOO_C.replace('$DESCRIPTOR', str(pipe_w))) | ||
args = ['gcc', '-fPIC', '-shared', '-o', dstname, srcname] | ||
p = subprocess.run(args, capture_output=True) | ||
|
||
if p.returncode != 0: | ||
# IFUNC is not supported on all architectures. | ||
if platform.machine() == 'x86_64': | ||
# It should be supported here. Something else went wrong. | ||
p.check_returncode() | ||
else: | ||
# IFUNC might not be supported on this machine. | ||
self.skipTest(f"could not compile indirect function: {p}") | ||
|
||
# Case #1: Test 'PyCFuncPtr_FromDll' from Modules/_ctypes/_ctypes.c | ||
L = CDLL(dstname) | ||
with self.assertRaisesRegex(AttributeError, "function 'foo' not found"): | ||
# Try accessing the 'foo' symbol. | ||
# It should resolve via dlsym() to NULL, | ||
# and since we subjectively treat NULL | ||
# addresses as errors, we should get | ||
# an error. | ||
L.foo | ||
|
||
# Assert that the IFUNC was called | ||
self.assertEqual(os.read(pipe_r, 2), b'OK') | ||
|
||
# Case #2: Test 'CDataType_in_dll_impl' from Modules/_ctypes/_ctypes.c | ||
with self.assertRaisesRegex(ValueError, "symbol 'foo' not found"): | ||
c_int.in_dll(L, "foo") | ||
|
||
# Assert that the IFUNC was called | ||
self.assertEqual(os.read(pipe_r, 2), b'OK') | ||
|
||
# Case #3: Test 'py_dl_sym' from Modules/_ctypes/callproc.c | ||
L = dlopen(dstname) | ||
with self.assertRaisesRegex(OSError, "symbol 'foo' not found"): | ||
dlsym(L, "foo") | ||
|
||
# Assert that the IFUNC was called | ||
self.assertEqual(os.read(pipe_r, 2), b'OK') | ||
|
||
|
||
if __name__ == "__main__": | ||
unittest.main() |
2 changes: 2 additions & 0 deletions
2
Misc/NEWS.d/next/C_API/2024-11-07-20-24-58.gh-issue-126554.ri12eb.rst
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
Fix error handling in :class:`ctypes.CDLL` objects | ||
which could result in a crash in rare situations. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters