-
-
Notifications
You must be signed in to change notification settings - Fork 30.8k
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
NULL ptr deref in _PyCode_ConstantKey when compiling code #128632
Comments
Hi there, I'm not familiar with the code so can't say WHY this is happening but the immediate cause of this seems to be the offset calculation in Python/assemble.c compute_localsplus_info() line 535, the last loop for freevars does not account for a cellvar put immediately above and overwrites that pointer instead of putting at the next location in the tuple: https://github.com/python/cpython/blob/main/Python/assemble.c#L535 When corrected the following error appears instead (no segfault): Traceback (most recent call last):
File "<python-input-1>", line 1, in <module>
compile(src, '<fuzz>', start, optimize=opt)
~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
SystemError: compiler_lookup_arg(name='__classdict__') with reftype=5 failed in in2; freevars of code <generic parameters of Gí>: ('__classdict__',) EDIT: Segfault reproducible with just this script: class A:
class B[__classdict__]: pass |
Thanks for that! Confirmed on current main back to 3.12.8. This is possibly a security problem, because the interpreter can be crashed with just cc @Eclips4 as an ast expert (you might like this issue!) |
Tracked the problem down to the fact that in this particular case a cell var and a free var get the same offset in locals which causes the free var pointer for the last umd->u_varnames = {'__classdict__': 0, '.generic_base': 1}
umd->u_cellvars = {'.type_params': 0}
umd->u_freevars = {'__classdict__': 0} # assuming the value should be a 1? Included a snippet of code here below which will detect the condition and raise a "Fix": Replace Python/assemble.c lines 506-538 with the following: // This counter mirrors the fix done in fix_cell_offsets().
int numdropped = 0, maxcelloffset = -1;
pos = 0;
while (PyDict_Next(umd->u_cellvars, &pos, &k, &v)) {
int has_name = PyDict_Contains(umd->u_varnames, k);
RETURN_IF_ERROR(has_name);
if (has_name) {
// Skip cells that are already covered by locals.
numdropped += 1;
continue;
}
int offset = PyLong_AsInt(v);
if (offset == -1 && PyErr_Occurred()) {
return ERROR;
}
assert(offset >= 0);
offset += nlocals - numdropped;
maxcelloffset = Py_MAX(maxcelloffset, offset);
assert(offset < nlocalsplus);
_Py_set_localsplus_info(offset, k, CO_FAST_CELL, names, kinds);
}
pos = 0;
while (PyDict_Next(umd->u_freevars, &pos, &k, &v)) {
int offset = PyLong_AsInt(v);
if (offset == -1 && PyErr_Occurred()) {
return ERROR;
}
assert(offset >= 0);
offset += nlocals - numdropped;
assert(offset < nlocalsplus);
// TODO: remove once gh-128632 is fixed properly or leave to prevent future unforseen segfaults?
if (offset <= maxcelloffset) {
PyErr_SetString(PyExc_SystemError,
"overlapping cell and free variable offsets detected (see gh-128632)");
return ERROR;
}
_Py_set_localsplus_info(offset, k, CO_FAST_FREE, names, kinds);
} P.S. If this niche case is not worth fixing properly let me know and I will send up a PR with this "fix" to at least avoid a crash and a test case. |
Thanks for minimizing this! |
Thank you @tom-pytel for the analysis. CC @JelleZijlstra . |
Crash report
What happened?
Unfortunately it's a slightly large minimal reproducer. You can use
xxd -r
to go from the hexdump to the actual binary.Found by OSS-Fuzz.
CPython versions tested on:
CPython main branch
Operating systems tested on:
No response
Output from running 'python -VV' on the command line:
No response
The text was updated successfully, but these errors were encountered: