Охота на 16-летний баг SQLite с помощью TLA+: уязвима ли dqlite?

В 2026 году SQLite выпустила патч для давно неизвестного бага в Write Ahead Log (WAL), механизме, который позволяет читателям не блокироваться при записи данных. Баг существовал в исходном коде с 2010 года, ровно 16 лет.

Суть проблемы: гонка данных в процессе контрольной точки (checkpoint). WAL использует три переменные в общей памяти, walSalt (счётчик сбросов WAL), mxFrame (текущая длина WAL), nBackfill (количество уже сохранённых страниц). Когда одновременно выполняются операции контрольной точки и записи, может возникнуть момент, когда контрольная точка делает копию устаревшего заголовка WAL. Если в это время другая транзакция сбросит WAL и перезапишет его содержимое, контрольная точка может пропустить целые страницы при сохранении в базу, приводя к повреждению данных.

Исследователи смоделировали это поведение на TLA+ (язык формальной верификации) и быстро нашли последовательность шагов, воспроизводящих баг. Модель подтвердила нарушение инварианта: строка кода в SQLite просто не проверяет, изменилась ли соль WAL между двумя моментами чтения заголовка.

Критически важный вывод для пользователей dqlite: эта уязвимость не влияет на dqlite. Причина, более консервативный подход к синхронизации. dqlite запрещает инициированные пользователем контрольные точки, отключает автоматические и захватывает больше блокировок, чем обычно требует SQLite, предотвращая одновременное выполнение чтения и записи во время контрольной точки. Собственная модель TLA+ команды dqlite подтвердила отсутствие гонки.

Патч (SQLite 3.51.3+) прост, добавляет проверку: сравнивает walSalt до и после чтения заголовка. Если соль изменилась, контрольная точка безопасно прерывается. Реальное влияние уязвимости минимально из-за узкого окна гонки, но обнаружение требовало формальных методов, традиционное тестирование не смогло воспроизвести баг.

Ключевые факты

  • Баг гонки в WAL checkpointing SQLite скрывался 16 лет (2010, 2026), требуя формальной верификации для обнаружения
  • Проблема: контрольная точка может прочитать устаревший заголовок WAL, если одновременно выполняется сброс WAL другой транзакцией, приводя к пропуску страниц при сохранении
  • dqlite защищена благодаря более консервативной синхронизации, запреты на параллельные контрольные точки и более строгие блокировки
  • TLA+ модель формально подтвердила как наличие бага в SQLite, так и отсутствие уязвимости в dqlite
  • Патч SQLite добавляет одну проверку, сравнение walSalt до и после чтения, прерывая контрольную точку при обнаружении сброса

Почему это важно

SQLite широко используется в системах от мобильных до встроенных устройств и серверов. Баг в механизме Write Ahead Log теоретически мог привести к молчаливому повреждению данных. Однако реальный риск минимален из-за узкого окна гонки, требующего специфичных условий синхронизации. Более важен методологический аспект: баг существовал 16 лет, скрываясь от всех методов тестирования и статического анализа, и был открыт только благодаря формальной верификации (TLA+). Это демонстрирует ценность формальных методов для критичных компонентов.

Кому это важно

Прежде всего разработчикам SQLite и дистрибьюторам, которые обновляют версию. Пользователи dqlite (распределённая база на базе SQLite, используемая Canonical в LXD и других проектах) не затронуты благодаря консервативной архитектуре. Разработчики встроенных систем, мобильных приложений и серверных решений, которые опираются на SQLite, должны обновиться на 3.51.3+. Исследователи и архитекторы систем оценят, как формальная верификация обнаружила то, что традиционное тестирование пропустило.

Как это применить

Обновите SQLite до версии 3.51.3 или более новой, если используете его в критичных системах. Если вы используете dqlite, обновление менее критично, но рекомендуется для полноты. Для архитектуры: если вы строите слои поверх SQLite (как dqlite), рассмотрите более консервативные стратегии синхронизации, явное отключение автоматических операций, требующих явного контроля от верхних уровней. Для тестирования: привлекайте формальные методы (TLA+, model checking) для компонентов с многопоточностью и условиями гонки, особенно в системах хранения данных.

Можно ли доверять

Статья авторитетна, написана командой dqlite из Canonical. Используется формальная верификация на TLA+, что обеспечивает высокий уровень доказательства. Исследование проводилось строгим образом: сначала смоделирована уязвимость в SQLite, затем отдельно смоделирована dqlite, чтобы показать защиту. Анализ C-кода SQLite подробен и сопровождается выдержками. Выводы верны и обоснованы: dqlite действительно не подвержена благодаря архитектуре, а не благодаря везению.

Риски и подводные камни

Главный риск, люди могут недооценить важность обновления, если услышат, что реальное влияние бага минимально. Хотя вероятность срабатывания условия гонки низка, необходимо применять обновление. Второй риск, разработчики могут предположить, что обновления TLA+-моделей достаточно для исправления всех багов, забыв, что этот баг потребовал специального исследования. Третий: если вы наследуете архитектуру от SQLite в собственной системе, не копируйте логику WAL дословно, учитывайте опыт Canonical и применяйте более строгую синхронизацию с самого начала.

«По этой причине dqlite блокирует любую инициированную пользователем контрольную точку и отключает автоматические. Кроме того, она захватывает больше блокировок, чем обычно требует SQLite, чтобы предотвратить и чтение, и запись во время контрольной точки.»

— Команда dqlite, Canonical