Skip to content

Commit 149ecbb

Browse files
[3.13] gh-142829: Fix use-after-free in Context.__eq__ via re-entrant ContextVar.set (GH-142905) (GH-143871)
(cherry picked from commit a4086d7) Co-authored-by: A.Ibrahim <abdulrasheedibrahim47@gmail.com>
1 parent 343b5c4 commit 149ecbb

File tree

3 files changed

+55
-5
lines changed

3 files changed

+55
-5
lines changed

Lib/test/test_context.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,36 @@ def sub(num):
370370
tp.shutdown()
371371
self.assertEqual(results, list(range(10)))
372372

373+
def test_context_eq_reentrant_contextvar_set(self):
374+
var = contextvars.ContextVar("v")
375+
ctx1 = contextvars.Context()
376+
ctx2 = contextvars.Context()
377+
378+
class ReentrantEq:
379+
def __eq__(self, other):
380+
ctx1.run(lambda: var.set(object()))
381+
return True
382+
383+
ctx1.run(var.set, ReentrantEq())
384+
ctx2.run(var.set, object())
385+
ctx1 == ctx2
386+
387+
def test_context_eq_reentrant_contextvar_set_in_hash(self):
388+
var = contextvars.ContextVar("v")
389+
ctx1 = contextvars.Context()
390+
ctx2 = contextvars.Context()
391+
392+
class ReentrantHash:
393+
def __hash__(self):
394+
ctx1.run(lambda: var.set(object()))
395+
return 0
396+
def __eq__(self, other):
397+
return isinstance(other, ReentrantHash)
398+
399+
ctx1.run(var.set, ReentrantHash())
400+
ctx2.run(var.set, ReentrantHash())
401+
ctx1 == ctx2
402+
373403

374404
# HAMT Tests
375405

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix a use-after-free crash in :class:`contextvars.Context` comparison when a
2+
custom ``__eq__`` method modifies the context via
3+
:meth:`~contextvars.ContextVar.set`.

Python/hamt.c

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2368,6 +2368,10 @@ _PyHamt_Eq(PyHamtObject *v, PyHamtObject *w)
23682368
return 0;
23692369
}
23702370

2371+
Py_INCREF(v);
2372+
Py_INCREF(w);
2373+
2374+
int res = 1;
23712375
PyHamtIteratorState iter;
23722376
hamt_iter_t iter_res;
23732377
hamt_find_t find_res;
@@ -2383,25 +2387,38 @@ _PyHamt_Eq(PyHamtObject *v, PyHamtObject *w)
23832387
find_res = hamt_find(w, v_key, &w_val);
23842388
switch (find_res) {
23852389
case F_ERROR:
2386-
return -1;
2390+
res = -1;
2391+
goto done;
23872392

23882393
case F_NOT_FOUND:
2389-
return 0;
2394+
res = 0;
2395+
goto done;
23902396

23912397
case F_FOUND: {
2398+
Py_INCREF(v_key);
2399+
Py_INCREF(v_val);
2400+
Py_INCREF(w_val);
23922401
int cmp = PyObject_RichCompareBool(v_val, w_val, Py_EQ);
2402+
Py_DECREF(v_key);
2403+
Py_DECREF(v_val);
2404+
Py_DECREF(w_val);
23932405
if (cmp < 0) {
2394-
return -1;
2406+
res = -1;
2407+
goto done;
23952408
}
23962409
if (cmp == 0) {
2397-
return 0;
2410+
res = 0;
2411+
goto done;
23982412
}
23992413
}
24002414
}
24012415
}
24022416
} while (iter_res != I_END);
24032417

2404-
return 1;
2418+
done:
2419+
Py_DECREF(v);
2420+
Py_DECREF(w);
2421+
return res;
24052422
}
24062423

24072424
Py_ssize_t

0 commit comments

Comments
 (0)