]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.15] gh-154431: Fix data race in `sys.audithook` (GH-154462) (#154477)
authorMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>
Wed, 22 Jul 2026 12:41:16 +0000 (14:41 +0200)
committerGitHub <noreply@github.com>
Wed, 22 Jul 2026 12:41:16 +0000 (12:41 +0000)
gh-154431: Fix data race in `sys.audithook` (GH-154462)
(cherry picked from commit 596cd5c5d7b6ada3e50c6499447eb90a065769ad)

Co-authored-by: sobolevn <mail@sobolevn.me>
Include/internal/pycore_interp_structs.h
Lib/test/test_free_threading/test_sys.py
Misc/NEWS.d/next/Library/2026-07-22-12-42-53.gh-issue-154431.U2kXXZ.rst [new file with mode: 0644]
Python/pystate.c
Python/sysmodule.c

index d8e83cf2ff5c9a911ddd53cc70cb6f16b9d461de..c1bf1770266681bdceeefba31ad5e10e81dcd0cc 100644 (file)
@@ -988,6 +988,7 @@ struct _is {
     struct _obmalloc_state *obmalloc;
 
     PyObject *audit_hooks;
+    PyMutex audit_hooks_mutex;
     PyType_WatchCallback type_watchers[TYPE_MAX_WATCHERS];
     PyCode_WatchCallback code_watchers[CODE_MAX_WATCHERS];
     PyContext_WatchCallback context_watchers[CONTEXT_MAX_WATCHERS];
index 271fdd13c62b668eed7cacff356e366ff64b9bef..b8ba933cb01adc0313ec53337ed5a3289582e1dd 100644 (file)
@@ -44,6 +44,20 @@ class SysModuleTest(unittest.TestCase):
         workers = [lambda: worker(i) for i in range(5)]
         threading_helper.run_concurrently(workers)
 
+    def test_sys_audit_hooks(self):
+        def _hook(*args):
+            return None
+
+        def adder():
+            for _ in range(100):
+                sys.addaudithook(_hook)
+
+        def auditor():
+            for _ in range(2000):
+                sys.audit("fusil.tsan.test")
+
+        threading_helper.run_concurrently([adder, auditor])
+
 
 if __name__ == "__main__":
     unittest.main()
diff --git a/Misc/NEWS.d/next/Library/2026-07-22-12-42-53.gh-issue-154431.U2kXXZ.rst b/Misc/NEWS.d/next/Library/2026-07-22-12-42-53.gh-issue-154431.U2kXXZ.rst
new file mode 100644 (file)
index 0000000..a56c3b3
--- /dev/null
@@ -0,0 +1 @@
+Fixes a data race in free-threading build in :func:`sys.addaudithook`.
index 8349df1b573952d77d7234fae986c35cecac25cf..185957f6343ba6a9458fc6e247079a22b41bf67c 100644 (file)
@@ -580,6 +580,7 @@ init_interpreter(PyInterpreterState *interp,
     llist_init(&interp->mem_free_queue.head);
     llist_init(&interp->asyncio_tasks_head);
     interp->asyncio_tasks_lock = (PyMutex){0};
+    interp->audit_hooks_mutex = (PyMutex){0};
     for (int i = 0; i < _PY_MONITORING_UNGROUPED_EVENTS; i++) {
         interp->monitors.tools[i] = 0;
     }
index aa9ff9e9a455de7fbaf5677c924e5e601e20d861..5140ad86d499236b99ebe2e79593694cb60f437d 100644 (file)
@@ -236,7 +236,7 @@ should_audit(PyInterpreterState *interp)
         return 0;
     }
     return (interp->runtime->audit_hooks.head
-            || interp->audit_hooks
+            || FT_ATOMIC_LOAD_PTR_ACQUIRE(interp->audit_hooks)
             || PyDTrace_AUDIT_ENABLED());
 }
 
@@ -306,13 +306,14 @@ sys_audit_tstate(PyThreadState *ts, const char *event,
     }
 
     /* Call interpreter hooks */
-    if (is->audit_hooks) {
+    PyObject *audit_hooks = FT_ATOMIC_LOAD_PTR_ACQUIRE(is->audit_hooks);
+    if (audit_hooks) {
         eventName = PyUnicode_FromString(event);
         if (!eventName) {
             goto exit;
         }
 
-        hooks = PyObject_GetIter(is->audit_hooks);
+        hooks = PyObject_GetIter(audit_hooks);
         if (!hooks) {
             goto exit;
         }
@@ -536,20 +537,29 @@ sys_addaudithook_impl(PyObject *module, PyObject *hook)
     }
 
     PyInterpreterState *interp = tstate->interp;
+    PyMutex mutex = interp->audit_hooks_mutex;
+    PyMutex_Lock(&mutex);
+
     if (interp->audit_hooks == NULL) {
-        interp->audit_hooks = PyList_New(0);
-        if (interp->audit_hooks == NULL) {
-            return NULL;
+        PyObject *new_list = PyList_New(0);
+        if (new_list == NULL) {
+            goto error;
         }
         /* Avoid having our list of hooks show up in the GC module */
-        PyObject_GC_UnTrack(interp->audit_hooks);
+        PyObject_GC_UnTrack(new_list);
+        FT_ATOMIC_STORE_PTR_RELEASE(interp->audit_hooks, new_list);
     }
 
     if (PyList_Append(interp->audit_hooks, hook) < 0) {
-        return NULL;
+        goto error;
     }
 
+    PyMutex_Unlock(&mutex);
     Py_RETURN_NONE;
+
+error:
+    PyMutex_Unlock(&mutex);
+    return NULL;
 }
 
 /*[clinic input]