]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
GH-108390: Prevent non-local events being set with `sys.monitoring.set_local_events...
authorMark Shannon <mark@hotpy.org>
Tue, 5 Sep 2023 07:03:53 +0000 (08:03 +0100)
committerGitHub <noreply@github.com>
Tue, 5 Sep 2023 07:03:53 +0000 (08:03 +0100)
Include/cpython/code.h
Include/internal/pycore_instruments.h
Include/internal/pycore_interp.h
Lib/test/test_monitoring.py
Misc/NEWS.d/next/Core and Builtins/2023-08-13-17-18-22.gh-issue-108390.TkBccC.rst [new file with mode: 0644]
Python/ceval.c
Python/instrumentation.c

index 24c5ec23590c9418e22cf9927ba335143f3486f3..45b09a1265df80266b3f95e8f45dbf900559d317 100644 (file)
@@ -8,16 +8,21 @@
 extern "C" {
 #endif
 
-
+/* Count of all local monitoring events */
+#define  _PY_MONITORING_LOCAL_EVENTS 10
 /* Count of all "real" monitoring events (not derived from other events) */
 #define _PY_MONITORING_UNGROUPED_EVENTS 15
 /* Count of all  monitoring events */
 #define _PY_MONITORING_EVENTS 17
 
-/* Table of which tools are active for each monitored event. */
-typedef struct _Py_Monitors {
+/* Tables of which tools are active for each monitored event. */
+typedef struct _Py_LocalMonitors {
+    uint8_t tools[_PY_MONITORING_LOCAL_EVENTS];
+} _Py_LocalMonitors;
+
+typedef struct _Py_GlobalMonitors {
     uint8_t tools[_PY_MONITORING_UNGROUPED_EVENTS];
-} _Py_Monitors;
+} _Py_GlobalMonitors;
 
 /* Each instruction in a code object is a fixed-width value,
  * currently 2 bytes: 1-byte opcode + 1-byte oparg.  The EXTENDED_ARG
@@ -88,9 +93,9 @@ typedef struct {
  */
 typedef struct {
     /* Monitoring specific to this code object */
-    _Py_Monitors local_monitors;
+    _Py_LocalMonitors local_monitors;
     /* Monitoring that is active on this code object */
-    _Py_Monitors active_monitors;
+    _Py_LocalMonitors active_monitors;
     /* The tools that are to be notified for events for the matching code unit */
     uint8_t *tools;
     /* Information to support line events */
index 43214aef7f7a1b45d91ffb8eb63531ca14aa9e1e..fad475c3dafb99bc85af7c2ddc128bd60723c151 100644 (file)
@@ -29,7 +29,7 @@ extern "C" {
 #define PY_MONITORING_EVENT_STOP_ITERATION 9
 
 #define PY_MONITORING_IS_INSTRUMENTED_EVENT(ev) \
-    ((ev) <= PY_MONITORING_EVENT_STOP_ITERATION)
+    ((ev) < _PY_MONITORING_LOCAL_EVENTS)
 
 /* Other events, mainly exceptions */
 
index f171c546efd53c9186fb35c919a099bbb6e53660..e0b7a3259292571e8589079056ec69eaa9e28821 100644 (file)
@@ -187,7 +187,7 @@ struct _is {
     uint16_t optimizer_resume_threshold;
     uint16_t optimizer_backedge_threshold;
 
-    _Py_Monitors monitors;
+    _Py_GlobalMonitors monitors;
     bool f_opcode_trace_set;
     bool sys_profile_initialized;
     bool sys_trace_initialized;
index 845185be737eb2c6740abbfae81dfaf5ac489290..95fe8d03b0b29a1b2d4be4dde5eb8c5f26b6c53e 100644 (file)
@@ -1218,9 +1218,11 @@ class TestInstallIncrementallly(MonitoringTestBase, unittest.TestCase):
         self.check_events(self.func2,
                           recorders = recorders, must_include = self.MUST_INCLUDE_CI)
 
+LOCAL_RECORDERS = CallRecorder, LineRecorder, CReturnRecorder, CRaiseRecorder
+
 class TestLocalEvents(MonitoringTestBase, unittest.TestCase):
 
-    def check_events(self, func, expected, tool=TEST_TOOL, recorders=(ExceptionRecorder,)):
+    def check_events(self, func, expected, tool=TEST_TOOL, recorders=()):
         try:
             self.assertEqual(sys.monitoring._all_events(), {})
             event_list = []
@@ -1248,7 +1250,7 @@ class TestLocalEvents(MonitoringTestBase, unittest.TestCase):
             line2 = 2
             line3 = 3
 
-        self.check_events(func1, recorders = MANY_RECORDERS, expected = [
+        self.check_events(func1, recorders = LOCAL_RECORDERS, expected = [
             ('line', 'func1', 1),
             ('line', 'func1', 2),
             ('line', 'func1', 3)])
@@ -1260,7 +1262,7 @@ class TestLocalEvents(MonitoringTestBase, unittest.TestCase):
             [].append(2)
             line3 = 3
 
-        self.check_events(func2, recorders = MANY_RECORDERS, expected = [
+        self.check_events(func2, recorders = LOCAL_RECORDERS, expected = [
             ('line', 'func2', 1),
             ('line', 'func2', 2),
             ('call', 'append', [2]),
@@ -1277,15 +1279,17 @@ class TestLocalEvents(MonitoringTestBase, unittest.TestCase):
                 line = 5
             line = 6
 
-        self.check_events(func3, recorders = MANY_RECORDERS, expected = [
+        self.check_events(func3, recorders = LOCAL_RECORDERS, expected = [
             ('line', 'func3', 1),
             ('line', 'func3', 2),
             ('line', 'func3', 3),
-            ('raise', KeyError),
             ('line', 'func3', 4),
             ('line', 'func3', 5),
             ('line', 'func3', 6)])
 
+    def test_set_non_local_event(self):
+        with self.assertRaises(ValueError):
+            sys.monitoring.set_local_events(TEST_TOOL, just_call.__code__, E.RAISE)
 
 def line_from_offset(code, offset):
     for start, end, line in code.co_lines():
@@ -1698,3 +1702,19 @@ class TestRegressions(MonitoringTestBase, unittest.TestCase):
             self.assertEqual(caught, "inner")
         finally:
             sys.monitoring.set_events(TEST_TOOL, 0)
+
+    def test_108390(self):
+
+        class Foo:
+            def __init__(self, set_event):
+                if set_event:
+                    sys.monitoring.set_events(TEST_TOOL, E.PY_RESUME)
+
+        def make_foo_optimized_then_set_event():
+            for i in range(100):
+                Foo(i == 99)
+
+        try:
+            make_foo_optimized_then_set_event()
+        finally:
+            sys.monitoring.set_events(TEST_TOOL, 0)
diff --git a/Misc/NEWS.d/next/Core and Builtins/2023-08-13-17-18-22.gh-issue-108390.TkBccC.rst b/Misc/NEWS.d/next/Core and Builtins/2023-08-13-17-18-22.gh-issue-108390.TkBccC.rst
new file mode 100644 (file)
index 0000000..3ed5960
--- /dev/null
@@ -0,0 +1,4 @@
+Raise an exception when setting a non-local event (``RAISE``, ``EXCEPTION_HANDLED``,
+etc.) in ``sys.monitoring.set_local_events``.
+
+Fixes crash when tracing in recursive calls to Python classes.
index 6f90d8e6fd064b4550e152c72182340c425210ca..b02bf60315b7e1f9dd549d007ed7395bbb859c1a 100644 (file)
@@ -1978,28 +1978,30 @@ do_monitor_exc(PyThreadState *tstate, _PyInterpreterFrame *frame,
     return err;
 }
 
-static inline int
-no_tools_for_event(PyThreadState *tstate, _PyInterpreterFrame *frame, int event)
+static inline bool
+no_tools_for_global_event(PyThreadState *tstate, int event)
 {
+    return tstate->interp->monitors.tools[event] == 0;
+}
+
+static inline bool
+no_tools_for_local_event(PyThreadState *tstate, _PyInterpreterFrame *frame, int event)
+{
+    assert(event < _PY_MONITORING_LOCAL_EVENTS);
     _PyCoMonitoringData *data = _PyFrame_GetCode(frame)->_co_monitoring;
     if (data) {
-        if (data->active_monitors.tools[event] == 0) {
-            return 1;
-        }
+        return data->active_monitors.tools[event] == 0;
     }
     else {
-        if (tstate->interp->monitors.tools[event] == 0) {
-            return 1;
-        }
+        return no_tools_for_global_event(tstate, event);
     }
-    return 0;
 }
 
 static void
 monitor_raise(PyThreadState *tstate, _PyInterpreterFrame *frame,
               _Py_CODEUNIT *instr)
 {
-    if (no_tools_for_event(tstate, frame, PY_MONITORING_EVENT_RAISE)) {
+    if (no_tools_for_global_event(tstate, PY_MONITORING_EVENT_RAISE)) {
         return;
     }
     do_monitor_exc(tstate, frame, instr, PY_MONITORING_EVENT_RAISE);
@@ -2009,7 +2011,7 @@ static void
 monitor_reraise(PyThreadState *tstate, _PyInterpreterFrame *frame,
               _Py_CODEUNIT *instr)
 {
-    if (no_tools_for_event(tstate, frame, PY_MONITORING_EVENT_RERAISE)) {
+    if (no_tools_for_global_event(tstate, PY_MONITORING_EVENT_RERAISE)) {
         return;
     }
     do_monitor_exc(tstate, frame, instr, PY_MONITORING_EVENT_RERAISE);
@@ -2019,7 +2021,7 @@ static int
 monitor_stop_iteration(PyThreadState *tstate, _PyInterpreterFrame *frame,
                        _Py_CODEUNIT *instr)
 {
-    if (no_tools_for_event(tstate, frame, PY_MONITORING_EVENT_STOP_ITERATION)) {
+    if (no_tools_for_local_event(tstate, frame, PY_MONITORING_EVENT_STOP_ITERATION)) {
         return 0;
     }
     return do_monitor_exc(tstate, frame, instr, PY_MONITORING_EVENT_STOP_ITERATION);
@@ -2030,7 +2032,7 @@ monitor_unwind(PyThreadState *tstate,
                _PyInterpreterFrame *frame,
                _Py_CODEUNIT *instr)
 {
-    if (no_tools_for_event(tstate, frame, PY_MONITORING_EVENT_PY_UNWIND)) {
+    if (no_tools_for_global_event(tstate, PY_MONITORING_EVENT_PY_UNWIND)) {
         return;
     }
     do_monitor_exc(tstate, frame, instr, PY_MONITORING_EVENT_PY_UNWIND);
@@ -2042,7 +2044,7 @@ monitor_handled(PyThreadState *tstate,
                 _PyInterpreterFrame *frame,
                 _Py_CODEUNIT *instr, PyObject *exc)
 {
-    if (no_tools_for_event(tstate, frame, PY_MONITORING_EVENT_EXCEPTION_HANDLED)) {
+    if (no_tools_for_global_event(tstate, PY_MONITORING_EVENT_EXCEPTION_HANDLED)) {
         return 0;
     }
     return _Py_call_instrumentation_arg(tstate, PY_MONITORING_EVENT_EXCEPTION_HANDLED, frame, instr, exc);
@@ -2053,7 +2055,7 @@ monitor_throw(PyThreadState *tstate,
               _PyInterpreterFrame *frame,
               _Py_CODEUNIT *instr)
 {
-    if (no_tools_for_event(tstate, frame, PY_MONITORING_EVENT_PY_THROW)) {
+    if (no_tools_for_global_event(tstate, PY_MONITORING_EVENT_PY_THROW)) {
         return;
     }
     do_monitor_exc(tstate, frame, instr, PY_MONITORING_EVENT_PY_THROW);
index 36459687be793684cb7250f1807bcb75e742a838..9065043f55d8a70cd07d2c6230efb13e13dd808c 100644 (file)
@@ -138,9 +138,9 @@ is_instrumented(int opcode)
 
 #ifndef NDEBUG
 static inline bool
-monitors_equals(_Py_Monitors a, _Py_Monitors b)
+monitors_equals(_Py_LocalMonitors a, _Py_LocalMonitors b)
 {
-    for (int i = 0; i < _PY_MONITORING_UNGROUPED_EVENTS; i++) {
+    for (int i = 0; i < _PY_MONITORING_LOCAL_EVENTS; i++) {
         if (a.tools[i] != b.tools[i]) {
             return false;
         }
@@ -149,42 +149,47 @@ monitors_equals(_Py_Monitors a, _Py_Monitors b)
 }
 #endif
 
-static inline _Py_Monitors
-monitors_sub(_Py_Monitors a, _Py_Monitors b)
+static inline _Py_LocalMonitors
+monitors_sub(_Py_LocalMonitors a, _Py_LocalMonitors b)
 {
-    _Py_Monitors res;
-    for (int i = 0; i < _PY_MONITORING_UNGROUPED_EVENTS; i++) {
+    _Py_LocalMonitors res;
+    for (int i = 0; i < _PY_MONITORING_LOCAL_EVENTS; i++) {
         res.tools[i] = a.tools[i] & ~b.tools[i];
     }
     return res;
 }
 
 #ifndef NDEBUG
-static inline _Py_Monitors
-monitors_and(_Py_Monitors a, _Py_Monitors b)
+static inline _Py_LocalMonitors
+monitors_and(_Py_LocalMonitors a, _Py_LocalMonitors b)
 {
-    _Py_Monitors res;
-    for (int i = 0; i < _PY_MONITORING_UNGROUPED_EVENTS; i++) {
+    _Py_LocalMonitors res;
+    for (int i = 0; i < _PY_MONITORING_LOCAL_EVENTS; i++) {
         res.tools[i] = a.tools[i] & b.tools[i];
     }
     return res;
 }
 #endif
 
-static inline _Py_Monitors
-monitors_or(_Py_Monitors a, _Py_Monitors b)
+/* The union of the *local* events in a and b.
+ * Global events like RAISE are ignored.
+ * Used for instrumentation, as only local
+ * events get instrumented.
+ */
+static inline _Py_LocalMonitors
+local_union(_Py_GlobalMonitors a, _Py_LocalMonitors b)
 {
-    _Py_Monitors res;
-    for (int i = 0; i < _PY_MONITORING_UNGROUPED_EVENTS; i++) {
+    _Py_LocalMonitors res;
+    for (int i = 0; i < _PY_MONITORING_LOCAL_EVENTS; i++) {
         res.tools[i] = a.tools[i] | b.tools[i];
     }
     return res;
 }
 
 static inline bool
-monitors_are_empty(_Py_Monitors m)
+monitors_are_empty(_Py_LocalMonitors m)
 {
-    for (int i = 0; i < _PY_MONITORING_UNGROUPED_EVENTS; i++) {
+    for (int i = 0; i < _PY_MONITORING_LOCAL_EVENTS; i++) {
         if (m.tools[i]) {
             return false;
         }
@@ -193,9 +198,9 @@ monitors_are_empty(_Py_Monitors m)
 }
 
 static inline bool
-multiple_tools(_Py_Monitors *m)
+multiple_tools(_Py_LocalMonitors *m)
 {
-    for (int i = 0; i < _PY_MONITORING_UNGROUPED_EVENTS; i++) {
+    for (int i = 0; i < _PY_MONITORING_LOCAL_EVENTS; i++) {
         if (_Py_popcount32(m->tools[i]) > 1) {
             return true;
         }
@@ -204,7 +209,19 @@ multiple_tools(_Py_Monitors *m)
 }
 
 static inline _PyMonitoringEventSet
-get_events(_Py_Monitors *m, int tool_id)
+get_local_events(_Py_LocalMonitors *m, int tool_id)
+{
+    _PyMonitoringEventSet result = 0;
+    for (int e = 0; e < _PY_MONITORING_LOCAL_EVENTS; e++) {
+        if ((m->tools[e] >> tool_id) & 1) {
+            result |= (1 << e);
+        }
+    }
+    return result;
+}
+
+static inline _PyMonitoringEventSet
+get_events(_Py_GlobalMonitors *m, int tool_id)
 {
     _PyMonitoringEventSet result = 0;
     for (int e = 0; e < _PY_MONITORING_UNGROUPED_EVENTS; e++) {
@@ -457,10 +474,10 @@ sanity_check_instrumentation(PyCodeObject *code)
     if (data == NULL) {
         return;
     }
-    _Py_Monitors active_monitors = _PyInterpreterState_GET()->monitors;
+    _Py_GlobalMonitors active_monitors = _PyInterpreterState_GET()->monitors;
     if (code->_co_monitoring) {
         _Py_Monitors local_monitors = code->_co_monitoring->local_monitors;
-        active_monitors = monitors_or(active_monitors, local_monitors);
+        active_monitors = local_union(active_monitors, local_monitors);
     }
     assert(monitors_equals(
         code->_co_monitoring->active_monitors,
@@ -909,7 +926,7 @@ is_version_up_to_date(PyCodeObject *code, PyInterpreterState *interp)
 static bool
 instrumentation_cross_checks(PyInterpreterState *interp, PyCodeObject *code)
 {
-    _Py_Monitors expected = monitors_or(
+    _Py_LocalMonitors expected = local_union(
         interp->monitors,
         code->_co_monitoring->local_monitors);
     return monitors_equals(code->_co_monitoring->active_monitors, expected);
@@ -938,12 +955,7 @@ get_tools_for_instruction(PyCodeObject *code, PyInterpreterState *interp, int i,
         }
     }
     else {
-        if (code->_co_monitoring) {
-            tools = code->_co_monitoring->active_monitors.tools[event];
-        }
-        else {
-            tools = interp->monitors.tools[event];
-        }
+        tools = interp->monitors.tools[event];
     }
     return tools;
 }
@@ -1471,7 +1483,7 @@ initialize_lines(PyCodeObject *code)
 }
 
 static void
-initialize_line_tools(PyCodeObject *code, _Py_Monitors *all_events)
+initialize_line_tools(PyCodeObject *code, _Py_LocalMonitors *all_events)
 {
     uint8_t *line_tools = code->_co_monitoring->line_tools;
     assert(line_tools != NULL);
@@ -1491,8 +1503,8 @@ allocate_instrumentation_data(PyCodeObject *code)
             PyErr_NoMemory();
             return -1;
         }
-        code->_co_monitoring->local_monitors = (_Py_Monitors){ 0 };
-        code->_co_monitoring->active_monitors = (_Py_Monitors){ 0 };
+        code->_co_monitoring->local_monitors = (_Py_LocalMonitors){ 0 };
+        code->_co_monitoring->active_monitors = (_Py_LocalMonitors){ 0 };
         code->_co_monitoring->tools = NULL;
         code->_co_monitoring->lines = NULL;
         code->_co_monitoring->line_tools = NULL;
@@ -1509,7 +1521,7 @@ update_instrumentation_data(PyCodeObject *code, PyInterpreterState *interp)
     if (allocate_instrumentation_data(code)) {
         return -1;
     }
-    _Py_Monitors all_events = monitors_or(
+    _Py_LocalMonitors all_events = local_union(
         interp->monitors,
         code->_co_monitoring->local_monitors);
     bool multitools = multiple_tools(&all_events);
@@ -1577,14 +1589,23 @@ _Py_Instrument(PyCodeObject *code, PyInterpreterState *interp)
         return 0;
     }
     int code_len = (int)Py_SIZE(code);
+    /* code->_co_firsttraceable >= code_len indicates
+     * that no instrumentation can be inserted.
+     * Exit early to avoid creating instrumentation
+     * data for potential statically allocated code
+     * objects.
+     * See https://github.com/python/cpython/issues/108390 */
+    if (code->_co_firsttraceable >= code_len) {
+        return 0;
+    }
     if (update_instrumentation_data(code, interp)) {
         return -1;
     }
-    _Py_Monitors active_events = monitors_or(
+    _Py_LocalMonitors active_events = local_union(
         interp->monitors,
         code->_co_monitoring->local_monitors);
-    _Py_Monitors new_events;
-    _Py_Monitors removed_events;
+    _Py_LocalMonitors new_events;
+    _Py_LocalMonitors removed_events;
 
     bool restarted = interp->last_restart_version > code->_co_instrumentation_version;
     if (restarted) {
@@ -1725,10 +1746,22 @@ instrument_all_executing_code_objects(PyInterpreterState *interp) {
 }
 
 static void
-set_events(_Py_Monitors *m, int tool_id, _PyMonitoringEventSet events)
+set_events(_Py_GlobalMonitors *m, int tool_id, _PyMonitoringEventSet events)
 {
     assert(0 <= tool_id && tool_id < PY_MONITORING_TOOL_IDS);
     for (int e = 0; e < _PY_MONITORING_UNGROUPED_EVENTS; e++) {
+        uint8_t *tools = &m->tools[e];
+        int active = (events >> e) & 1;
+        *tools &= ~(1 << tool_id);
+        *tools |= (active << tool_id);
+    }
+}
+
+static void
+set_local_events(_Py_LocalMonitors *m, int tool_id, _PyMonitoringEventSet events)
+{
+    assert(0 <= tool_id && tool_id < PY_MONITORING_TOOL_IDS);
+    for (int e = 0; e < _PY_MONITORING_LOCAL_EVENTS; e++) {
         uint8_t *tools = &m->tools[e];
         int val = (events >> e) & 1;
         *tools &= ~(1 << tool_id);
@@ -1771,19 +1804,23 @@ _PyMonitoring_SetLocalEvents(PyCodeObject *code, int tool_id, _PyMonitoringEvent
 {
     assert(0 <= tool_id && tool_id < PY_MONITORING_TOOL_IDS);
     PyInterpreterState *interp = _PyInterpreterState_GET();
-    assert(events < (1 << _PY_MONITORING_UNGROUPED_EVENTS));
+    assert(events < (1 << _PY_MONITORING_LOCAL_EVENTS));
+    if (code->_co_firsttraceable >= Py_SIZE(code)) {
+        PyErr_Format(PyExc_SystemError, "cannot instrument shim code object '%U'", code->co_name);
+        return -1;
+    }
     if (check_tool(interp, tool_id)) {
         return -1;
     }
     if (allocate_instrumentation_data(code)) {
         return -1;
     }
-    _Py_Monitors *local = &code->_co_monitoring->local_monitors;
-    uint32_t existing_events = get_events(local, tool_id);
+    _Py_LocalMonitors *local = &code->_co_monitoring->local_monitors;
+    uint32_t existing_events = get_local_events(local, tool_id);
     if (existing_events == events) {
         return 0;
     }
-    set_events(local, tool_id, events);
+    set_local_events(local, tool_id, events);
     if (is_version_up_to_date(code, interp)) {
         /* Force instrumentation update */
         code->_co_instrumentation_version = UINT64_MAX;
@@ -1942,7 +1979,7 @@ monitoring_get_events_impl(PyObject *module, int tool_id)
     if (check_valid_tool(tool_id))  {
         return -1;
     }
-    _Py_Monitors *m = &_PyInterpreterState_GET()->monitors;
+    _Py_GlobalMonitors *m = &_PyInterpreterState_GET()->monitors;
     _PyMonitoringEventSet event_set = get_events(m, tool_id);
     return event_set;
 }
@@ -2005,7 +2042,7 @@ monitoring_get_local_events_impl(PyObject *module, int tool_id,
     _PyMonitoringEventSet event_set = 0;
     _PyCoMonitoringData *data = ((PyCodeObject *)code)->_co_monitoring;
     if (data != NULL) {
-        for (int e = 0; e < _PY_MONITORING_UNGROUPED_EVENTS; e++) {
+        for (int e = 0; e < _PY_MONITORING_LOCAL_EVENTS; e++) {
             if ((data->local_monitors.tools[e] >> tool_id) & 1) {
                 event_set |= (1 << e);
             }
@@ -2039,15 +2076,16 @@ monitoring_set_local_events_impl(PyObject *module, int tool_id,
     if (check_valid_tool(tool_id))  {
         return NULL;
     }
-    if (event_set < 0 || event_set >= (1 << _PY_MONITORING_EVENTS)) {
-        PyErr_Format(PyExc_ValueError, "invalid event set 0x%x", event_set);
-        return NULL;
-    }
     if ((event_set & C_RETURN_EVENTS) && (event_set & C_CALL_EVENTS) != C_CALL_EVENTS) {
         PyErr_Format(PyExc_ValueError, "cannot set C_RETURN or C_RAISE events independently");
         return NULL;
     }
     event_set &= ~C_RETURN_EVENTS;
+    if (event_set < 0 || event_set >= (1 << _PY_MONITORING_LOCAL_EVENTS)) {
+        PyErr_Format(PyExc_ValueError, "invalid local event set 0x%x", event_set);
+        return NULL;
+    }
+
     if (_PyMonitoring_SetLocalEvents((PyCodeObject*)code, tool_id, event_set)) {
         return NULL;
     }