gh-99377: Add audit events for thread creation and clear (GH-99378)

This commit is contained in:
Steve Dower 2022-11-16 17:15:52 +00:00 committed by GitHub
parent 01fa907aa8
commit 19c1462e8d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 117 additions and 7 deletions

View File

@ -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`.

View File

@ -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.

View File

@ -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

View File

@ -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")

View File

@ -0,0 +1 @@
Add audit events for thread creation and clear operations.

View File

@ -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);

View File

@ -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;