https://github.com/python/cpython/commit/ec8a4201f804e69d4630cc1c74ff44db8c6d6efa
commit: ec8a4201f804e69d4630cc1c74ff44db8c6d6efa
branch: main
author: Thomas Kowalski <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2026-08-30T10:29:31+03:00
summary:

gh-148442: Eliminate race condition in list rich comparison (GH-148531)

files:
A 
Misc/NEWS.d/next/Core_and_Builtins/2026-04-13-00-00-00.gh-issue-148442.aBcDeF.rst
M Lib/test/test_list.py
M Objects/listobject.c

diff --git a/Lib/test/test_list.py b/Lib/test/test_list.py
index 44ecd62d6735def..1b0724c2b1c99dc 100644
--- a/Lib/test/test_list.py
+++ b/Lib/test/test_list.py
@@ -244,7 +244,7 @@ def __eq__(self, other):
 
         list1 = [X()]
         list2 = [Y()]
-        self.assertTrue(list1 == list2)
+        self.assertFalse(list1 == list2)
 
         list3 = [Z()]
         list4 = [1]
@@ -261,6 +261,54 @@ def __lt__(self, other):
         with self.assertRaises(TypeError):
             a[0] < a
 
+    def test_richcompare_stale_element_list_vitem(self):
+        # gh-148442: list_richcompare_impl must use the captured vitem for
+        # the final ordering comparison, not re-read list1's slot after __eq__
+        # may have mutated it.
+        #
+        # x.__eq__(0) puts AlwaysLT() into list1[0] and returns False.
+        class AlwaysLT:
+            def __eq__(self, other: object) -> bool:
+                return False
+
+            def __gt__(self, other: object) -> bool:
+                return False
+
+        class Mutating:
+            def __eq__(self, other: object) -> bool:
+                list1[0] = AlwaysLT()
+                return False
+
+            def __gt__(self, other: object) -> bool:
+                return True
+
+        list1 = [Mutating(), 0]
+        list2 = [0, 0]
+        self.assertTrue(list1 > list2)
+
+    def test_richcompare_stale_element_list_witem(self):
+        # gh-148442: list_richcompare_impl must use the captured witem for
+        # the final ordering comparison, not re-read list2's slot after __eq__
+        # may have mutated it.
+        #
+        # x.__eq__(0) puts AlwaysGT() into list2[0] and returns False.
+        class AlwaysGT:
+            pass
+
+        class Mutating:
+            def __eq__(self, other: object) -> bool:
+                list2[0] = AlwaysGT()
+                return False
+
+            def __gt__(self, other: object) -> bool:
+                if isinstance(other, AlwaysGT):
+                    return False  # pretend AlwaysGT beats us
+                return True       # beat everything else (including 0)
+
+        list1 = [Mutating(), 0]
+        list2 = [0, 0]
+        self.assertTrue(list1 > list2)
+
     def test_list_index_modifing_operand(self):
         # See gh-120384
         class evil:
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 000000000000000..e30a3dee6f54dd7
--- /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.
diff --git a/Objects/listobject.c b/Objects/listobject.c
index 8a9c9bda68269b8..81eb3e1102159b5 100644
--- a/Objects/listobject.c
+++ b/Objects/listobject.c
@@ -3454,7 +3454,10 @@ 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.
+     * We incref vitem/witem before calling PyObject_RichCompareBool, which may
+     * release the GIL and allow the list to be mutated in the meantime.
+     */
     for (i = 0; i < Py_SIZE(vl) && i < Py_SIZE(wl); i++) {
         PyObject *vitem = vl->ob_item[i];
         PyObject *witem = wl->ob_item[i];
@@ -3465,36 +3468,36 @@ 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)
-            break;
-    }
-
-    if (i >= Py_SIZE(vl) || i >= Py_SIZE(wl)) {
-        /* No more items to compare -- compare sizes */
-        Py_RETURN_RICHCOMPARE(Py_SIZE(vl), Py_SIZE(wl), op);
-    }
+        }
+        if (!k) {
+            /* 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;
+        }
 
-    /* We have an item that differs -- shortcuts for EQ/NE */
-    if (op == Py_EQ) {
-        Py_RETURN_FALSE;
-    }
-    if (op == Py_NE) {
-        Py_RETURN_TRUE;
+        Py_DECREF(vitem);
+        Py_DECREF(witem);
     }
 
-    /* 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);
-    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 *

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to