Skip to content

Commit 748d933

Browse files
committed
Fix use-after-free in tl_pending_args across subinterpreters
Clear tl_pending_args to NULL whenever tl_pending_callback is set to false. Previously, the thread-local pointer was left dangling after callback completion. When a dirty scheduler thread later handled a different subinterpreter's code, Py_XDECREF on the stale pointer would attempt to free memory from the wrong allocator.
1 parent 47c4a5c commit 748d933

File tree

3 files changed

+21
-3
lines changed

3 files changed

+21
-3
lines changed

c_src/py_callback.c

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -563,6 +563,7 @@ static PyObject *build_pending_callback_exc_args(void) {
563563
PyObject *exc_args = PyTuple_New(3);
564564
if (exc_args == NULL) {
565565
tl_pending_callback = false;
566+
Py_CLEAR(tl_pending_args);
566567
return NULL;
567568
}
568569

@@ -575,6 +576,7 @@ static PyObject *build_pending_callback_exc_args(void) {
575576
Py_XDECREF(func_name_obj);
576577
Py_DECREF(exc_args);
577578
tl_pending_callback = false;
579+
Py_CLEAR(tl_pending_args);
578580
return NULL;
579581
}
580582

@@ -610,6 +612,7 @@ static ERL_NIF_TERM build_suspended_result(ErlNifEnv *env, suspended_state_t *su
610612
ERL_NIF_TERM args_term = py_to_term(env, tl_pending_args);
611613

612614
tl_pending_callback = false;
615+
Py_CLEAR(tl_pending_args);
613616

614617
return enif_make_tuple4(env,
615618
ATOM_SUSPENDED,
@@ -811,6 +814,7 @@ static ERL_NIF_TERM build_suspended_context_result(ErlNifEnv *env, suspended_con
811814
ERL_NIF_TERM args_term = py_to_term(env, tl_pending_args);
812815

813816
tl_pending_callback = false;
817+
Py_CLEAR(tl_pending_args);
814818

815819
return enif_make_tuple4(env,
816820
ATOM_SUSPENDED,
@@ -1553,6 +1557,7 @@ static PyObject *erlang_call_impl(PyObject *self, PyObject *args) {
15531557
tl_pending_func_name = enif_alloc(func_name_len + 1);
15541558
if (tl_pending_func_name == NULL) {
15551559
tl_pending_callback = false;
1560+
Py_CLEAR(tl_pending_args);
15561561
Py_DECREF(call_args);
15571562
PyErr_SetString(PyExc_MemoryError, "Failed to allocate function name");
15581563
return NULL;
@@ -1561,9 +1566,12 @@ static PyObject *erlang_call_impl(PyObject *self, PyObject *args) {
15611566
tl_pending_func_name[func_name_len] = '\0';
15621567
tl_pending_func_name_len = func_name_len;
15631568

1564-
/* Store args (take ownership) */
1565-
Py_XDECREF(tl_pending_args);
1566-
tl_pending_args = call_args; /* Takes ownership, don't decref */
1569+
/* Store args (take ownership)
1570+
* Use Py_XSETREF for swap-first pattern: sets tl_pending_args to new value
1571+
* BEFORE decref'ing old value. This prevents re-entrancy issues if the old
1572+
* object's finalizer triggers another erlang.call() during decref.
1573+
*/
1574+
Py_XSETREF(tl_pending_args, call_args);
15671575

15681576
/* Raise exception to abort Python execution */
15691577
PyErr_SetString(SuspensionRequiredException, "callback pending");
@@ -2649,6 +2657,7 @@ static ERL_NIF_TERM nif_resume_callback_dirty(ErlNifEnv *env, int argc, const ER
26492657
Py_DECREF(exc_args);
26502658
if (new_suspended == NULL) {
26512659
tl_pending_callback = false;
2660+
Py_CLEAR(tl_pending_args);
26522661
result = make_error(env, "create_nested_suspended_state_failed");
26532662
} else {
26542663
result = build_suspended_result(env, new_suspended);
@@ -2717,6 +2726,7 @@ static ERL_NIF_TERM nif_resume_callback_dirty(ErlNifEnv *env, int argc, const ER
27172726
Py_DECREF(exc_args);
27182727
if (new_suspended == NULL) {
27192728
tl_pending_callback = false;
2729+
Py_CLEAR(tl_pending_args);
27202730
result = make_error(env, "create_nested_suspended_state_failed");
27212731
} else {
27222732
result = build_suspended_result(env, new_suspended);

c_src/py_exec.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,7 @@ static void process_request(py_request_t *req) {
306306
Py_DECREF(exc_args);
307307
if (suspended == NULL) {
308308
tl_pending_callback = false;
309+
Py_CLEAR(tl_pending_args);
309310
req->result = make_error(env, "create_suspended_state_failed");
310311
} else {
311312
req->result = build_suspended_result(env, suspended);
@@ -393,6 +394,7 @@ static void process_request(py_request_t *req) {
393394
Py_DECREF(exc_args);
394395
if (suspended == NULL) {
395396
tl_pending_callback = false;
397+
Py_CLEAR(tl_pending_args);
396398
req->result = make_error(env, "create_suspended_state_failed");
397399
} else {
398400
req->result = build_suspended_result(env, suspended);

c_src/py_nif.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2280,6 +2280,7 @@ static ERL_NIF_TERM nif_context_call(ErlNifEnv *env, int argc, const ERL_NIF_TER
22802280

22812281
if (suspended == NULL) {
22822282
tl_pending_callback = false;
2283+
Py_CLEAR(tl_pending_args);
22832284
result = make_error(env, "create_suspended_state_failed");
22842285
} else {
22852286
result = build_suspended_context_result(env, suspended);
@@ -2382,6 +2383,7 @@ static ERL_NIF_TERM nif_context_eval(ErlNifEnv *env, int argc, const ERL_NIF_TER
23822383

23832384
if (suspended == NULL) {
23842385
tl_pending_callback = false;
2386+
Py_CLEAR(tl_pending_args);
23852387
result = make_error(env, "create_suspended_state_failed");
23862388
} else {
23872389
result = build_suspended_context_result(env, suspended);
@@ -2867,12 +2869,14 @@ static ERL_NIF_TERM nif_context_resume(ErlNifEnv *env, int argc, const ERL_NIF_T
28672869

28682870
if (nested == NULL) {
28692871
tl_pending_callback = false;
2872+
Py_CLEAR(tl_pending_args);
28702873
result = make_error(env, "create_nested_suspended_state_failed");
28712874
} else {
28722875
/* Copy accumulated callback results from parent to nested state */
28732876
if (copy_callback_results_to_nested(nested, state) != 0) {
28742877
enif_release_resource(nested);
28752878
tl_pending_callback = false;
2879+
Py_CLEAR(tl_pending_args);
28762880
result = make_error(env, "copy_callback_results_failed");
28772881
} else {
28782882
result = build_suspended_context_result(env, nested);
@@ -2921,12 +2925,14 @@ static ERL_NIF_TERM nif_context_resume(ErlNifEnv *env, int argc, const ERL_NIF_T
29212925

29222926
if (nested == NULL) {
29232927
tl_pending_callback = false;
2928+
Py_CLEAR(tl_pending_args);
29242929
result = make_error(env, "create_nested_suspended_state_failed");
29252930
} else {
29262931
/* Copy accumulated callback results from parent to nested state */
29272932
if (copy_callback_results_to_nested(nested, state) != 0) {
29282933
enif_release_resource(nested);
29292934
tl_pending_callback = false;
2935+
Py_CLEAR(tl_pending_args);
29302936
result = make_error(env, "copy_callback_results_failed");
29312937
} else {
29322938
result = build_suspended_context_result(env, nested);

0 commit comments

Comments
 (0)