From b1776d2843a66bf1b31f7c113cbedd8c87154061 Mon Sep 17 00:00:00 2001 From: Anselm Kruis Date: Sun, 22 Aug 2021 23:20:37 +0200 Subject: [PATCH 1/2] Stackless issue #303: PEP-578 Audit Hooks for Stackless Stackless now raises more auditing events. - Reducing objects of type AsyncGeneratorType, CoroutineType or GeneratorType, reading the attribute 'tasklet.frame' or calling 'PyTasklet_GetFrame()' raises an auditing event "sys._getframe" with no arguments. - Unpickling of a frame that could be evaluated now raises auditing event "stackless.frame.__setstate__" with no arguments and, if the frame has a trace function, also auditing event "sys.settrace". - If Stackless pickle flag bit 'PICKLEFLAGS_PRESERVE_AG_FINALIZER' is set then unpickling an async_generator with a pickled finalizer raises auditing event "stackless.async_generator.set_finalizer". --- Doc/c-api/stackless.rst | 2 ++ Doc/library/stackless/pickling.rst | 22 ++++++++++++++++++++++ Doc/library/stackless/tasklets.rst | 27 ++++++++++++++++++++------- Python/_warnings.c | 8 +++++--- Stackless/changelog.txt | 13 +++++++++++++ Stackless/module/taskletobject.c | 9 +++++++-- Stackless/pickling/prickelpit.c | 13 +++++++++++++ 7 files changed, 82 insertions(+), 12 deletions(-) diff --git a/Doc/c-api/stackless.rst b/Doc/c-api/stackless.rst index 15bc1072856077..b0303ba169bc24 100644 --- a/Doc/c-api/stackless.rst +++ b/Doc/c-api/stackless.rst @@ -187,6 +187,8 @@ Tasklets Returns the current frame that *task* is executing in, or *NULL* + .. audit-event:: sys._getframe "" c.PyTasklet_GetFrame + .. c:function:: int PyTasklet_IsMain(PyTaskletObject *task) Returns ``1`` if *task* is the main tasklet, otherwise ``0``. diff --git a/Doc/library/stackless/pickling.rst b/Doc/library/stackless/pickling.rst index 2b630235653577..69c0522add3691 100644 --- a/Doc/library/stackless/pickling.rst +++ b/Doc/library/stackless/pickling.rst @@ -147,6 +147,13 @@ types: C-types PyAsyncGenASend and PyAsyncGenAThrow (see :pep:`525`) as well as all kinds of :ref:`Dictionary view objects `. +Reduction functions raise appropriate auditing hooks. + +.. audit-event:: sys._getframe "" + + Reducing objects of type :data:`~types.AsyncGeneratorType`, :data:`~types.CoroutineType` or + :data:`~types.GeneratorType` raises an auditing event ``sys._getframe`` with no arguments. + Code ==== @@ -177,6 +184,16 @@ to execute it raises If a program tries to unpickle a frame using a code object whose first bytecode instruction is invalid, then |SLP| marks the frame as invalid. Any attempt to execute the frame raises :exc:`RuntimeError`. +.. audit-event:: stackless.frame.__setstate__ "" + + |SLP| raises a auditing event ``stackless.frame.__setstate__`` with no arguments + on unpickling frames that could be evaluated. + +.. audit-event:: sys.settrace "" + + |SLP| raises a auditing event ``sys.settrace`` with no arguments on unpickling frames + with a trace function that could be executed. + Functions ========= @@ -222,3 +239,8 @@ If :const:`~stackless.PICKLEFLAGS_PRESERVE_AG_FINALIZER` has been set and if ``ag_finalizer`` by value. Otherwise, if :const:`~stackless.PICKLEFLAGS_RESET_AG_FINALIZER` has been set, |SLP| unpickles ``ag_finalizer`` as uninitialised. + +.. audit-event:: stackless.async_generator.set_finalizer "" + + If ``ag_finalizer`` was unpickled by value, this function raises a auditing event + ``stackless.async_generator.set_finalizer`` with no arguments. diff --git a/Doc/library/stackless/tasklets.rst b/Doc/library/stackless/tasklets.rst index 620fca97a9c07e..eb5526c3cdd444 100644 --- a/Doc/library/stackless/tasklets.rst +++ b/Doc/library/stackless/tasklets.rst @@ -433,6 +433,8 @@ The ``tasklet`` class See :meth:`object.__reduce_ex__`. + .. audit-event:: sys._getframe "" tasklet.__reduce_ex__ + .. method:: tasklet.__setstate__(state) See :meth:`object.__setstate__`. @@ -444,18 +446,20 @@ The ``tasklet`` class the :class:`~contextvars.Context` object of the tasklet to the :class:`~contextvars.Context` object of the current tasklet. - .. versionchanged:: 3.8 - - If the state contains a trace- or profile-function :meth:`~__setstate__` now - raises an auditing event ``sys.settrace`` resp. ``sys.setprofile`` with - no arguments. - :param state: the state as given by ``__reduce_ex__(...)[2]`` :type state: :class:`tuple` :return: self :rtype: :class:`tasklet` :raises RuntimeError: if the tasklet is already alive + If the state contains a trace- or profile-function :meth:`~__setstate__` raises + auditing events. + + .. audit-event:: sys.settrace "" tasklet.__setstate__ + + .. audit-event:: sys.setprofile "" tasklet.__setstate__ + + The following (read-only) attributes allow tasklet state to be checked: .. attribute:: tasklet.alive @@ -578,11 +582,20 @@ and thus may not be available in all |SLP| implementations. are the tasklet counterparts of the functions :func:`sys.settrace`, :func:`sys.gettrace`, :func:`sys.setprofile` and :func:`sys.getprofile`. - .. versionchanged:: 3.8 + .. audit-event:: sys.settrace "" tasklet.trace_function + + .. audit-event:: sys.setprofile "" tasklet.profile_function Assignments to these attributes now raise an auditing event ``sys.settrace`` resp. ``sys.setprofile`` with no arguments. +.. attribute:: tasklet.frame + + The current frame of the tasklet or :data:`None`. + + .. audit-event:: sys._getframe "" tasklet.frame + + ^^^^^^^^^^^^^^^^^^ Tasklet Life Cycle diff --git a/Python/_warnings.c b/Python/_warnings.c index c73383ece0a340..8cfdb4dc260852 100644 --- a/Python/_warnings.c +++ b/Python/_warnings.c @@ -1,7 +1,7 @@ #include "Python.h" #include "pycore_pystate.h" #include "frameobject.h" -#include "stackless_api.h" +#include "pycore_stackless.h" #include "clinic/_warnings.c.h" #define MODULE_NAME "_warnings" @@ -835,9 +835,11 @@ setup_context(Py_ssize_t stack_level, PyObject **filename, int *lineno, PyFrameObject *f = NULL; PyObject *current = PyStackless_GetCurrent(); if (current != NULL) { - f = (PyFrameObject *)PyTasklet_GetFrame((PyTaskletObject*)current); + f = slp_get_frame((PyTaskletObject*)current); /* returns a borrowed reference */ + while (f != NULL && !PyFrame_Check(f)) { + f = f->f_back; + } Py_DECREF(current); - Py_XDECREF(f); /* turn it into a borrowed reference */ } /* fallback to the state frame */ if (f == NULL) diff --git a/Stackless/changelog.txt b/Stackless/changelog.txt index 19f8b7ce9a8db0..a2ed4bf78c7541 100644 --- a/Stackless/changelog.txt +++ b/Stackless/changelog.txt @@ -9,6 +9,19 @@ What's New in Stackless 3.X.X? *Release date: 20XX-XX-XX* +- https://github.com/stackless-dev/stackless/issues/303 + Stackless now raises more auditing events: + - Reducing objects of type AsyncGeneratorType, CoroutineType or + GeneratorType, reading the attribute 'tasklet.frame' or calling + 'PyTasklet_GetFrame()' raises an auditing event "sys._getframe" with no + arguments. + - Unpickling of a frame that could be evaluated now raises auditing event + "stackless.frame.__setstate__" with no arguments and, if the frame has a + trace function, also auditing event "sys.settrace". + - If Stackless pickle flag bit 'PICKLEFLAGS_PRESERVE_AG_FINALIZER' is set + then unpickling an async_generator with a pickled finalizer raises auditing + event "stackless.async_generator.set_finalizer". + What's New in Stackless 3.8.0 and 3.8.1? ======================================== diff --git a/Stackless/module/taskletobject.c b/Stackless/module/taskletobject.c index 5aac5fa40001d9..d28e586179b943 100644 --- a/Stackless/module/taskletobject.c +++ b/Stackless/module/taskletobject.c @@ -655,7 +655,10 @@ tasklet_reduce(PyTaskletObject * t, PyObject *value) f = t->f.frame; while (f != NULL) { int ret; - PyObject * frame_reducer = slp_reduce_frame(f); + PyObject * frame_reducer; + if (PySys_Audit("sys._getframe", NULL)) + goto err_exit; + frame_reducer = slp_reduce_frame(f); if (frame_reducer == NULL) goto err_exit; ret = PyList_Append(lis, frame_reducer); @@ -1912,8 +1915,10 @@ tasklet_get_frame(PyTaskletObject *task, void *closure) PyObject * PyTasklet_GetFrame(PyTaskletObject *task) { - PyFrameObject *f = (PyFrameObject *) slp_get_frame(task); + if (PySys_Audit("sys._getframe", NULL)) + return NULL; + PyFrameObject *f = slp_get_frame(task); while (f != NULL && !PyFrame_Check(f)) { f = f->f_back; } diff --git a/Stackless/pickling/prickelpit.c b/Stackless/pickling/prickelpit.c index c9825632a8d78c..78ec671b575a85 100644 --- a/Stackless/pickling/prickelpit.c +++ b/Stackless/pickling/prickelpit.c @@ -1212,6 +1212,13 @@ frame_setstate(PyFrameObject *f, PyObject *args) /* See if this frame is valid to be run. */ f->f_executing = valid ? f_executing : SLP_FRAME_EXECUTING_INVALID; + if(valid && f_executing) { + if (PySys_Audit("stackless.frame.__setstate__", NULL)) + goto err_exit; + if (f->f_trace && PySys_Audit("sys.settrace", NULL)) { + goto err_exit; + } + } Py_TYPE(f) = &PyFrame_Type; Py_INCREF(f); @@ -1684,6 +1691,8 @@ reduce_to_gen_obj_head(gen_obj_head_ty *goh, PyFrameObject * frame, const _PyErr /* Pickle NULL as None. See gen_setstate() for the corresponding * unpickling code. */ if (frame != NULL) { + if (PySys_Audit("sys._getframe", NULL)) + return -1; goh->frame = slp_reduce_frame(frame); if (goh->frame == NULL) return -1; @@ -2186,6 +2195,10 @@ async_gen_setstate(PyObject *self, PyObject *args) Py_CLEAR(async_gen->ag_finalizer); } else { + if(PySys_Audit("stackless.async_generator.set_finalizer", NULL)) { + async_gen = NULL; + goto error; + } async_gen->ag_hooks_inited = hooks_inited; Py_INCREF(finalizer); Py_XSETREF(async_gen->ag_finalizer, finalizer); From ca29f3b80a22297d5ab8e87ae78ddd5c8980d6ec Mon Sep 17 00:00:00 2001 From: Anselm Kruis Date: Tue, 24 Aug 2021 16:44:48 +0200 Subject: [PATCH 2/2] add frame argumnet to the auditing event stackless.frame.__setstate__ --- Doc/library/stackless/pickling.rst | 10 +++++----- Stackless/changelog.txt | 4 ++-- Stackless/pickling/prickelpit.c | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Doc/library/stackless/pickling.rst b/Doc/library/stackless/pickling.rst index 69c0522add3691..07f8f81d494580 100644 --- a/Doc/library/stackless/pickling.rst +++ b/Doc/library/stackless/pickling.rst @@ -184,15 +184,15 @@ to execute it raises If a program tries to unpickle a frame using a code object whose first bytecode instruction is invalid, then |SLP| marks the frame as invalid. Any attempt to execute the frame raises :exc:`RuntimeError`. -.. audit-event:: stackless.frame.__setstate__ "" +.. audit-event:: stackless.frame.__setstate__ frame - |SLP| raises a auditing event ``stackless.frame.__setstate__`` with no arguments - on unpickling frames that could be evaluated. + On unpickling frames |SLP| raises an auditing event ``stackless.frame.__setstate__`` with the fully initialized + frame object as the argument, if the frame could be evaluated. .. audit-event:: sys.settrace "" - |SLP| raises a auditing event ``sys.settrace`` with no arguments on unpickling frames - with a trace function that could be executed. + On unpickling frames |SLP| raises an auditing event ``sys.settrace`` with no arguments + if the frame has a trace function that could be executed. Functions diff --git a/Stackless/changelog.txt b/Stackless/changelog.txt index a2ed4bf78c7541..4f19b2f45560e6 100644 --- a/Stackless/changelog.txt +++ b/Stackless/changelog.txt @@ -16,8 +16,8 @@ What's New in Stackless 3.X.X? 'PyTasklet_GetFrame()' raises an auditing event "sys._getframe" with no arguments. - Unpickling of a frame that could be evaluated now raises auditing event - "stackless.frame.__setstate__" with no arguments and, if the frame has a - trace function, also auditing event "sys.settrace". + "stackless.frame.__setstate__" and, if the frame has a trace function, + also auditing event "sys.settrace". - If Stackless pickle flag bit 'PICKLEFLAGS_PRESERVE_AG_FINALIZER' is set then unpickling an async_generator with a pickled finalizer raises auditing event "stackless.async_generator.set_finalizer". diff --git a/Stackless/pickling/prickelpit.c b/Stackless/pickling/prickelpit.c index 78ec671b575a85..a24dcde3b96072 100644 --- a/Stackless/pickling/prickelpit.c +++ b/Stackless/pickling/prickelpit.c @@ -1212,15 +1212,15 @@ frame_setstate(PyFrameObject *f, PyObject *args) /* See if this frame is valid to be run. */ f->f_executing = valid ? f_executing : SLP_FRAME_EXECUTING_INVALID; + Py_TYPE(f) = &PyFrame_Type; if(valid && f_executing) { - if (PySys_Audit("stackless.frame.__setstate__", NULL)) + if (PySys_Audit("stackless.frame.__setstate__", "O", f)) goto err_exit; if (f->f_trace && PySys_Audit("sys.settrace", NULL)) { goto err_exit; } } - Py_TYPE(f) = &PyFrame_Type; Py_INCREF(f); return (PyObject *) f; err_exit: