Skip to content

Commit 19c1462

Browse files
authored
gh-99377: Add audit events for thread creation and clear (GH-99378)
1 parent 01fa907 commit 19c1462

File tree

7 files changed

+117
-7
lines changed

7 files changed

+117
-7
lines changed

Doc/c-api/init.rst

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1239,12 +1239,25 @@ All of the following functions must be called after :c:func:`Py_Initialize`.
12391239
The global interpreter lock need not be held, but may be held if it is
12401240
necessary to serialize calls to this function.
12411241
1242+
.. audit-event:: cpython.PyThreadState_New id c.PyThreadState_New
1243+
1244+
Raise an auditing event ``cpython.PyThreadState_New`` with Python's thread
1245+
id as the argument. The event will be raised from the thread creating the new
1246+
``PyThreadState``, which may not be the new thread.
1247+
12421248
12431249
.. c:function:: void PyThreadState_Clear(PyThreadState *tstate)
12441250
12451251
Reset all information in a thread state object. The global interpreter lock
12461252
must be held.
12471253
1254+
.. audit-event:: cpython.PyThreadState_Clear id c.PyThreadState_Clear
1255+
1256+
Raise an auditing event ``cpython.PyThreadState_Clear`` with Python's
1257+
thread id as the argument. The event may be raised from a different thread
1258+
than the one being cleared. Exceptions raised from a hook will be treated
1259+
as unraisable and will not abort the operation.
1260+
12481261
.. versionchanged:: 3.9
12491262
This function now calls the :c:member:`PyThreadState.on_delete` callback.
12501263
Previously, that happened in :c:func:`PyThreadState_Delete`.

Doc/library/_thread.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ This module defines the following constants and functions:
5757
When the function raises a :exc:`SystemExit` exception, it is silently
5858
ignored.
5959

60+
.. audit-event:: _thread.start_new_thread function,args,kwargs start_new_thread
61+
6062
.. versionchanged:: 3.8
6163
:func:`sys.unraisablehook` is now used to handle unhandled exceptions.
6264

Lib/test/audit-tests.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,48 @@ def hook(event, args):
419419
sys._getframe()
420420

421421

422+
def test_threading():
423+
import _thread
424+
425+
def hook(event, args):
426+
if event.startswith(("_thread.", "cpython.PyThreadState", "test.")):
427+
print(event, args)
428+
429+
sys.addaudithook(hook)
430+
431+
lock = _thread.allocate_lock()
432+
lock.acquire()
433+
434+
class test_func:
435+
def __repr__(self): return "<test_func>"
436+
def __call__(self):
437+
sys.audit("test.test_func")
438+
lock.release()
439+
440+
i = _thread.start_new_thread(test_func(), ())
441+
lock.acquire()
442+
443+
444+
def test_threading_abort():
445+
# Ensures that aborting PyThreadState_New raises the correct exception
446+
import _thread
447+
448+
class ThreadNewAbortError(Exception):
449+
pass
450+
451+
def hook(event, args):
452+
if event == "cpython.PyThreadState_New":
453+
raise ThreadNewAbortError()
454+
455+
sys.addaudithook(hook)
456+
457+
try:
458+
_thread.start_new_thread(lambda: None, ())
459+
except ThreadNewAbortError:
460+
# Other exceptions are raised and the test will fail
461+
pass
462+
463+
422464
def test_wmi_exec_query():
423465
import _wmi
424466

Lib/test/test_audit.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,31 @@ def test_sys_getframe(self):
186186

187187
self.assertEqual(actual, expected)
188188

189+
190+
def test_threading(self):
191+
returncode, events, stderr = self.run_python("test_threading")
192+
if returncode:
193+
self.fail(stderr)
194+
195+
if support.verbose:
196+
print(*events, sep='\n')
197+
actual = [(ev[0], ev[2]) for ev in events]
198+
expected = [
199+
("_thread.start_new_thread", "(<test_func>, (), None)"),
200+
("cpython.PyThreadState_New", "(2,)"),
201+
("test.test_func", "()"),
202+
("cpython.PyThreadState_Clear", "(2,)"),
203+
]
204+
205+
self.assertEqual(actual, expected)
206+
207+
def test_threading_abort(self):
208+
# Ensures that aborting PyThreadState_New raises the correct exception
209+
returncode, events, stderr = self.run_python("test_threading_abort")
210+
if returncode:
211+
self.fail(stderr)
212+
213+
189214
def test_wmi_exec_query(self):
190215
import_helper.import_module("_wmi")
191216
returncode, events, stderr = self.run_python("test_wmi_exec_query")
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add audit events for thread creation and clear operations.

