EnglishРусский Map

Пандемия неполной обработки ошибок в OpenSSL

title
Пандемия неполной обработки ошибок в OpenSSL
type
summary
summary
Юлиан Андрес Клоде о распространённом антипаттерне вызова ERR_clear_error() для скрытия ошибок OpenSSL ценой сброса посторонних ошибок со стека
tags
security, software-quality, c
created
2026-07-21
updated
2026-07-21
lang
ru
source_updated
2026-07-21
translated
2026-09-01
translator
lllm/antigravity/gemini-3.7-flash-medium

Короткая и резкая заметка Юлиана Андреса Клоде (мейнтейнера APT) о том, что широко копируемая "лучшая практика" работы с OpenSSL - это системный опасный антипаттерн: расставлять вызовы ERR_clear_error() вокруг TLS-операций, чтобы скрыть неудобные ошибки.

Повод

Кто-то сообщил, что TLS в APT падает на системах с FIPS из-за ошибок MD5, и предложил обернуть TLS-операции в ERR_clear_error(). Клоде отказался, исходя из принципа: если компонент не обработал свои ошибки, это не даёт права сбрасывать вообще все ошибки где-то ещё - программа должна была либо упасть раньше, либо отбросить конкретную ошибку только после проверки, что это безопасно. Затем он выяснил, что этот обходной путь годами считался хорошей практикой: кодовые базы повсюду вызывают ERR_clear_error() перед TLS-операциями, а апстрим OpenSSL сам предлагает так делать.

Главная ошибка: у OpenSSL есть стек ошибок

Корень проблемы в том, что многие авторы не понимают: OpenSSL хранит стек ошибок, а не один слот под ошибку. Отсюда следуют два антипаттерна:

  1. Тотальная очистка. Вызов ERR_clear_error() перед TLS-операцией зачищает всё, что лежит на стеке. Это молча сбрасывает посторонние ошибки, оставленные более ранним кодом, - ошибки, которые могут указывать на реальную проблему. Ошибку, создавшую запись, нужно исправлять (или обрабатывать там, где она произошла), а не выметать позже просто потому, что она мешает.
  2. Проверить верхнюю и сбросить всё. Вызвать операцию OpenSSL, проверить верхнюю ошибку и, если она кажется "не такой уж критичной", сбросить весь стек. Тот же дефект: посторонние ошибки, лежащие глубже, молча выбрасываются.

Оба паттерна превращают состояние "где-то произошла ошибка" в "ошибок нет" - именно такой сбой подрывает доверие к критичному для безопасности коду. Это и есть error-stack-anti-pattern - сброс всего канала ошибок ради подавления одной записи.

Решение

Использовать механизм меток в OpenSSL для изоляции собственного контекста ошибок вместо глобальной очистки: ERR_set_mark() ставит метку, затем ERR_pop_to_mark() откатывает стек до неё. Это guard-обёртка вокруг группы операций OpenSSL, которая удаляет только те ошибки, которые сгенерировали вы, не трогая ничего от другого кода. Призыв Клоде к действию: провести аудит своей кодовой базы на каждый вызов ERR_clear_error() и выяснить, безопасен ли он или относится к плохим паттернам выше; а авторам OpenSSL - перестать поощрять практики, которые "в корне подрывают любое доверие к ПО".

Почему это важно не только для OpenSSL

Общий урок касается гигиены обработки ошибок на границах библиотек: когда библиотека предоставляет общий канал ошибок с состоянием, подход "очищу его, чтобы мой happy path заработал" почти всегда ошибочен, так как отбрасывает информацию, принадлежащую другому коду. Кроме того, это тонкая ошибка в C-стиле обработки ошибок, которую легко допустить: и reviewing-ai-code, и testing-heavy-no-review-workflow отмечают, что подобное трудно отловить исключительно на ревью - антипаттерн распространился именно потому, что локально выглядит корректным и проходит проверки. Сравните с более широкими вопросами корректности и цепочек поставок в supply-chain-security.