Skip to content

bpo-42800: add audit hooks for f_code and tb_frame #24182

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Apr 29, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 10 additions & 5 deletions Doc/extending/newtypes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -287,18 +287,23 @@ combined using bitwise-OR.
+===========================+==============================================+
| :const:`READONLY` | Never writable. |
+---------------------------+----------------------------------------------+
| :const:`READ_RESTRICTED` | Not readable in restricted mode. |
+---------------------------+----------------------------------------------+
| :const:`WRITE_RESTRICTED` | Not writable in restricted mode. |
+---------------------------+----------------------------------------------+
| :const:`RESTRICTED` | Not readable or writable in restricted mode. |
| :const:`AUDIT_READ` | Emit an ``object.__getattr__`` |
| | :ref:`audit events <audit-events>` before |
| | reading. |
+---------------------------+----------------------------------------------+

.. versionchanged:: 3.10
:const:`RESTRICTED`, :const:`READ_RESTRICTED` and :const:`WRITE_RESTRICTED`
are deprecated. However, :const:`READ_RESTRICTED` is an alias for
:const:`AUDIT_READ`, so fields that specify either :const:`RESTRICTED` or
:const:`READ_RESTRICTED` will also raise an audit event.

.. index::
single: READONLY
single: READ_RESTRICTED
single: WRITE_RESTRICTED
single: RESTRICTED
single: AUDIT_READ

An interesting advantage of using the :c:member:`~PyTypeObject.tp_members` table to build
descriptors that are used at runtime is that any attribute defined this way can
Expand Down
2 changes: 1 addition & 1 deletion Doc/library/audit_events.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Audit events table

This table contains all events raised by :func:`sys.audit` or
:c:func:`PySys_Audit` calls throughout the CPython runtime and the
standard library. These calls were added in 3.8.0 or later.
standard library. These calls were added in 3.8.0 or later (see :pep:`578`).

See :func:`sys.addaudithook` and :c:func:`PySys_AddAuditHook` for
information on handling these events.
Expand Down
3 changes: 3 additions & 0 deletions Doc/library/stdtypes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5206,6 +5206,9 @@ environment. Code objects are returned by the built-in :func:`compile` function
and can be extracted from function objects through their :attr:`__code__`
attribute. See also the :mod:`code` module.

Accessing ``__code__`` raises an :ref:`auditing event <auditing>`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should be able to add a regular ..audit_event item (or whatever the name is) and override the text by putting an indented paragraph below it. That will make sure the entry gets into the auto-generated table. Otherwise, it all looks good

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried that, and IMO it makes the audit event table look worse due to the way it only references these docs via unlabeled footnotes in the object.__getattr__ section, and it scrambles the order of the footnotes so the first object.__getattr__ footnote no longer points at object, it points at one of the specific attributes.

My longer reasoning is here: https://bugs.python.org/msg384749

I think a better answer would be for someone who understands the sphinx table generator to add a directive that inserts labeled cross-references for each attribute below the object.__getattr__ row.

Right now when you add cross-references for all of the attributes the table row looks something like this:

object.__getattr__ | description | [1] [2] [3] [4] [5]

But would be actually useful with a structure like this that actually labels the references:

event description references
object.__getattr__ description [1]
attr traceback.tb_frame description [1]
attr code.f_code description [1]

``object.__getattr__`` with arguments ``obj`` and ``"__code__"``.

.. index::
builtin: exec
builtin: eval
Expand Down
6 changes: 6 additions & 0 deletions Doc/reference/datamodel.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1005,6 +1005,9 @@ Internal types
:attr:`f_lasti` gives the precise instruction (this is an index into the
bytecode string of the code object).

Accessing ``f_code`` raises an :ref:`auditing event <auditing>`
``object.__getattr__`` with arguments ``obj`` and ``"f_code"``.

.. index::
single: f_trace (frame attribute)
single: f_trace_lines (frame attribute)
Expand Down Expand Up @@ -1089,6 +1092,9 @@ Internal types
:keyword:`try` statement with no matching except clause or with a
finally clause.

Accessing ``tb_frame`` raises an :ref:`auditing event <auditing>`
``object.__getattr__`` with arguments ``obj`` and ``"tb_frame"``.

.. index::
single: tb_next (traceback attribute)

