ctypes: We do not correctly handle NULL dlsym() return values #126554
Copy link
Copy link
Closed
Labels
3.12only security fixesonly security fixes3.13only security fixesonly security fixes3.14bugs and security fixesbugs and security fixesextension-modulesC modules in the Modules dirC modules in the Modules dirtopic-ctypestype-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error
Description
Bug report
Bug description:
The man(3) page for dlsym() states:
In unusual cases (see NOTES) the value of the symbol could actually be
NULL. Therefore, a NULL return from dlsym() need not indicate an error.
The correct way to distinguish an error from a symbol whose value is NULL
is to call dlerror(3) to clear any old error conditions, then call dl‐
sym(), and then call dlerror(3) again, saving its return value into a
variable, and check whether this saved value is not NULL.
As such, there can be cases where a call to dlsym returns NULL, while no error has been encountered and the string that dlerror() returns has not been (re)set.
Currently, (https://github.com/python/cpython/blob/main/Modules/_ctypes/_ctypes.c#L970) we do:
<...>
address = (void *)dlsym(handle, name);
if (!address) {
#ifdef __CYGWIN__
<snip Windows stuff>
#else
PyErr_SetString(PyExc_ValueError, dlerror());
<...>If dlsym() returns NULL, then by calling dlerror() we pass either NULL or a previous, unconsumed error string to PyErr_SetString.
In the context of ctypes, a NULL return by dlsym() might indeed always be considered an error.
To correctly express this, we should:
- Call
dlerror()beforedlsym(), to clear any previous error - Call
dlerror()afterdlsym(), to see ifdlsym()encountered any error- If
dlerror()returns a non-NULL value, then pass its result toPyErr_SetString - If
dlerror()returns NULL, then check ifaddressis NULL, and if so, pass a custom error string toPyErr_SetString.
- If
CPython versions tested on:
CPython main branch
Operating systems tested on:
Linux
Linked PRs
- gh-126554: ctypes: Correctly handle NULL dlsym values #126555
- [3.13] gh-126554: ctypes: Correctly handle NULL dlsym values (GH-126555) #126861
- [3.12] gh-126554: ctypes: Correctly handle NULL dlsym values (GH-126555) #127764
- gh-126554: correct detection of
gccforTestNullDlsym.test_null_dlsym#129872 - [3.13] gh-126554: correct detection of
gccforTestNullDlsym.test_null_dlsym(GH-129872) #129944 - [3.12] gh-126554: correct detection of
gccforTestNullDlsym.test_null_dlsym(GH-129872) #129945
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
3.12only security fixesonly security fixes3.13only security fixesonly security fixes3.14bugs and security fixesbugs and security fixesextension-modulesC modules in the Modules dirC modules in the Modules dirtopic-ctypestype-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error
Activity
efimov-mikhail commentedon Nov 7, 2024
Is it actually a bug?
Can we make a repro?
grgalex commentedon Nov 7, 2024
EDIT: See Reproducer below.
grgalex commentedon Nov 7, 2024
I managed to create a reproducer using Indirect Functions (IFUNCS) by adapting this SO answer: https://stackoverflow.com/a/53590014
foo.cwith the following contents:main.cwith the following contents:foo.cinto a shared library namedlibfoo.so:mainexecutable:main:grgalex commentedon Nov 7, 2024
When using
ctypesto search for symbolfooin libfoo.so we get a Segmentation Fault.(Tested on Python 3.10 from my system, as well as one built from
main.)So this turned out to be a bug after all!
grgalex commentedon Nov 7, 2024
With the interpreter built from my PR we instead get:
grgalex commentedon Nov 7, 2024
cc: @efimov-mikhail
grgalex commentedon Nov 8, 2024
Fun fact:
The StackOverflow answer, which I adapted to form the reproducer is actually written by Michael Kerrisk [1].
So, he was the one who came up to clarify his statements in the man page, when noone else {c,w}ould :)
[1] https://en.wikipedia.org/wiki/Michael_Kerrisk
gh-126554: ctypes: Correctly handle NULL dlsym values (GH-126555)
pythongh-126554: ctypes: Correctly handle NULL dlsym values (pythonGH…
encukou commentedon Nov 15, 2024
Thanks @grgalex for the fix, and @ZeroIntensity & @picnixz for the thorough reviews!
Well, when a symbol you asked for points to uninitialized memory (like a NULL), a segfault seems rather reasonable to me. (Granted, it does happen for the wrong reasons.)
Treating NULL specially could possibly be considered a feature, rather than a bug.
Given how hard this is to reproduce, I wouldn't mind having the fix to 3.14 only, and omitting the backports.
[3.13] gh-126554: ctypes: Correctly handle NULL dlsym values (GH-126555…
pythongh-126554: ctypes: Correctly handle NULL dlsym values (pythonGH…
[3.12] gh-126554: ctypes: Correctly handle NULL dlsym values (GH-126555…
pythongh-126554: ctypes: Correctly handle NULL dlsym values (pythonGH…
gh-126554: correct detection of `gcc` for `TestNullDlsym.test_null_dl…
pythongh-126554: correct detection of `gcc` for `TestNullDlsym.test_n…
pythongh-126554: correct detection of `gcc` for `TestNullDlsym.test_n…
[3.12] gh-126554: correct detection of `gcc` for `TestNullDlsym.test_…
[3.13] gh-126554: correct detection of `gcc` for `TestNullDlsym.test_…