summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSteve Dower <steve.dower@python.org>2022-11-16 17:15:52 (GMT)
committerGitHub <noreply@github.com>2022-11-16 17:15:52 (GMT)
commit19c1462e8dca3319c8290e2edcce482bd18cb018 (patch)
tree01d4d3189f912b0444faf55a44e34057e8dc5afb
parent01fa907aa8e7c475a76b407f35c635b26c9f47f8 (diff)
downloadcpython-19c1462e8dca3319c8290e2edcce482bd18cb018.zip
cpython-19c1462e8dca3319c8290e2edcce482bd18cb018.tar.gz
cpython-19c1462e8dca3319c8290e2edcce482bd18cb018.tar.bz2
gh-99377: Add audit events for thread creation and clear (GH-99378)
-rw-r--r--Doc/c-api/init.rst13
-rw-r--r--Doc/library/_thread.rst2
-rw-r--r--Lib/test/audit-tests.py42
-rw-r--r--Lib/test/test_audit.py25
-rw-r--r--Misc/NEWS.d/next/Core and Builtins/2022-11-11-14-04-01.gh-issue-99377.-CJvWn.rst1
-rw-r--r--Modules/_threadmodule.c10
-rw-r--r--Python/pystate.c31
7 files changed, 117 insertions, 7 deletions
diff --git a/Doc/c-api/init.rst b/Doc/c-api/init.rst
index afb1771..273838c 100644
--- a/Doc/c-api/init.rst
+++ b/Doc/c-api/init.rst
@@ -1239,12 +1239,25 @@ All of the following functions must be called after :c:func:`Py_Initialize`.
The global interpreter lock need not be held, but may be held if it is
necessary to serialize calls to this function.
+ .. audit-event:: cpython.PyThreadState_New id c.PyThreadState_New
+
+ Raise an auditing event ``cpython.PyThreadState_New`` with Python's thread
+ id as the argument. The event will be raised from the thread creating the new
+ ``PyThreadState``, which may not be the new thread.
+
.. c:function:: void PyThreadState_Clear(PyThreadState *tstate)
Reset all information in a thread state object. The global interpreter lock
must be held.
+ .. audit-event:: cpython.PyThreadState_Clear id c.PyThreadState_Clear
+
+ Raise an auditing event ``cpython.PyThreadState_Clear`` with Python's
+ thread id as the argument. The event may be raised from a different thread
+ than the one being cleared. Exceptions raised from a hook will be treated
+ as unraisable and will not abort the operation.
+
.. versionchanged:: 3.9
This function now calls the :c:member:`PyThreadState.on_delete` callback.
Previously, that happened in :c:func:`PyThreadState_Delete`.
diff --git a/Doc/library/_thread.rst b/Doc/library/_thread.rst
index 9df9e79..122692a 100644
--- a/Doc/library/_thread.rst
+++ b/Doc/library/_thread.rst
@@ -57,6 +57,8 @@ This module defines the following constants and functions:
When the function raises a :exc:`SystemExit` exception, it is silently
ignored.
+ .. audit-event:: _thread.start_new_thread function,args,kwargs start_new_thread
+
.. versionchanged:: 3.8
:func:`sys.unraisablehook` is now used to handle unhandled exceptions.
diff --git a/Lib/test/audit-tests.py b/Lib/test/audit-tests.py
index a411072..bf56cea 100644
--- a/Lib/test/audit-tests.py
+++ b/Lib/test/audit-tests.py
@@ -419,6 +419,48 @@ def test_sys_getframe():
sys._getframe()
+def test_threading():
+ import _thread
+
+ def hook(event, args):
+ if event.startswith(("_thread.", "cpython.PyThreadState", "test.")):
+ print(event, args)
+
+ sys.addaudithook(hook)
+
+ lock = _thread.allocate_lock()
+ lock.acquire()
+
+ class test_func:
+ def __repr__(self): return "<test_func>"
+ def __call__(self):
+ sys.audit("test.test_func")
+ lock.release()
+
+ i = _thread.start_new_thread(test_func(), ())
+ lock.acquire()
+
+
+def test_threading_abort():
+ # Ensures that aborting PyThreadState_New raises the correct exception
+ import _thread
+
+ class ThreadNewAbortError(Exception):
+ pass
+
+ def hook(event, args):
+ if event == "cpython.PyThreadState_New":
+ raise ThreadNewAbortError()
+
+ sys.addaudithook(hook)
+
+ try:
+ _thread.start_new_thread(lambda: None, ())
+ except ThreadNewAbortError:
+ # Other exceptions are raised and the test will fail
+ pass
+
+
def test_wmi_exec_query():
import _wmi
diff --git a/Lib/test/test_audit.py b/Lib/test/test_audit.py
index e8d560a..5a2997a 100644
--- a/Lib/test/test_audit.py
+++ b/Lib/test/test_audit.py
@@ -186,6 +186,31 @@ class AuditTest(unittest.TestCase):
self.assertEqual(actual, expected)
+
+ def test_threading(self):
+ returncode, events, stderr = self.run_python("test_threading")
+ if returncode:
+ self.fail(stderr)
+
+ if support.verbose:
+ print(*events, sep='\n')
+ actual = [(ev[0], ev[2]) for ev in events]
+ expected = [
+ ("_thread.start_new_thread", "(<test_func>, (), None)"),
+ ("cpython.PyThreadState_New", "(2,)"),
+ ("test.test_func", "()"),
+ ("cpython.PyThreadState_Clear", "(2,)"),
+ ]
+
+ self.assertEqual(actual, expected)
+
+ def test_threading_abort(self):
+ # Ensures that aborting PyThreadState_New raises the correct exception
+ returncode, events, stderr = self.run_python("test_threading_abort")
+ if returncode:
+ self.fail(stderr)
+
+
def test_wmi_exec_query(self):
import_helper.import_module("_wmi")
returncode, events, stderr = self.run_python("test_wmi_exec_query")
diff --git a/Misc/NEWS.d/next/Core and Builtins/2022-11-11-14-04-01.gh-issue-99377.-CJvWn.rst b/Misc/NEWS.d/next/Core and Builtins/2022-11-11-14-04-01.gh-issue-99377.-CJvWn.rst
new file mode 100644
index 0000000..631b9ca
--- /dev/null
+++ b/Misc/NEWS.d/next/Core and Builtins/2022-11-11-14-04-01.gh-issue-99377.-CJvWn.rst
@@ -0,0 +1 @@
+Add audit events for thread creation and clear operations.
diff --git a/Modules/_threadmodule.c b/Modules/_threadmodule.c
index 5968d4e..ec8b6d8 100644
--- a/Modules/_threadmodule.c
+++ b/Modules/_threadmodule.c
@@ -1145,6 +1145,11 @@ thread_PyThread_start_new_thread(PyObject *self, PyObject *fargs)
return NULL;
}
+ if (PySys_Audit("_thread.start_new_thread", "OOO",
+ func, args, kwargs ? kwargs : Py_None) < 0) {
+ return NULL;
+ }
+
PyInterpreterState *interp = _PyInterpreterState_GET();
if (!_PyInterpreterState_HasFeature(interp, Py_RTFLAGS_THREADS)) {
PyErr_SetString(PyExc_RuntimeError,
@@ -1160,7 +1165,10 @@ thread_PyThread_start_new_thread(PyObject *self, PyObject *fargs)
boot->tstate = _PyThreadState_Prealloc(boot->interp);
if (boot->tstate == NULL) {
PyMem_Free(boot);
- return PyErr_NoMemory();
+ if (!PyErr_Occurred()) {
+ return PyErr_NoMemory();
+ }
+ return NULL;
}
boot->runtime = runtime;
boot->func = Py_NewRef(func);
diff --git a/Python/pystate.c b/Python/pystate.c
index b94fbf6..d6f2645 100644
--- a/Python/pystate.c
+++ b/Python/pystate.c
@@ -873,14 +873,29 @@ PyThreadState *
PyThreadState_New(PyInterpreterState *interp)
{
PyThreadState *tstate = new_threadstate(interp);
- _PyThreadState_SetCurrent(tstate);
+ if (tstate) {
+ _PyThreadState_SetCurrent(tstate);
+ if (PySys_Audit("cpython.PyThreadState_New", "K", tstate->id) < 0) {
+ PyThreadState_Clear(tstate);
+ _PyThreadState_DeleteCurrent(tstate);
+ return NULL;
+ }
+ }
return tstate;
}
PyThreadState *
_PyThreadState_Prealloc(PyInterpreterState *interp)
{
- return new_threadstate(interp);
+ PyThreadState *tstate = new_threadstate(interp);
+ if (tstate) {
+ if (PySys_Audit("cpython.PyThreadState_New", "K", tstate->id) < 0) {
+ PyThreadState_Clear(tstate);
+ _PyThreadState_Delete(tstate, 0);
+ return NULL;
+ }
+ }
+ return tstate;
}
// We keep this around for (accidental) stable ABI compatibility.
@@ -1028,6 +1043,10 @@ _PyInterpreterState_ClearModules(PyInterpreterState *interp)
void
PyThreadState_Clear(PyThreadState *tstate)
{
+ if (PySys_Audit("cpython.PyThreadState_Clear", "K", tstate->id) < 0) {
+ PyErr_WriteUnraisable(NULL);
+ }
+
int verbose = _PyInterpreterState_GetConfig(tstate->interp)->verbose;
if (verbose && tstate->cframe->current_frame != NULL) {
@@ -1545,16 +1564,16 @@ _PyGILState_Init(_PyRuntimeState *runtime)
PyStatus
_PyGILState_SetTstate(PyThreadState *tstate)
{
+ /* must init with valid states */
+ assert(tstate != NULL);
+ assert(tstate->interp != NULL);
+
if (!_Py_IsMainInterpreter(tstate->interp)) {
/* Currently, PyGILState is shared by all interpreters. The main
* interpreter is responsible to initialize it. */
return _PyStatus_OK();
}
- /* must init with valid states */
- assert(tstate != NULL);
- assert(tstate->interp != NULL);
-
struct _gilstate_runtime_state *gilstate = &tstate->interp->runtime->gilstate;
gilstate->autoInterpreterState = tstate->interp;