-
-
Notifications
You must be signed in to change notification settings - Fork 33.7k
Closed as not planned
Closed as not planned
Copy link
Labels
Description
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.
Lines 64 to 82 in b8d3fdd
| 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.
Lines 39 to 64 in b8d3fdd
| 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; | |
| } | |
| } | |