PyCode_AddWatcher does not validate that the callback is non-NULL. As a result, the function will still assign a watcher slot, store the NULL pointer in interp->code_watchers[i], and set the corresponding bit in active_code_watchers.
|
|
|
int |
|
PyCode_AddWatcher(PyCode_WatchCallback callback) |
|
{ |
|
PyInterpreterState *interp = _PyInterpreterState_GET(); |
|
assert(interp->_initialized); |
|
|
|
for (int i = 0; i < CODE_MAX_WATCHERS; i++) { |
|
if (!interp->code_watchers[i]) { |
|
interp->code_watchers[i] = callback; |
|
interp->active_code_watchers |= (1 << i); |
|
return i; |
|
} |
|
} |
|
|
|
PyErr_SetString(PyExc_RuntimeError, "no more code watcher IDs available"); |
|
return -1; |
|
} |
|
|
This creates an inconsistent internal state, because notify_code_watchers assumes, in release build, that any watcher bit set implies a non-NULL callback.
|
|
|
static void |
|
notify_code_watchers(PyCodeEvent event, PyCodeObject *co) |
|
{ |
|
assert(Py_REFCNT(co) > 0); |
|
PyInterpreterState *interp = _PyInterpreterState_GET(); |
|
assert(interp->_initialized); |
|
uint8_t bits = interp->active_code_watchers; |
|
int i = 0; |
|
while (bits) { |
|
assert(i < CODE_MAX_WATCHERS); |
|
if (bits & 1) { |
|
PyCode_WatchCallback cb = interp->code_watchers[i]; |
|
// callback must be non-null if the watcher bit is set |
|
assert(cb != NULL); |
|
if (cb(event, co) < 0) { |
|
PyErr_FormatUnraisable( |
|
"Exception ignored in %s watcher callback for %R", |
|
code_event_name(event), co); |
|
} |
|
} |
|
i++; |
|
bits >>= 1; |
|
} |
|
} |
|
|
PyCode_AddWatcherdoes not validate that the callback is non-NULL. As a result, the function will still assign a watcher slot, store the NULL pointer ininterp->code_watchers[i], and set the corresponding bit inactive_code_watchers.cpython/Objects/codeobject.c
Lines 64 to 82 in b8d3fdd
This creates an inconsistent internal state, because
notify_code_watchersassumes, in release build, that any watcher bit set implies a non-NULL callback.cpython/Objects/codeobject.c
Lines 39 to 64 in b8d3fdd