TGViewer
Находки в опенсорсе Находки в опенсорсе @opensource_findings · 12.6K subscribers
Post #973 16.5K
Если вы понимаете данный баг, то вы знаете питон лучше 95% людей

А если нет, то вы многое узнаете про то, как работает память и почему мутабельностью стоит пользоваться с осторожностью.
Недавно я увидел один из лучших багов в CPython за долгое время. А я видел много багов 🌚️️

Вот код, который делает две критичные безумные вещи (попробуйте их найти прежде, чем читать дальше):


class Evil:
def __eq__(self, other):
return other

leaked = vars(list) == Evil()
name = "example"
leaked[name] = lambda self: "probe"
print(getattr(list, name)([]))
del leaked[name]
print(hasattr(list, name))


Разбор бага

Во-первых, что произойдет?
1. Мы мутируем встроенный и иммутабельный тип list, хотя такое должно быть невозможно
2. Интерпретатор закрашится; не упадет с исключением, а словит core dump на уровне C кода

Но почему? Пройдемся по каждой строке. Со ссылками на исходники: кликайте и читайте!

1. Сначала мы создадим класс Evil, который просто возвращает из __eq__ второй объект, который ему передали. Так можно делать, тут нет ничего сломанного.
2. Далее, мы сравниваем vars(list) с Evil, и вот тут как раз в leaked попадет второй объект из Evil.__eq__, в нашем случае vars(list)
3. vars возвращает вам list.__dict__, который является не обычным dict, а types.MappingProxyType, то есть иммутабельным маппингом поверх оригинального значения. Добавлять в него ключи нельзя. Потому что мы не хотим, чтобы в список или другие типы нам подкидывали какие-то новые методы во время работы программы
4. Как работает сравнение для mappingproxy? mappingproxy хранит в себе оригинальный мутабельный словарь, который он "проксирует" или "защищает от изменений". И сравнивает на самом деле не себя, а оригинальный объект
5. В случае с list.__dict__ мы получаем PyDictProxy_New(self->tp_dict), где хранится тот самый настоящий и защищенный __dict__ из типа list, который обычно не доступен вне C кода
6. При сравнении mappingproxy разворачивается и достает из себя ->mapping, тот самый чистый и мутабельный ->tp_dict
7. Теперь у нас есть ->tp_dict, мы можем в него добавлять методы: leaked[name] = lambda self: "probe". Они будут работать. Мы только что достигли пункта 1. и мутировали встроенный Python тип без единого импорта
8. Далее происходит еще более дикое. Мы удаляем метод, который добавили через del leaked[name]
9. Питон не ожидает такого: методы у встроенных типов не могут появляться и исчезать. И при следующем обращении к hasattr(list, name) крашится вот тут на обращении к уже освобожденной памяти. EXC_BAD_ACCESS, пункт 2. пал

пу-пу-пу

Фикс

Баг: https://github.com/python/cpython/issues/152405

Как такое чинить?
1. Нужно сохранить обратную совместимость для всех видов сравнений. Менять типы или значения нельзя
2. Необходимо убрать креш и мутацию типа
3. Сильно раздувать потребление памяти / время работы тоже нельзя

Мой PR: https://github.com/python/cpython/pull/152483

Что он делает? Если мы сравниваем прокси поверх обычного словаря, но не с известными нам безопасными типами, то мы делаем копию словаря и сравниваем ее:


if (
PyDict_CheckExact(v->mapping) &&
!(PyAnyDict_CheckExact(w) || PyODict_CheckExact(w))
) {
// So, instead we send a copy:
PyObject *copy = PyDict_Copy(v->mapping);
if (copy == NULL) {
return NULL;
}
PyObject *res = PyObject_RichCompare(copy, w, op);
Py_DECREF(copy);
return res;
}


Таким образом - все ошибки выше уходят. Любая мутация останется в копии. Доп память не тратится в большом количестве популярных случаев.
Данный баг все еще есть на всех версиях питона. Я вам его не показывал, вы ничего не видели.

Обсуждение: какие у вас были самые кринжовые / прикольные баги?

| Поддержать | YouTube | GitHub | Чат |
GitHub cpython/Python/bltinmodule.c at f6e904e1a666cb1e5664750b1c3d8f89cba3a769 · python/cpython The Python programming language. Contribute to python/cpython development by creating an account on GitHub.
  • 👍 64
  • 🔥 51
  • 🤯 18
  • ❤ 11
  • 👎 1
  • 🤡 1
More from @opensource_findings
  1. Sep 15, 2026PEPы в Python окончательно вышли из-под контроля Давайте посмотрим, что происходит с ПЕПам…
  2. Sep 10, 2026Большая бесплатная конференция в Нижнем Новгороде 17 октября Регистрация: https://itgorky.…
  3. Sep 1, 2026Post #985
  4. Aug 24, 2026JIT могут удалить из CPython! PEP-836: https://peps.python.org/pep-0836 JIT в CPython имее…
  5. Aug 18, 2026Как AWS у опенсорсера пакеты отжимал Нерегулярная рубрика "посмотрите, что творится!". Дан…
  6. Aug 14, 2026Какие уникальные фичи есть в django-modern-rest? Иногда, когда я добавляю какие-то фичи в…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →