-
-
Notifications
You must be signed in to change notification settings - Fork 33.7k
Open
Labels
3.13bugs and security fixesbugs and security fixes3.14bugs and security fixesbugs and security fixes3.15new features, bugs and security fixesnew features, bugs and security fixesinterpreter-core(Objects, Python, Grammar, and Parser dirs)(Objects, Python, Grammar, and Parser dirs)type-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or errortype-crashA hard crash of the interpreter, possibly with a core dumpA hard crash of the interpreter, possibly with a core dump
Description
What happened?
Implementing __neg__ to return a non-integer leaves float_richcompare holding an arbitrary object after PyNumber_Negative, yet it still feeds that value into _PyLong_Lshift, triggering the debug assertion PyLong_Check(a) and risking type confusion on release builds during mixed float-int comparisons.
Proof of Concept:
class EvilInt(int):
def __neg__(self):
return ""
victim = EvilInt(-(1 << 50))
probe = -float(1 << 50) - 0.5
probe < victimAffected Versions:
Details
| Python Version | Status | Exit Code |
|---|---|---|
Python 3.9.24+ (heads/3.9:111bbc15b26, Oct 28 2025, 13:30:44) |
ASSERT | -6 |
Python 3.10.19+ (heads/3.10:014261980b1, Oct 28 2025, 13:31:21) [Clang 18.1.3 (1ubuntu1)] |
ASSERT | -6 |
Python 3.11.14+ (heads/3.11:88f3f5b5f11, Oct 28 2025, 13:32:07) [Clang 18.1.3 (1ubuntu1)] |
ASSERT | -6 |
Python 3.12.12+ (heads/3.12:8cb2092bd8c, Oct 28 2025, 13:33:03) [Clang 18.1.3 (1ubuntu1)] |
ASSERT | -6 |
Python 3.13.9+ (heads/3.13:9c8eade20c6, Oct 28 2025, 13:33:58) [Clang 18.1.3 (1ubuntu1)] |
ASSERT | -6 |
Python 3.14.0+ (heads/3.14:2e216728038, Oct 28 2025, 13:34:48) [Clang 18.1.3 (1ubuntu1)] |
ASSERT | -6 |
Python 3.15.0a1+ (heads/main:f5394c257ce, Oct 28 2025, 13:35:40) [Clang 18.1.3 (1ubuntu1)] |
ASSERT | -6 |
Vulnerable Code:
Details
static PyObject*
float_richcompare(PyObject *v, PyObject *w, int op)
{
/* ... */
double fracpart;
double intpart;
PyObject *result = NULL;
PyObject *vv = NULL;
PyObject *ww = w;
if (wsign < 0) {
// Trigger the __neg__ on the int object whose return value set to ww
ww = PyNumber_Negative(w);
if (ww == NULL)
goto Error;
}
else
Py_INCREF(ww);
fracpart = modf(i, &intpart);
vv = PyLong_FromDouble(intpart);
if (vv == NULL)
goto Error;
if (fracpart != 0.0) {
/* Shift left, and or a 1 bit into vv
* to represent the lost fraction.
*/
PyObject *temp;
// Type Confusion: ww has been casted to PyLong with only assertion check (doesn't work on release version)
temp = _PyLong_Lshift(ww, 1);
Py_SETREF(ww, temp);
/* ... */
r = PyObject_RichCompareBool(vv, ww, op);
PyObject *
_PyLong_Lshift(PyObject *a, int64_t shiftby)
{
return long_lshift_int64(_PyLong_CAST(a), shiftby);
}Sanitizer Output:
python: Objects/longobject.c:5173: PyObject *_PyLong_Lshift(PyObject *, size_t): Assertion `PyLong_Check(a)' failed.
Aborted (core dumped)
Linked PRs
Metadata
Metadata
Assignees
Labels
3.13bugs and security fixesbugs and security fixes3.14bugs and security fixesbugs and security fixes3.15new features, bugs and security fixesnew features, bugs and security fixesinterpreter-core(Objects, Python, Grammar, and Parser dirs)(Objects, Python, Grammar, and Parser dirs)type-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or errortype-crashA hard crash of the interpreter, possibly with a core dumpA hard crash of the interpreter, possibly with a core dump