Repository navigation
undefined behavior: tstate->datastack_top == NULL #96569
Copy link
Copy link
Closed
Labels
3.11only security fixesonly security fixes3.12only security fixesonly security fixesinterpreter-core(Objects, Python, Grammar, and Parser dirs)(Objects, Python, Grammar, and Parser dirs)release-blockertype-crashA hard crash of the interpreter, possibly with a core dumpA hard crash of the interpreter, possibly with a core dump
Description
I was chasing down some C trouble in code I had been experimenting. I used all the debug options I could find:
export CC="clang"
configure --with-assertions --with-address-sanitizer --with-trace-refs --with-undefined-behavior-sanitizer --with-pydebug
nice make -j8For sanity checking, I ran this on current main. I got:
../../Python/pystate.c:2199:27: runtime error: applying non-zero offset 112 to null pointer
SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior ../../Python/pystate.c:2199:27 in
../../Python/pystate.c:2199:27: runtime error: applying non-zero offset 112 to null pointer
SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior ../../Python/pystate.c:2199:27 in
../../Python/pystate.c:2199:27: runtime error: applying non-zero offset 112 to null pointer
SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior ../../Python/pystate.c:2199:27 in
For a minimal reproducible example, have a look at my example PR that adds this check and fails to build:
diff --git a/Python/pystate.c b/Python/pystate.c
index a11f1622ecd..09543add9dd 100644
--- a/Python/pystate.c
+++ b/Python/pystate.c
@@ -2196,6 +2196,7 @@ _PyThreadState_PushFrame(PyThreadState *tstate, size_t size)
{
assert(size < INT_MAX/sizeof(PyObject *));
PyObject **base = tstate->datastack_top;
+ assert(base != NULL);
PyObject **top = base + size;
if (top >= tstate->datastack_limit) {
base = push_chunk(tstate, (int)size);Error messages
Enter any relevant error message caused by the crash, including a core dump if there is one.
I already pasted the error message I get from the sanitizers above. Here's the error message I get from my assertion instead (and building with just sequential make):
./Programs/_freeze_module zipimport ../../Lib/zipimport.py Python/frozen_modules/zipimport.h
./_bootstrap_python ../../Programs/_freeze_module.py abc ../../Lib/abc.py Python/frozen_modules/abc.h
_bootstrap_python: ../../Python/pystate.c:2199: _PyInterpreterFrame *_PyThreadState_PushFrame(PyThreadState *, size_t): Assertion `base != NULL' failed.
make: *** [Makefile:1238: Python/frozen_modules/abc.h] Aborted (core dumped)
Your environment
I tested this on Archlinux against latest main. You can also see it in action on the failed test run for my PR on github.
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
3.11only security fixesonly security fixes3.12only security fixesonly security fixesinterpreter-core(Objects, Python, Grammar, and Parser dirs)(Objects, Python, Grammar, and Parser dirs)release-blockertype-crashA hard crash of the interpreter, possibly with a core dumpA hard crash of the interpreter, possibly with a core dump
Projects
- StatusShow more project fieldsDone
Activity
sweeneyde commentedon Sep 5, 2022
cc @markshannon
It looks like this happens at the very first call to PyEval_EvalCode, when executing
<module>for importlib.It goes:
PyImport_ImportFrozenmoduleObject > exec_code_in_module > PyEval_EvalCode > _PyEval_Vector > _PyEvalFramePushAndInit > _PyThreadState_PushFramematthiasgoergens commentedon Sep 5, 2022
git bisectputs the finger an 42a64c0 but that one looks fairly innocuous as far as C is concerned?In any case here's my bisecting log:
(Ignore the
hot-fixone at the start..)sweeneyde commentedon Sep 5, 2022
It looks like this is happening in the 3.11 branch @pablogsal
sweeneyde commentedon Sep 5, 2022
I'm suspicious of 45e62a2, where
_PyEvalFramePushAndInit's call to_PyThreadState_BumpFramePointer(checks forbase==NULL) got transformed into a usage of_PyThreadState_PushFrame(does not check forbase==NULL).sweeneyde commentedon Sep 5, 2022
One option would be to never let datastack_top be NULL, but that could interfere with the efforts at 77195cd to be compatible with greenlet and other frame-swapping extensions.
So by my shallow understanding, the easiest solution is probably just to check for NULL when pushing frames.
[-]Null pointer trouble[/-][+]undefined behavior: tstate->datastack_top == NULL[/+]markshannon commentedon Sep 5, 2022
Given that:
datastack_topto be NULL fixes the root cause.I think we should go back to ensuring that
datastack_topis neverNULL, at least in the long term.In the short term, we could add the
NULLcheck for 3.11, then make the larger change (including #32303) for 3.12@pablogsal thoughts?
kumaraditya303 commentedon Sep 5, 2022
@markshannon The latest version of greenlet is compatible with 3.11 https://pypi.org/project/greenlet/#files
pablogsal commentedon Sep 5, 2022
As mentioned previously, greenlet has been released being compatible with 3.11 so whatever we do we should try to not break it.
What consequences ensuring that
datastack_topis neverNULLwill have?I agree that for 3.11 (and given that the last RC release is today) we should add a null check as long as we understand the consequences of that because we really don't want to learn about side effects later.
markshannon commentedon Sep 5, 2022
Is greenlet compatible with 3.11? There has been no work on it since March
10 remaining items
matthiasgoergens commentedon Sep 5, 2022
More specifically, in this case the arithmetic is already undefined.
Null + anything is undefined.
You don't even need to use the pointer in any way afterwards or compare it etc.
Undefined behaviour also 'travels backwards in time'. Ie it removes any obligation on the compiler for the entire execution, not just for what happens after the UB occurs.
(I haven't seen this instance here cause any problems in practice, but I also didn't look for problems. I stopped at getting a reproduction, and only looked into the causes a bit.)
So the potential problem is not so much that your comparison will give an unexpected result, but that an optimisting compiler will remove the entire branch that leads up to the UB (because compilers like to treat UB by just pretending the circumstances in which it occurs can never happen, and this lets them remove code, and thus win speed benchmarks.)
markshannon commentedon Sep 6, 2022
Greenlets sets
datastack_toptoNULL.Technically, greenlets would be breaking a CPython invariant, but the effect is the same.
pablogsal commentedon Sep 6, 2022
How confident are you adding
NULLchecks for 3.11 and fixing this in 3.12 properly? Are we going to find more problems around this area just because the patch is insufficient?markshannon commentedon Sep 6, 2022
Fairly confident
datastack_toponly occurs 15 or so times in the code, and I've checked them all.vstinner commentedon Sep 6, 2022
Oh, that's surpising. With my change, co-authored by Miro Hrončok (@hroncok), greenlet only saves/restores datastack_top value: python-greenlet/greenlet@31ccde2
pablogsal commentedon Sep 6, 2022
Ok, let's go with the NULL check then for 3.11 unless there is a better alternative
markshannon commentedon Sep 6, 2022
A new greenlet has a
NULLframestack, so when it swaps the greenlet with the current frame, it sets the framestack toNULL. At least, that is my understanding.python-greenlet/greenlet@31ccde2#diff-98aec0a05657427241b330f6514931482276b068dc3b15d2b4102a1871b9a9a0R628
GH-96569: Add two NULL checks to avoid undefined behavior. (GH-96585)
[3.11] GH-96569: Avoid undefined behavior (#96616)
gvanrossum commentedon Sep 11, 2022
Does this still need to stay open? @pablogsal @markshannon
matthiasgoergens commentedon Sep 11, 2022
I think we can close this, because we have merged fixes for both main (a while ago) and 3.11 (more recently).
I am closing this issue. But please feel free to reopen, if necessary.