Expand Down
1 change: 1 addition & 0 deletions Include/structmember.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ typedef struct PyMemberDef {
#define PY_WRITE_RESTRICTED 4
#define RESTRICTED (READ_RESTRICTED | PY_WRITE_RESTRICTED)

#define AUDIT_READ READ_RESTRICTED

/* Current API, use this */
PyAPI_FUNC(PyObject *) PyMember_GetOne(const char *, struct PyMemberDef *);
Expand Down
1 change: 1 addition & 0 deletions Misc/ACKS
Original file line number Diff line number Diff line change
Expand Up @@ -718,6 +718,7 @@ Kevan Heydon
Wouter van Heyst
Kelsey Hightower
Jason Hildebrand
Ryan Hileman
Aaron Hill
Joel Hillacre
Richie Hindle
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Audit hooks are now fired for frame.f_code, traceback.tb_frame, and generator code/frame attribute access.
2 changes: 1 addition & 1 deletion Objects/descrobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ member_get(PyMemberDescrObject *descr, PyObject *obj, PyObject *type)
if (descr_check((PyDescrObject *)descr, obj, &res))
return res;

if (descr->d_member->flags & READ_RESTRICTED) {
if (descr->d_member->flags & AUDIT_READ) {
if (PySys_Audit("object.__getattr__", "Os",
obj ? obj : Py_None, descr->d_member->name) < 0) {
return NULL;
Expand Down
2 changes: 1 addition & 1 deletion Objects/frameobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

static PyMemberDef frame_memberlist[] = {
{"f_back", T_OBJECT, OFF(f_back), READONLY},
{"f_code", T_OBJECT, OFF(f_code), READONLY},
{"f_code", T_OBJECT, OFF(f_code), READONLY|AUDIT_READ},
{"f_builtins", T_OBJECT, OFF(f_builtins), READONLY},
{"f_globals", T_OBJECT, OFF(f_globals), READONLY},
{"f_lasti", T_INT, OFF(f_lasti), READONLY},
Expand Down
12 changes: 6 additions & 6 deletions Objects/genobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -756,8 +756,8 @@ static PyGetSetDef gen_getsetlist[] = {
};

static PyMemberDef gen_memberlist[] = {
{"gi_frame", T_OBJECT, offsetof(PyGenObject, gi_frame), READONLY},
{"gi_code", T_OBJECT, offsetof(PyGenObject, gi_code), READONLY},
{"gi_frame", T_OBJECT, offsetof(PyGenObject, gi_frame), READONLY|AUDIT_READ},
{"gi_code", T_OBJECT, offsetof(PyGenObject, gi_code), READONLY|AUDIT_READ},
{NULL} /* Sentinel */
};

Expand Down Expand Up @@ -993,8 +993,8 @@ static PyGetSetDef coro_getsetlist[] = {
};

static PyMemberDef coro_memberlist[] = {
{"cr_frame", T_OBJECT, offsetof(PyCoroObject, cr_frame), READONLY},
{"cr_code", T_OBJECT, offsetof(PyCoroObject, cr_code), READONLY},
{"cr_frame", T_OBJECT, offsetof(PyCoroObject, cr_frame), READONLY|AUDIT_READ},
{"cr_code", T_OBJECT, offsetof(PyCoroObject, cr_code), READONLY|AUDIT_READ},
{"cr_origin", T_OBJECT, offsetof(PyCoroObject, cr_origin), READONLY},
{NULL} /* Sentinel */
};
Expand Down Expand Up @@ -1375,10 +1375,10 @@ static PyGetSetDef async_gen_getsetlist[] = {
};

static PyMemberDef async_gen_memberlist[] = {
{"ag_frame", T_OBJECT, offsetof(PyAsyncGenObject, ag_frame), READONLY},
{"ag_frame", T_OBJECT, offsetof(PyAsyncGenObject, ag_frame), READONLY|AUDIT_READ},
{"ag_running", T_BOOL, offsetof(PyAsyncGenObject, ag_running_async),
READONLY},
{"ag_code", T_OBJECT, offsetof(PyAsyncGenObject, ag_code), READONLY},
{"ag_code", T_OBJECT, offsetof(PyAsyncGenObject, ag_code), READONLY|AUDIT_READ},
{NULL} /* Sentinel */
};

Expand Down
2 changes: 1 addition & 1 deletion Python/traceback.c
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ static PyMethodDef tb_methods[] = {
};

static PyMemberDef tb_memberlist[] = {
{"tb_frame", T_OBJECT, OFF(tb_frame), READONLY},
{"tb_frame", T_OBJECT, OFF(tb_frame), READONLY|AUDIT_READ},
{"tb_lasti", T_INT, OFF(tb_lasti), READONLY},
{"tb_lineno", T_INT, OFF(tb_lineno), READONLY},
{NULL} /* Sentinel */
Expand Down