Skip to content

PyCode_AddWatcher callback must be non-null if the watcher bit is set #143042

@nybblista

Description

@nybblista

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;
}
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions