gh-100227: Lock Around Adding Global Audit Hooks (gh-105515)

The risk of a race with this state is relatively low, but we play it safe anyway.
This commit is contained in:
Eric Snow 2023-06-08 12:38:15 -06:00 committed by GitHub
parent 34c63b86d3
commit e822a676f1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 47 additions and 21 deletions

View file

@ -143,7 +143,10 @@ typedef struct pyruntimestate {
// is called multiple times.
Py_OpenCodeHookFunction open_code_hook;
void *open_code_userdata;
_Py_AuditHookEntry *audit_hook_head;
struct {
PyThread_type_lock mutex;
_Py_AuditHookEntry *head;
} audit_hooks;
struct _py_object_runtime_state object_state;
struct _Py_float_runtime_state float_state;

View file

@ -380,7 +380,7 @@ _Py_COMP_DIAG_IGNORE_DEPR_DECLS
static const _PyRuntimeState initial = _PyRuntimeState_INIT(_PyRuntime);
_Py_COMP_DIAG_POP
#define NUMLOCKS 6
#define NUMLOCKS 7
#define LOCKS_INIT(runtime) \
{ \
&(runtime)->interpreters.mutex, \
@ -389,6 +389,7 @@ _Py_COMP_DIAG_POP
&(runtime)->unicode_state.ids.lock, \
&(runtime)->imports.extensions.mutex, \
&(runtime)->atexit.mutex, \
&(runtime)->audit_hooks.mutex, \
}
static int
@ -432,7 +433,7 @@ init_runtime(_PyRuntimeState *runtime,
runtime->open_code_hook = open_code_hook;
runtime->open_code_userdata = open_code_userdata;
runtime->audit_hook_head = audit_hook_head;
runtime->audit_hooks.head = audit_hook_head;
PyPreConfig_InitPythonConfig(&runtime->preconfig);
@ -458,7 +459,7 @@ _PyRuntimeState_Init(_PyRuntimeState *runtime)
initialization and interpreter initialization. */
void *open_code_hook = runtime->open_code_hook;
void *open_code_userdata = runtime->open_code_userdata;
_Py_AuditHookEntry *audit_hook_head = runtime->audit_hook_head;
_Py_AuditHookEntry *audit_hook_head = runtime->audit_hooks.head;
// bpo-42882: Preserve next_index value if Py_Initialize()/Py_Finalize()
// is called multiple times.
Py_ssize_t unicode_next_index = runtime->unicode_state.ids.next_index;

View file

@ -168,7 +168,7 @@ should_audit(PyInterpreterState *interp)
if (!interp) {
return 0;
}
return (interp->runtime->audit_hook_head
return (interp->runtime->audit_hooks.head
|| interp->audit_hooks
|| PyDTrace_AUDIT_ENABLED());
}
@ -224,8 +224,11 @@ sys_audit_tstate(PyThreadState *ts, const char *event,
goto exit;
}
/* Call global hooks */
_Py_AuditHookEntry *e = is->runtime->audit_hook_head;
/* Call global hooks
*
* We don't worry about any races on hooks getting added,
* since that would not leave is in an inconsistent state. */
_Py_AuditHookEntry *e = is->runtime->audit_hooks.head;
for (; e; e = e->next) {
if (e->hookCFunction(event, eventArgs, e->userData) < 0) {
goto exit;
@ -353,8 +356,12 @@ _PySys_ClearAuditHooks(PyThreadState *ts)
_PySys_Audit(ts, "cpython._PySys_ClearAuditHooks", NULL);
_PyErr_Clear(ts);
_Py_AuditHookEntry *e = runtime->audit_hook_head, *n;
runtime->audit_hook_head = NULL;
/* We don't worry about the very unlikely race right here,
* since it's entirely benign. Nothing else removes entries
* from the list and adding an entry right now would not cause
* any trouble. */
_Py_AuditHookEntry *e = runtime->audit_hooks.head, *n;
runtime->audit_hooks.head = NULL;
while (e) {
n = e->next;
PyMem_RawFree(e);
@ -362,6 +369,22 @@ _PySys_ClearAuditHooks(PyThreadState *ts)
}
}
static void
add_audit_hook_entry_unlocked(_PyRuntimeState *runtime,
_Py_AuditHookEntry *entry)
{
if (runtime->audit_hooks.head == NULL) {
runtime->audit_hooks.head = entry;
}
else {
_Py_AuditHookEntry *last = runtime->audit_hooks.head;
while (last->next) {
last = last->next;
}
last->next = entry;
}
}
int
PySys_AddAuditHook(Py_AuditHookFunction hook, void *userData)
{
@ -389,29 +412,28 @@ PySys_AddAuditHook(Py_AuditHookFunction hook, void *userData)
}
}
_Py_AuditHookEntry *e = runtime->audit_hook_head;
if (!e) {
e = (_Py_AuditHookEntry*)PyMem_RawMalloc(sizeof(_Py_AuditHookEntry));
runtime->audit_hook_head = e;
} else {
while (e->next) {
e = e->next;
}
e = e->next = (_Py_AuditHookEntry*)PyMem_RawMalloc(
_Py_AuditHookEntry *e = (_Py_AuditHookEntry*)PyMem_RawMalloc(
sizeof(_Py_AuditHookEntry));
}
if (!e) {
if (tstate != NULL) {
_PyErr_NoMemory(tstate);
}
return -1;
}
e->next = NULL;
e->hookCFunction = (Py_AuditHookFunction)hook;
e->userData = userData;
if (runtime->audit_hooks.mutex == NULL) {
/* The runtime must not be initailized yet. */
add_audit_hook_entry_unlocked(runtime, e);
}
else {
PyThread_acquire_lock(runtime->audit_hooks.mutex, WAIT_LOCK);
add_audit_hook_entry_unlocked(runtime, e);
PyThread_release_lock(runtime->audit_hooks.mutex);
}
return 0;
}