-
-
Notifications
You must be signed in to change notification settings - Fork 32k
gh-126554: ctypes: Correctly handle NULL dlsym values #126555
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 6 commits
3c3c29d
f71abe8
64a7394
512a084
cd7d5d7
cf39b5b
a72ce98
c820d16
6751125
e1f3b43
8f7587d
2b3a88c
9a1af29
3e02e09
fbdbc26
fdf6013
3aa29e5
0616214
6d59ab7
31c621d
53475a4
7fc538e
e20d97f
95b072e
048385c
658098f
6f4b921
25b6cf9
4bac254
ee6122c
08a7b88
8c6b8ae
291bf16
1cbffcc
5748c13
65e2b7e
38fbaed
cc8e366
4319332
8356cf4
1e1e78b
4e87040
6a3aea7
73918bf
0ca32d4
6db5963
d179377
6933314
33cf8b7
94dfbaa
cda7771
4b1bb47
2290aa4
ab22349
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
import os.path | ||
import sys | ||
import unittest | ||
from ctypes import CDLL | ||
|
||
FOO_C = r""" | ||
#include <stdio.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 | ||
*/ | ||
grgalex marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
asm (".type foo, @gnu_indirect_function"); | ||
grgalex marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
void *foo(void) | ||
{ | ||
fprintf(stderr, "foo IFUNC called\n"); | ||
return NULL; | ||
} | ||
""" | ||
|
||
|
||
@unittest.skipUnless(sys.platform.startswith('linux'), | ||
'Test only valid for Linux') | ||
class TestNullDlsym(unittest.TestCase): | ||
def test_null_dlsym(self): | ||
ZeroIntensity marked this conversation as resolved.
Show resolved
Hide resolved
|
||
import subprocess | ||
import tempfile | ||
|
||
try: | ||
p = subprocess.Popen(['gcc', '--version'], stdout=subprocess.PIPE, | ||
stderr=subprocess.DEVNULL) | ||
out, _ = p.communicate() | ||
picnixz marked this conversation as resolved.
Show resolved
Hide resolved
|
||
except OSError: | ||
raise unittest.SkipTest('gcc, needed for test, not available') | ||
grgalex marked this conversation as resolved.
Show resolved
Hide resolved
|
||
with tempfile.TemporaryDirectory() as d: | ||
# Create a source file foo.c, that uses | ||
# a GNU Indirect Function. See FOO_C. | ||
srcname = os.path.join(d, 'foo.c') | ||
libname = 'py_ctypes_test_null_dlsym' | ||
dstname = os.path.join(d, 'lib%s.so' % libname) | ||
with open(srcname, 'w') as f: | ||
f.write(FOO_C) | ||
self.assertTrue(os.path.exists(srcname)) | ||
# Compile the file to a shared library | ||
cmd = ['gcc', '-fPIC', '-shared', '-o', dstname, srcname] | ||
out = subprocess.check_output(cmd) | ||
self.assertTrue(os.path.exists(dstname)) | ||
# Load the shared library | ||
L = CDLL(dstname) | ||
picnixz marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
with self.assertRaises(AttributeError) as cm: | ||
ZeroIntensity marked this conversation as resolved.
Show resolved
Hide resolved
|
||
# 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 | ||
ZeroIntensity marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
self.assertEqual(str(cm.exception), | ||
"function 'foo' not found") | ||
|
||
if __name__ == "__main__": | ||
unittest.main() |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Fix rare crash upon system failure in :class:`ctypes.CDLL`. | ||
grgalex marked this conversation as resolved.
Show resolved
Hide resolved
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -967,18 +967,34 @@ CDataType_in_dll_impl(PyObject *type, PyTypeObject *cls, PyObject *dll, | |
return NULL; | ||
} | ||
#else | ||
/* dlerror() always returns the latest error. | ||
* | ||
* Clear the previous value before calling dlsym(), | ||
* to ensure we can tell if our call resulted in an error. | ||
*/ | ||
dlerror(); | ||
ZeroIntensity marked this conversation as resolved.
Show resolved
Hide resolved
grgalex marked this conversation as resolved.
Show resolved
Hide resolved
|
||
address = (void *)dlsym(handle, name); | ||
if (!address) { | ||
#ifdef __CYGWIN__ | ||
/* dlerror() isn't very helpful on cygwin */ | ||
if (!address) { | ||
/* dlerror() isn't very helpful on cygwin */ | ||
PyErr_Format(PyExc_ValueError, | ||
"symbol '%s' not found", | ||
name); | ||
return NULL; | ||
} | ||
#else | ||
PyErr_SetString(PyExc_ValueError, dlerror()); | ||
#endif | ||
char *dlerr = dlerror(); | ||
grgalex marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if (dlerr) { | ||
PyErr_SetString(PyExc_ValueError, dlerr); | ||
ZeroIntensity marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return NULL; | ||
} | ||
else if (!address) { | ||
PyErr_Format(PyExc_ValueError, | ||
"symbol '%s' not found", | ||
name); | ||
return NULL; | ||
} | ||
#endif | ||
#endif | ||
ctypes_state *st = get_module_state_by_def(Py_TYPE(type)); | ||
return PyCData_AtAddress(st, type, address); | ||
|
@@ -3774,19 +3790,37 @@ PyCFuncPtr_FromDll(PyTypeObject *type, PyObject *args, PyObject *kwds) | |
return NULL; | ||
} | ||
#else | ||
/* dlerror() always returns the latest error. | ||
* | ||
* Clear the previous value before calling dlsym(), | ||
* to ensure we can tell if our call resulted in an error. | ||
*/ | ||
dlerror(); | ||
grgalex marked this conversation as resolved.
Show resolved
Hide resolved
|
||
address = (PPROC)dlsym(handle, name); | ||
if (!address) { | ||
#ifdef __CYGWIN__ | ||
/* dlerror() isn't very helpful on cygwin */ | ||
if (!address) { | ||
/* dlerror() isn't very helpful on cygwin */ | ||
PyErr_Format(PyExc_AttributeError, | ||
"function '%s' not found", | ||
name); | ||
Py_DECREF(ftuple); | ||
return NULL; | ||
} | ||
#else | ||
PyErr_SetString(PyExc_AttributeError, dlerror()); | ||
#endif | ||
char *dlerr = dlerror(); | ||
grgalex marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if (dlerr) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For the case where another thread sets There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok, I see your point. Even though glibc's So, let's flip the In this way, and given that our Of course, we could always print a wrong message, but that is just as possible even if we don't flip the WDYT? |
||
PyErr_SetString(PyExc_AttributeError, dlerr); | ||
Py_DECREF(ftuple); | ||
return NULL; | ||
} | ||
else if (!address) { | ||
PyErr_Format(PyExc_AttributeError, | ||
"function '%s' not found", | ||
name); | ||
Py_DECREF(ftuple); | ||
return NULL; | ||
} | ||
#endif | ||
#endif | ||
ctypes_state *st = get_module_state_by_def(Py_TYPE(type)); | ||
if (!_validate_paramflags(st, type, paramflags)) { | ||
|
Uh oh!
There was an error while loading. Please reload this page.