From 9790a2706573359e02fcfc5f18f9907467f4ec49 Mon Sep 17 00:00:00 2001 From: Armin Rigo Date: Wed, 2 May 2007 19:23:31 +0000 Subject: [PATCH] Fix for #1303614 and #1174712: - __dict__ descriptor abuse for subclasses of built-in types - subclassing from both ModuleType and another built-in types Thanks zseil for the patch. --- Lib/test/crashers/dangerous_subclassing.py | 12 --- Lib/test/crashers/modify_dict_attr.py | 20 ----- Lib/test/test_descr.py | 80 ++++++++++++++++++- Misc/NEWS | 10 +-- Objects/typeobject.c | 89 +++++++++++++++++++++- 5 files changed, 166 insertions(+), 45 deletions(-) delete mode 100644 Lib/test/crashers/dangerous_subclassing.py delete mode 100644 Lib/test/crashers/modify_dict_attr.py diff --git a/Lib/test/crashers/dangerous_subclassing.py b/Lib/test/crashers/dangerous_subclassing.py deleted file mode 100644 index 0479952b2ca..00000000000 --- a/Lib/test/crashers/dangerous_subclassing.py +++ /dev/null @@ -1,12 +0,0 @@ - -# http://python.org/sf/1174712 - -import types - -class X(types.ModuleType, str): - """Such a subclassing is incorrectly allowed -- - see the SF bug report for explanations""" - -if __name__ == '__main__': - X('name') # segfault: ModuleType.__init__() reads - # the dict at the wrong offset diff --git a/Lib/test/crashers/modify_dict_attr.py b/Lib/test/crashers/modify_dict_attr.py deleted file mode 100644 index 11c672106ab..00000000000 --- a/Lib/test/crashers/modify_dict_attr.py +++ /dev/null @@ -1,20 +0,0 @@ - -# http://python.org/sf/1303614 - -class Y(object): - pass - -class type_with_modifiable_dict(type, Y): - pass - -class MyClass(object): - """This class has its __dict__ attribute indirectly - exposed via the __dict__ getter/setter of Y. - """ - __metaclass__ = type_with_modifiable_dict - - -if __name__ == '__main__': - dictattr = Y.__dict__['__dict__'] - dictattr.__delete__(MyClass) # if we set tp_dict to NULL, - print MyClass # doing anything with MyClass segfaults diff --git a/Lib/test/test_descr.py b/Lib/test/test_descr.py index 53181ac1359..57cef440972 100644 --- a/Lib/test/test_descr.py +++ b/Lib/test/test_descr.py @@ -3,6 +3,7 @@ from test.test_support import verify, vereq, verbose, TestFailed, TESTFN, get_original_stdout from copy import deepcopy import warnings +import types warnings.filterwarnings("ignore", r'complex divmod\(\), // and % are deprecated$', @@ -861,6 +862,16 @@ def pymods(): ("getattr", "foo"), ("delattr", "foo")]) + # http://python.org/sf/1174712 + try: + class Module(types.ModuleType, str): + pass + except TypeError: + pass + else: + raise TestFailed("inheriting from ModuleType and str at the " + "same time should fail") + def multi(): if verbose: print "Testing multiple inheritance..." class C(object): @@ -2907,8 +2918,73 @@ def setdict(): cant(a, []) cant(a, 1) del a.__dict__ # Deleting __dict__ is allowed - # Classes don't allow __dict__ assignment - cant(C, {}) + + class Base(object): + pass + def verify_dict_readonly(x): + """ + x has to be an instance of a class inheriting from Base. + """ + cant(x, {}) + try: + del x.__dict__ + except (AttributeError, TypeError): + pass + else: + raise TestFailed, "shouldn't allow del %r.__dict__" % x + dict_descr = Base.__dict__["__dict__"] + try: + dict_descr.__set__(x, {}) + except (AttributeError, TypeError): + pass + else: + raise TestFailed, "dict_descr allowed access to %r's dict" % x + + # Classes don't allow __dict__ assignment and have readonly dicts + class Meta1(type, Base): + pass + class Meta2(Base, type): + pass + class D(object): + __metaclass__ = Meta1 + class E(object): + __metaclass__ = Meta2 + for cls in C, D, E: + verify_dict_readonly(cls) + class_dict = cls.__dict__ + try: + class_dict["spam"] = "eggs" + except TypeError: + pass + else: + raise TestFailed, "%r's __dict__ can be modified" % cls + + # Modules also disallow __dict__ assignment + class Module1(types.ModuleType, Base): + pass + class Module2(Base, types.ModuleType): + pass + for ModuleType in Module1, Module2: + mod = ModuleType("spam") + verify_dict_readonly(mod) + mod.__dict__["spam"] = "eggs" + + # Exception's __dict__ can be replaced, but not deleted + class Exception1(Exception, Base): + pass + class Exception2(Base, Exception): + pass + for ExceptionType in Exception, Exception1, Exception2: + e = ExceptionType() + e.__dict__ = {"a": 1} + vereq(e.a, 1) + try: + del e.__dict__ + except (TypeError, AttributeError): + pass + else: + raise TestFaied, "%r's __dict__ can be deleted" % e + def pickles(): if verbose: diff --git a/Misc/NEWS b/Misc/NEWS index 36a18c2213f..2a1ee0a0abb 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -12,6 +12,9 @@ What's New in Python 2.6 alpha 1? Core and builtins ----------------- +- Bug #1303614: don't expose object's __dict__ when the dict is + inherited from a builtin base. + - When __slots__ are set to a unicode string, make it work the same as setting a plain string, ie don't expand to single letter identifiers. @@ -199,13 +202,6 @@ Core and builtins - Bug #1664966: Fix crash in exec if Unicode filename can't be decoded. -- Add new requirements for metaclasses. 1) If type or a subclass of type - occurs in __bases__, it must occur before any non-type bases, e.g. - before regular classes. 2) If you assign to __bases__, you may not - change the metaclass. Many more illegal assignments to __bases__ - are now checked and raise TypeErrors. This changed fixed at least - one known crash. - Library ------- diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 89d2d4f9813..77358513528 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -1423,10 +1423,12 @@ extra_ivars(PyTypeObject *type, PyTypeObject *base) type->tp_itemsize != base->tp_itemsize; } if (type->tp_weaklistoffset && base->tp_weaklistoffset == 0 && - type->tp_weaklistoffset + sizeof(PyObject *) == t_size) + type->tp_weaklistoffset + sizeof(PyObject *) == t_size && + type->tp_flags & Py_TPFLAGS_HEAPTYPE) t_size -= sizeof(PyObject *); if (type->tp_dictoffset && base->tp_dictoffset == 0 && - type->tp_dictoffset + sizeof(PyObject *) == t_size) + type->tp_dictoffset + sizeof(PyObject *) == t_size && + type->tp_flags & Py_TPFLAGS_HEAPTYPE) t_size -= sizeof(PyObject *); return t_size != b_size; @@ -1452,12 +1454,73 @@ static int object_init(PyObject *, PyObject *, PyObject *); static int update_slot(PyTypeObject *, PyObject *); static void fixup_slot_dispatchers(PyTypeObject *); +/* + * Helpers for __dict__ descriptor. We don't want to expose the dicts + * inherited from various builtin types. The builtin base usually provides + * its own __dict__ descriptor, so we use that when we can. + */ +static PyTypeObject * +get_builtin_base_with_dict(PyTypeObject *type) +{ + while (type->tp_base != NULL) { + if (type->tp_dictoffset != 0 && + !(type->tp_flags & Py_TPFLAGS_HEAPTYPE)) + return type; + type = type->tp_base; + } + return NULL; +} + +static PyObject * +get_dict_descriptor(PyTypeObject *type) +{ + static PyObject *dict_str; + PyObject *descr; + + if (dict_str == NULL) { + dict_str = PyString_InternFromString("__dict__"); + if (dict_str == NULL) + return NULL; + } + descr = _PyType_Lookup(type, dict_str); + if (descr == NULL || !PyDescr_IsData(descr)) + return NULL; + + return descr; +} + +static void +raise_dict_descr_error(PyObject *obj) +{ + PyErr_Format(PyExc_TypeError, + "this __dict__ descriptor does not support " + "'%.200s' objects", obj->ob_type->tp_name); +} + static PyObject * subtype_dict(PyObject *obj, void *context) { - PyObject **dictptr = _PyObject_GetDictPtr(obj); + PyObject **dictptr; PyObject *dict; + PyTypeObject *base; + base = get_builtin_base_with_dict(obj->ob_type); + if (base != NULL) { + descrgetfunc func; + PyObject *descr = get_dict_descriptor(base); + if (descr == NULL) { + raise_dict_descr_error(obj); + return NULL; + } + func = descr->ob_type->tp_descr_get; + if (func == NULL) { + raise_dict_descr_error(obj); + return NULL; + } + return func(descr, obj, (PyObject *)(obj->ob_type)); + } + + dictptr = _PyObject_GetDictPtr(obj); if (dictptr == NULL) { PyErr_SetString(PyExc_AttributeError, "This object has no __dict__"); @@ -1473,9 +1536,27 @@ subtype_dict(PyObject *obj, void *context) static int subtype_setdict(PyObject *obj, PyObject *value, void *context) { - PyObject **dictptr = _PyObject_GetDictPtr(obj); + PyObject **dictptr; PyObject *dict; + PyTypeObject *base; + base = get_builtin_base_with_dict(obj->ob_type); + if (base != NULL) { + descrsetfunc func; + PyObject *descr = get_dict_descriptor(base); + if (descr == NULL) { + raise_dict_descr_error(obj); + return -1; + } + func = descr->ob_type->tp_descr_set; + if (func == NULL) { + raise_dict_descr_error(obj); + return -1; + } + return func(descr, obj, value); + } + + dictptr = _PyObject_GetDictPtr(obj); if (dictptr == NULL) { PyErr_SetString(PyExc_AttributeError, "This object has no __dict__");