Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions Lib/test/test_descr.py
Original file line number Diff line number Diff line change
Expand Up @@ -5052,6 +5052,53 @@ def __getattribute__(self, name):

self.assertRaises(AttributeError, getattr, EvilGetattribute(), "attr")

def test_getattr_name_changes_class(self):
# gh-159098: The type could be freed while hashing or comparing
# the attribute name.
class Replacement:
pass

def make_obj():
class Victim:
pass
return Victim()

class HashChangesClass(str):
def __hash__(self):
obj.__class__ = Replacement
gc.collect()
return super().__hash__()

obj = make_obj()
with self.assertRaises(AttributeError):
getattr(obj, HashChangesClass("missing"))

class EqChangesClass(str):
def __hash__(self):
return hash("pad")
def __eq__(self, other):
obj.__class__ = Replacement
gc.collect()
return False

obj = make_obj()
obj.pad = None
with self.assertRaises(AttributeError):
getattr(obj, EqChangesClass("missing"))

class KeyChangesClass(str):
def __hash__(self):
return hash("missing")
def __eq__(self, other):
obj.__class__ = Replacement
gc.collect()
return False

obj = make_obj()
obj.__dict__[KeyChangesClass("pad")] = None
with self.assertRaises(AttributeError):
obj.missing()

def test_type___getattribute__(self):
self.assertRaises(TypeError, type.__getattribute__, list, type)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fix a crash in attribute and method lookup when hashing or comparing a
:class:`str` subclass changes the object's class.
17 changes: 17 additions & 0 deletions Objects/object.c
Original file line number Diff line number Diff line change
Expand Up @@ -1709,12 +1709,14 @@ _PyObject_GetMethod(PyObject *obj, PyObject *name, PyObject **method)
dict = NULL;
}
}
_Py_INCREF_TYPE(tp);
if (dict != NULL) {
Py_INCREF(dict);
if (PyDict_GetItemRef(dict, name, method) != 0) {
// found or error
Py_DECREF(dict);
Py_XDECREF(descr);
_Py_DECREF_TYPE(tp);
return 0;
}
// not found
Expand All @@ -1723,23 +1725,27 @@ _PyObject_GetMethod(PyObject *obj, PyObject *name, PyObject **method)

if (meth_found) {
*method = descr;
_Py_DECREF_TYPE(tp);
return 1;
}

if (f != NULL) {
*method = f(descr, obj, (PyObject *)Py_TYPE(obj));
Py_DECREF(descr);
_Py_DECREF_TYPE(tp);
return 0;
}

if (descr != NULL) {
*method = descr;
_Py_DECREF_TYPE(tp);
return 0;
}

PyErr_Format(PyExc_AttributeError,
"'%.100s' object has no attribute '%U'",
tp->tp_name, name);
_Py_DECREF_TYPE(tp);

_PyObject_SetAttributeErrorContext(obj, name);
return 0;
Expand Down Expand Up @@ -1823,22 +1829,26 @@ _PyObject_GetMethodStackRef(PyThreadState *ts, _PyStackRef *self,
dict = NULL;
}
}
_Py_INCREF_TYPE(tp);
if (dict != NULL) {
assert(PyUnicode_CheckExact(name));
int found = _PyDict_GetMethodStackRef((PyDictObject *)dict, name, method);
if (found < 0) {
assert(PyStackRef_IsNull(*method));
PyStackRef_CLEAR(*self);
_Py_DECREF_TYPE(tp);
return -1;
}
else if (found) {
PyStackRef_CLEAR(*self);
_Py_DECREF_TYPE(tp);
return 0;
}
}

if (meth_found) {
assert(!PyStackRef_IsNull(*method));
_Py_DECREF_TYPE(tp);
return 1;
}

Expand All @@ -1847,17 +1857,20 @@ _PyObject_GetMethodStackRef(PyThreadState *ts, _PyStackRef *self,
PyObject *callable = _PyClassMethod_GetFunc(descr);
PyStackRef_XSETREF(*method, PyStackRef_FromPyObjectNew(callable));
PyStackRef_XSETREF(*self, PyStackRef_FromPyObjectNew((PyObject *)tp));
_Py_DECREF_TYPE(tp);
return 1;
}
else if (Py_IS_TYPE(descr, &PyStaticMethod_Type)) {
PyObject *callable = _PyStaticMethod_GetFunc(descr);
PyStackRef_XSETREF(*method, PyStackRef_FromPyObjectNew(callable));
PyStackRef_CLEAR(*self);
_Py_DECREF_TYPE(tp);
return 0;
}
PyObject *value = f(descr, obj, (PyObject *)tp);
PyStackRef_CLEAR(*method);
PyStackRef_CLEAR(*self);
_Py_DECREF_TYPE(tp);
if (value) {
*method = PyStackRef_FromPyObjectSteal(value);
return 0;
Expand All @@ -1868,12 +1881,14 @@ _PyObject_GetMethodStackRef(PyThreadState *ts, _PyStackRef *self,
if (descr != NULL) {
assert(!PyStackRef_IsNull(*method));
PyStackRef_CLEAR(*self);
_Py_DECREF_TYPE(tp);
return 0;
}

PyErr_Format(PyExc_AttributeError,
"'%.100s' object has no attribute '%U'",
tp->tp_name, name);
_Py_DECREF_TYPE(tp);

_PyObject_SetAttributeErrorContext(obj, name);
assert(PyStackRef_IsNull(*method));
Expand Down Expand Up @@ -1912,6 +1927,7 @@ _PyObject_GenericGetAttrWithDict(PyObject *obj, PyObject *name,
}

Py_INCREF(name);
_Py_INCREF_TYPE(tp);

PyThreadState *tstate = _PyThreadState_GET();
_PyCStackRef cref;
Expand Down Expand Up @@ -2016,6 +2032,7 @@ _PyObject_GenericGetAttrWithDict(PyObject *obj, PyObject *name,
}
done:
_PyThreadState_PopCStackRef(tstate, &cref);
_Py_DECREF_TYPE(tp);
Py_DECREF(name);
return res;
}
Expand Down
Loading