From d3e998b0d84cab7b3ebb178ff8a6e85b1c91c3b8 Mon Sep 17 00:00:00 2001 From: Ryan Hileman Date: Fri, 8 Jan 2021 19:19:17 -0800 Subject: [PATCH 1/4] bpo-42800: audit hooks for frame and code attrs * Accessing the following attributes will now fire PEP 578 style audit hooks as ("object.__getattr__", obj, name): * PyTracebackObject: tb_frame * PyFrameObject: f_code * PyGenObject: gi_code, gi_frame * PyCoroObject: cr_code, cr_frame * PyAsyncGenObject: ag_code, ag_frame * Document audit hooks for tb_frame, f_code, and __code__ * Add an AUDIT_READ attribute flag aliased to READ_RESTRICTED. * Update obsolete RESTRICTED flag documentation. --- Doc/extending/newtypes.rst | 7 ++----- Doc/library/audit_events.rst | 2 +- Doc/library/stdtypes.rst | 3 +++ Doc/reference/datamodel.rst | 6 ++++++ Include/structmember.h | 1 + Misc/ACKS | 1 + .../2021-01-09-17-07-36.bpo-42800._dtZvW.rst | 1 + Objects/descrobject.c | 2 +- Objects/frameobject.c | 2 +- Objects/genobject.c | 12 ++++++------ Python/traceback.c | 2 +- 11 files changed, 24 insertions(+), 15 deletions(-) create mode 100644 Misc/NEWS.d/next/Security/2021-01-09-17-07-36.bpo-42800._dtZvW.rst diff --git a/Doc/extending/newtypes.rst b/Doc/extending/newtypes.rst index d9023709fddc86..7748672c9cc773 100644 --- a/Doc/extending/newtypes.rst +++ b/Doc/extending/newtypes.rst @@ -287,11 +287,8 @@ 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 a :pep:`578` ``object.__getattr__`` | +| | audit event before read. | +---------------------------+----------------------------------------------+ .. index:: diff --git a/Doc/library/audit_events.rst b/Doc/library/audit_events.rst index 367d56e4e37359..8227a7955bef81 100644 --- a/Doc/library/audit_events.rst +++ b/Doc/library/audit_events.rst @@ -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. diff --git a/Doc/library/stdtypes.rst b/Doc/library/stdtypes.rst index 2331849c02e982..b8eb3c227be0d7 100644 --- a/Doc/library/stdtypes.rst +++ b/Doc/library/stdtypes.rst @@ -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 ` +``object.__getattr__`` with arguments ``obj`` and ``"__code__"``. + .. index:: builtin: exec builtin: eval diff --git a/Doc/reference/datamodel.rst b/Doc/reference/datamodel.rst index 62f852953623c1..8305eaeb67f005 100644 --- a/Doc/reference/datamodel.rst +++ b/Doc/reference/datamodel.rst @@ -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 ` + ``object.__getattr__`` with arguments ``obj`` and ``"f_code"``. + .. index:: single: f_trace (frame attribute) single: f_trace_lines (frame attribute) @@ -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 ` + ``object.__getattr__`` with arguments ``obj`` and ``"tb_frame"``. + .. index:: single: tb_next (traceback attribute) diff --git a/Include/structmember.h b/Include/structmember.h index b54f7081f458dd..955edd31c4ca54 100644 --- a/Include/structmember.h +++ b/Include/structmember.h @@ -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 *); diff --git a/Misc/ACKS b/Misc/ACKS index 211455b4dfc3c2..07d5486db85579 100644 --- a/Misc/ACKS +++ b/Misc/ACKS @@ -718,6 +718,7 @@ Kevan Heydon Wouter van Heyst Kelsey Hightower Jason Hildebrand +Ryan Hileman Aaron Hill Joel Hillacre Richie Hindle diff --git a/Misc/NEWS.d/next/Security/2021-01-09-17-07-36.bpo-42800._dtZvW.rst b/Misc/NEWS.d/next/Security/2021-01-09-17-07-36.bpo-42800._dtZvW.rst new file mode 100644 index 00000000000000..d01c0c3073a503 --- /dev/null +++ b/Misc/NEWS.d/next/Security/2021-01-09-17-07-36.bpo-42800._dtZvW.rst @@ -0,0 +1 @@ +Audit hooks are now fired for frame.f_code, traceback.tb_frame, and generator code/frame attribute access. diff --git a/Objects/descrobject.c b/Objects/descrobject.c index 16c695a08f47d9..4ccca068055938 100644 --- a/Objects/descrobject.c +++ b/Objects/descrobject.c @@ -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; diff --git a/Objects/frameobject.c b/Objects/frameobject.c index 4c5eaa23d345be..146ee22ec34b59 100644 --- a/Objects/frameobject.c +++ b/Objects/frameobject.c @@ -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}, diff --git a/Objects/genobject.c b/Objects/genobject.c index bde92b462da199..d97c4b205a6631 100644 --- a/Objects/genobject.c +++ b/Objects/genobject.c @@ -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 */ }; @@ -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 */ }; @@ -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 */ }; diff --git a/Python/traceback.c b/Python/traceback.c index b82cfd3665ce1d..3ed6cdee7e3b43 100644 --- a/Python/traceback.c +++ b/Python/traceback.c @@ -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 */ From 33d79f8bafe884db29fc83840341c361f502afd9 Mon Sep 17 00:00:00 2001 From: Steve Dower Date: Wed, 28 Apr 2021 21:06:49 +0100 Subject: [PATCH 2/4] Update newtypes.rst --- Doc/extending/newtypes.rst | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/Doc/extending/newtypes.rst b/Doc/extending/newtypes.rst index 7748672c9cc773..11fcaf0f67657c 100644 --- a/Doc/extending/newtypes.rst +++ b/Doc/extending/newtypes.rst @@ -287,15 +287,23 @@ combined using bitwise-OR. +===========================+==============================================+ | :const:`READONLY` | Never writable. | +---------------------------+----------------------------------------------+ -| :const:`AUDIT_READ` | Emit a :pep:`578` ``object.__getattr__`` | -| | audit event before read. | +| :const:`AUDIT_READ` | Emit an ``object.__getattr__`` | +| | :ref:`audit events ` before | +| | reading. | +---------------------------+----------------------------------------------+ +.. version-changed:: 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 @@ -306,6 +314,8 @@ class object, and get the doc string using its :attr:`__doc__` attribute. As with the :c:member:`~PyTypeObject.tp_methods` table, a sentinel entry with a :attr:`name` value of ``NULL`` is required. +.. version + .. XXX Descriptors need to be explained in more detail somewhere, but not here. Descriptor objects have two handler functions which correspond to the From 492af7375acfa631c56dc62508eb3c48b145ae6c Mon Sep 17 00:00:00 2001 From: Steve Dower Date: Wed, 28 Apr 2021 21:07:49 +0100 Subject: [PATCH 3/4] Update newtypes.rst --- Doc/extending/newtypes.rst | 2 -- 1 file changed, 2 deletions(-) diff --git a/Doc/extending/newtypes.rst b/Doc/extending/newtypes.rst index 11fcaf0f67657c..5ee57d17ea4479 100644 --- a/Doc/extending/newtypes.rst +++ b/Doc/extending/newtypes.rst @@ -314,8 +314,6 @@ class object, and get the doc string using its :attr:`__doc__` attribute. As with the :c:member:`~PyTypeObject.tp_methods` table, a sentinel entry with a :attr:`name` value of ``NULL`` is required. -.. version - .. XXX Descriptors need to be explained in more detail somewhere, but not here. Descriptor objects have two handler functions which correspond to the From 204f051dd888c54c6be8b79bb1193534f51bb908 Mon Sep 17 00:00:00 2001 From: Steve Dower Date: Thu, 29 Apr 2021 21:29:44 +0100 Subject: [PATCH 4/4] Update newtypes.rst --- Doc/extending/newtypes.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/extending/newtypes.rst b/Doc/extending/newtypes.rst index 5ee57d17ea4479..8f7fc960770d4e 100644 --- a/Doc/extending/newtypes.rst +++ b/Doc/extending/newtypes.rst @@ -292,7 +292,7 @@ combined using bitwise-OR. | | reading. | +---------------------------+----------------------------------------------+ -.. version-changed:: 3.10 +.. 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