From 8447fa6130ae9832161abf925838a7a72b074ebb Mon Sep 17 00:00:00 2001 From: Thomas Kowalski Date: Sun, 12 Apr 2026 14:29:59 +0200 Subject: [PATCH 1/5] fix: eliminate race condition in list rich comparison --- Objects/listobject.c | 44 +++++++++++++++++++++++++++++--------------- 1 file changed, 29 insertions(+), 15 deletions(-) diff --git a/Objects/listobject.c b/Objects/listobject.c index 4a98c8e54ab03f..13f700aa9fcd84 100644 --- a/Objects/listobject.c +++ b/Objects/listobject.c @@ -3432,10 +3432,17 @@ list_richcompare_impl(PyObject *v, PyObject *w, int op) Py_RETURN_TRUE; } - /* Search for the first index where items are different */ + /* Search for the first index where items are different. + * Keep vitem/witem alive across the break so that the final ordering + * comparison uses the same differing objects. + * This is required as PyObject_RichCompareBool may release the GIL and + * the list may be mutated in the meantime. + */ + PyObject *vitem = NULL; + PyObject *witem = NULL; for (i = 0; i < Py_SIZE(vl) && i < Py_SIZE(wl); i++) { - PyObject *vitem = vl->ob_item[i]; - PyObject *witem = wl->ob_item[i]; + vitem = vl->ob_item[i]; + witem = wl->ob_item[i]; if (vitem == witem) { continue; } @@ -3443,33 +3450,40 @@ list_richcompare_impl(PyObject *v, PyObject *w, int op) Py_INCREF(vitem); Py_INCREF(witem); int k = PyObject_RichCompareBool(vitem, witem, Py_EQ); - Py_DECREF(vitem); - Py_DECREF(witem); - if (k < 0) + if (k < 0) { + Py_DECREF(vitem); + Py_DECREF(witem); return NULL; - if (!k) + } + if (!k) { + /* keep vitem/witem alive for the final comparison */ break; + } + + Py_DECREF(vitem); + Py_DECREF(witem); + vitem = witem = NULL; } - if (i >= Py_SIZE(vl) || i >= Py_SIZE(wl)) { - /* No more items to compare -- compare sizes */ + if (vitem == NULL) { + /* All compared elements were equal -- compare sizes */ Py_RETURN_RICHCOMPARE(Py_SIZE(vl), Py_SIZE(wl), op); } /* We have an item that differs -- shortcuts for EQ/NE */ if (op == Py_EQ) { + Py_DECREF(vitem); + Py_DECREF(witem); Py_RETURN_FALSE; } if (op == Py_NE) { + Py_DECREF(vitem); + Py_DECREF(witem); Py_RETURN_TRUE; } - /* Compare the final item again using the proper operator */ - PyObject *vitem = vl->ob_item[i]; - PyObject *witem = wl->ob_item[i]; - Py_INCREF(vitem); - Py_INCREF(witem); - PyObject *result = PyObject_RichCompare(vl->ob_item[i], wl->ob_item[i], op); + /* Compare the differing items using the proper operator */ + PyObject *result = PyObject_RichCompare(vitem, witem, op); Py_DECREF(vitem); Py_DECREF(witem); return result; From 0774692a15c8bf866379c3f9d31223764f816575 Mon Sep 17 00:00:00 2001 From: Thomas Kowalski Date: Mon, 13 Apr 2026 17:30:28 -0400 Subject: [PATCH 2/5] refactor: hoist the comparison logic --- Objects/listobject.c | 55 ++++++++++++++++++-------------------------- 1 file changed, 22 insertions(+), 33 deletions(-) diff --git a/Objects/listobject.c b/Objects/listobject.c index 13f700aa9fcd84..926625069a886f 100644 --- a/Objects/listobject.c +++ b/Objects/listobject.c @@ -3433,16 +3433,12 @@ list_richcompare_impl(PyObject *v, PyObject *w, int op) } /* Search for the first index where items are different. - * Keep vitem/witem alive across the break so that the final ordering - * comparison uses the same differing objects. - * This is required as PyObject_RichCompareBool may release the GIL and - * the list may be mutated in the meantime. + * We incref vitem/witem before calling PyObject_RichCompareBool, which may + * release the GIL and allow the list to be mutated in the meantime. */ - PyObject *vitem = NULL; - PyObject *witem = NULL; for (i = 0; i < Py_SIZE(vl) && i < Py_SIZE(wl); i++) { - vitem = vl->ob_item[i]; - witem = wl->ob_item[i]; + PyObject *vitem = vl->ob_item[i]; + PyObject *witem = wl->ob_item[i]; if (vitem == witem) { continue; } @@ -3456,37 +3452,30 @@ list_richcompare_impl(PyObject *v, PyObject *w, int op) return NULL; } if (!k) { - /* keep vitem/witem alive for the final comparison */ - break; + /* We have a differing item -- shortcuts for EQ/NE */ + if (op == Py_EQ) { + Py_DECREF(vitem); + Py_DECREF(witem); + Py_RETURN_FALSE; + } + if (op == Py_NE) { + Py_DECREF(vitem); + Py_DECREF(witem); + Py_RETURN_TRUE; + } + /* Compare the differing items using the proper operator */ + PyObject *result = PyObject_RichCompare(vitem, witem, op); + Py_DECREF(vitem); + Py_DECREF(witem); + return result; } Py_DECREF(vitem); Py_DECREF(witem); - vitem = witem = NULL; - } - - if (vitem == NULL) { - /* All compared elements were equal -- compare sizes */ - Py_RETURN_RICHCOMPARE(Py_SIZE(vl), Py_SIZE(wl), op); } - /* We have an item that differs -- shortcuts for EQ/NE */ - if (op == Py_EQ) { - Py_DECREF(vitem); - Py_DECREF(witem); - Py_RETURN_FALSE; - } - if (op == Py_NE) { - Py_DECREF(vitem); - Py_DECREF(witem); - Py_RETURN_TRUE; - } - - /* Compare the differing items using the proper operator */ - PyObject *result = PyObject_RichCompare(vitem, witem, op); - Py_DECREF(vitem); - Py_DECREF(witem); - return result; + /* All compared elements were equal -- compare sizes */ + Py_RETURN_RICHCOMPARE(Py_SIZE(vl), Py_SIZE(wl), op); } static PyObject * From 02a92ea44164cee3bbf3e368cd390bbbb39c228b Mon Sep 17 00:00:00 2001 From: Thomas Kowalski Date: Mon, 13 Apr 2026 17:32:27 -0400 Subject: [PATCH 3/5] news: add entry --- .../2026-04-13-00-00-00.gh-issue-148442.aBcDeF.rst | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-04-13-00-00-00.gh-issue-148442.aBcDeF.rst diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-04-13-00-00-00.gh-issue-148442.aBcDeF.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-04-13-00-00-00.gh-issue-148442.aBcDeF.rst new file mode 100644 index 00000000000000..e3911df8517535 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-04-13-00-00-00.gh-issue-148442.aBcDeF.rst @@ -0,0 +1,4 @@ +Fix a race condition in ``list.__lt__``, ``list.__le__``, ``list.__gt__``, +and ``list.__ge__``. Previously, under concurrent list mutation, the items +being compared could be replaced with other objects before the final comparison, +causing the final comparison result to be incorrect. \ No newline at end of file From eb32c43c94f86a61feb5c9d3ef9a79b88c947d3c Mon Sep 17 00:00:00 2001 From: Thomas Kowalski Date: Tue, 14 Apr 2026 08:58:07 -0400 Subject: [PATCH 4/5] test: update test_equal_operator_modifying_operand --- Lib/test/test_list.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_list.py b/Lib/test/test_list.py index 642b54d34849da..fb0fc945761e05 100644 --- a/Lib/test/test_list.py +++ b/Lib/test/test_list.py @@ -245,7 +245,7 @@ def __eq__(self, other): list1 = [X()] list2 = [Y()] - self.assertTrue(list1 == list2) + self.assertFalse(list1 == list2) list3 = [Z()] list4 = [1] From cc583a3b01bada94f2c23ef1596d668b8c62909d Mon Sep 17 00:00:00 2001 From: Thomas Kowalski Date: Tue, 14 Apr 2026 08:59:33 -0400 Subject: [PATCH 5/5] news: reformat entry --- .../2026-04-13-00-00-00.gh-issue-148442.aBcDeF.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-04-13-00-00-00.gh-issue-148442.aBcDeF.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-04-13-00-00-00.gh-issue-148442.aBcDeF.rst index e3911df8517535..e30a3dee6f54dd 100644 --- a/Misc/NEWS.d/next/Core_and_Builtins/2026-04-13-00-00-00.gh-issue-148442.aBcDeF.rst +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-04-13-00-00-00.gh-issue-148442.aBcDeF.rst @@ -1,4 +1,4 @@ Fix a race condition in ``list.__lt__``, ``list.__le__``, ``list.__gt__``, and ``list.__ge__``. Previously, under concurrent list mutation, the items being compared could be replaced with other objects before the final comparison, -causing the final comparison result to be incorrect. \ No newline at end of file +causing the final comparison result to be incorrect.