Modules/_threadmodule.c

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1145,6 +1145,11 @@ thread_PyThread_start_new_thread(PyObject *self, PyObject *fargs)
11451145
return NULL;
11461146
}
11471147

1148+
if (PySys_Audit("_thread.start_new_thread", "OOO",
1149+
func, args, kwargs ? kwargs : Py_None) < 0) {
1150+
return NULL;
1151+
}
1152+
11481153
PyInterpreterState *interp = _PyInterpreterState_GET();
11491154
if (!_PyInterpreterState_HasFeature(interp, Py_RTFLAGS_THREADS)) {
11501155
PyErr_SetString(PyExc_RuntimeError,
@@ -1160,7 +1165,10 @@ thread_PyThread_start_new_thread(PyObject *self, PyObject *fargs)
11601165
boot->tstate = _PyThreadState_Prealloc(boot->interp);
11611166
if (boot->tstate == NULL) {
11621167
PyMem_Free(boot);
1163-
return PyErr_NoMemory();
1168+
if (!PyErr_Occurred()) {
1169+
return PyErr_NoMemory();
1170+
}
1171+
return NULL;
11641172
}
11651173
boot->runtime = runtime;
11661174
boot->func = Py_NewRef(func);

Python/pystate.c

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -873,14 +873,29 @@ PyThreadState *
873873
PyThreadState_New(PyInterpreterState *interp)
874874
{
875875
PyThreadState *tstate = new_threadstate(interp);
876-
_PyThreadState_SetCurrent(tstate);
876+
if (tstate) {
877+
_PyThreadState_SetCurrent(tstate);
878+
if (PySys_Audit("cpython.PyThreadState_New", "K", tstate->id) < 0) {
879+
PyThreadState_Clear(tstate);
880+
_PyThreadState_DeleteCurrent(tstate);
881+
return NULL;
882+
}
883+
}
877884
return tstate;
878885
}
879886

880887
PyThreadState *
881888
_PyThreadState_Prealloc(PyInterpreterState *interp)
882889
{
883-
return new_threadstate(interp);
890+
PyThreadState *tstate = new_threadstate(interp);
891+
if (tstate) {
892+
if (PySys_Audit("cpython.PyThreadState_New", "K", tstate->id) < 0) {
893+
PyThreadState_Clear(tstate);
894+
_PyThreadState_Delete(tstate, 0);
895+
return NULL;
896+
}
897+
}
898+
return tstate;
884899
}
885900

886901
// We keep this around for (accidental) stable ABI compatibility.
@@ -1028,6 +1043,10 @@ _PyInterpreterState_ClearModules(PyInterpreterState *interp)
10281043
void
10291044
PyThreadState_Clear(PyThreadState *tstate)
10301045
{
1046+
if (PySys_Audit("cpython.PyThreadState_Clear", "K", tstate->id) < 0) {
1047+
PyErr_WriteUnraisable(NULL);
1048+
}
1049+
10311050
int verbose = _PyInterpreterState_GetConfig(tstate->interp)->verbose;
10321051

10331052
if (verbose && tstate->cframe->current_frame != NULL) {
@@ -1545,16 +1564,16 @@ _PyGILState_Init(_PyRuntimeState *runtime)
15451564
PyStatus
15461565
_PyGILState_SetTstate(PyThreadState *tstate)
15471566
{
1567+
/* must init with valid states */
1568+
assert(tstate != NULL);
1569+
assert(tstate->interp != NULL);
1570+
15481571
if (!_Py_IsMainInterpreter(tstate->interp)) {
15491572
/* Currently, PyGILState is shared by all interpreters. The main
15501573
* interpreter is responsible to initialize it. */
15511574
return _PyStatus_OK();
15521575
}
15531576

1554-
/* must init with valid states */
1555-
assert(tstate != NULL);
1556-
assert(tstate->interp != NULL);
1557-
15581577
struct _gilstate_runtime_state *gilstate = &tstate->interp->runtime->gilstate;
15591578

15601579
gilstate->autoInterpreterState = tstate->interp;

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy