[3.12] gh-100227: Lock Around Adding Global Audit Hooks (gh-105515) (gh-105525)

The risk of a race with this state is relatively low, but we play it safe anyway.
(cherry picked from commit e822a676f1)
This commit is contained in:
Eric Snow 2023-06-08 15:05:47 -06:00 committed by GitHub
parent 2ad2bd8b14
commit b08ea9a561
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 1334 additions and 1300 deletions

File diff suppressed because it is too large Load diff

View file

@ -143,7 +143,10 @@ typedef struct pyruntimestate {
// is called multiple times. // is called multiple times.
Py_OpenCodeHookFunction open_code_hook; Py_OpenCodeHookFunction open_code_hook;
void *open_code_userdata; 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_object_runtime_state object_state;
struct _Py_float_runtime_state float_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); static const _PyRuntimeState initial = _PyRuntimeState_INIT(_PyRuntime);
_Py_COMP_DIAG_POP _Py_COMP_DIAG_POP
#define NUMLOCKS 6 #define NUMLOCKS 7
#define LOCKS_INIT(runtime) \ #define LOCKS_INIT(runtime) \
{ \ { \
&(runtime)->interpreters.mutex, \ &(runtime)->interpreters.mutex, \
@ -389,6 +389,7 @@ _Py_COMP_DIAG_POP
&(runtime)->unicode_state.ids.lock, \ &(runtime)->unicode_state.ids.lock, \
&(runtime)->imports.extensions.mutex, \ &(runtime)->imports.extensions.mutex, \
&(runtime)->atexit.mutex, \ &(runtime)->atexit.mutex, \
&(runtime)->audit_hooks.mutex, \
} }
static int static int
@ -432,7 +433,7 @@ init_runtime(_PyRuntimeState *runtime,
runtime->open_code_hook = open_code_hook; runtime->open_code_hook = open_code_hook;
runtime->open_code_userdata = open_code_userdata; 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); PyPreConfig_InitPythonConfig(&runtime->preconfig);
@ -458,7 +459,7 @@ _PyRuntimeState_Init(_PyRuntimeState *runtime)
initialization and interpreter initialization. */ initialization and interpreter initialization. */
void *open_code_hook = runtime->open_code_hook; void *open_code_hook = runtime->open_code_hook;
void *open_code_userdata = runtime->open_code_userdata; 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() // bpo-42882: Preserve next_index value if Py_Initialize()/Py_Finalize()
// is called multiple times. // is called multiple times.
Py_ssize_t unicode_next_index = runtime->unicode_state.ids.next_index; Py_ssize_t unicode_next_index = runtime->unicode_state.ids.next_index;

View file

@ -168,7 +168,7 @@ should_audit(PyInterpreterState *interp)
if (!interp) { if (!interp) {
return 0; return 0;
} }
return (interp->runtime->audit_hook_head return (interp->runtime->audit_hooks.head
|| interp->audit_hooks || interp->audit_hooks
|| PyDTrace_AUDIT_ENABLED()); || PyDTrace_AUDIT_ENABLED());
} }
@ -224,8 +224,11 @@ sys_audit_tstate(PyThreadState *ts, const char *event,
goto exit; goto exit;
} }
/* Call global hooks */ /* Call global hooks
_Py_AuditHookEntry *e = is->runtime->audit_hook_head; *
* 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) { for (; e; e = e->next) {
if (e->hookCFunction(event, eventArgs, e->userData) < 0) { if (e->hookCFunction(event, eventArgs, e->userData) < 0) {
goto exit; goto exit;
@ -353,8 +356,12 @@ _PySys_ClearAuditHooks(PyThreadState *ts)
_PySys_Audit(ts, "cpython._PySys_ClearAuditHooks", NULL); _PySys_Audit(ts, "cpython._PySys_ClearAuditHooks", NULL);
_PyErr_Clear(ts); _PyErr_Clear(ts);
_Py_AuditHookEntry *e = runtime->audit_hook_head, *n; /* We don't worry about the very unlikely race right here,
runtime->audit_hook_head = NULL; * 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) { while (e) {
n = e->next; n = e->next;
PyMem_RawFree(e); 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 int
PySys_AddAuditHook(Py_AuditHookFunction hook, void *userData) 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; _Py_AuditHookEntry *e = (_Py_AuditHookEntry*)PyMem_RawMalloc(
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(
sizeof(_Py_AuditHookEntry)); sizeof(_Py_AuditHookEntry));
}
if (!e) { if (!e) {
if (tstate != NULL) { if (tstate != NULL) {
_PyErr_NoMemory(tstate); _PyErr_NoMemory(tstate);
} }
return -1; return -1;
} }
e->next = NULL; e->next = NULL;
e->hookCFunction = (Py_AuditHookFunction)hook; e->hookCFunction = (Py_AuditHookFunction)hook;
e->userData = userData; 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; return 0;
} }