В мире системного программного обеспечения иногда возникают ошибки, которые очень сложно воспроизвести, понять и исправить. Такие баги получают шуточное прозвище «Higgs-багсон», по аналогии с знаменитой частицей Хиггса в физике — теоретически предсказанной в 1960-х, но обнаруженной только в 2013 году. Одна из таких загадочных ошибок была обнаружена в ядре операционной системы Linux, в среде работы протокола сетевой файловой системы NFS с аутентификацией Kerberos. История ее поиска и устранения представляет собой прекрасный пример глубокой инженерной работы, тонкостей сетевого взаимодействия и многослойных механизмов безопасности. В этом материале мы подробно рассмотрим, что стоит за этим багом, почему он возник, как удалось его воспроизвести и исправить, а также какую информацию об устройстве ядра и протоколов раскрыла эта история.
На наших серверах была развернута критически важная система хранения и передачи данных торговой активности. Основной файловой системой был NFS версии 3, широко применяемый для удаленного доступа к файлам в распределенных средах. Одной из важных особенностей была аутентификация через Kerberos — мощный протокол с криптографической проверкой подлинности пользователей. Хотя в NFSv3 стандартно нет серьезных средств защиты (сервер просто доверяет клиенту, если тот соединяется с привилегированного порта и указывает пользователя), Kerberos был подключен для обеспечения безопасности. Проблема проявлялась крайне редко и непредсказуемо.
При копировании больших файлов пользователи иногда сталкивались с ошибкой -EACCES (отказ в доступе), несмотря на корректно настроенные права на файловой системе. Поскольку такие ошибки означали потерю прогресса в копировании и потенциальные риски нарушения целостности данных, баг вызывал серьезную тревогу. Причем ошибка не имела явной закономерности: идентичные задачи, запущенные одновременно, могли закончиться как успехом, так и неудачей без видимых причин. Был замечен важный сигнал — при отключении Kerberos в тестовой среде ошибка прекращалась. Это означало, что корень проблемы связан с аутентификацией и обработкой Kerberos-учетных данных.
В обычных приложениях Kerberos-открытия происходит в пространстве пользователей через библиотеку libkrb5. Однако NFS работает иначе: приложения просто вызывают системные вызовы для файлового ввода-вывода, не зная о Kerberos. За работу с аутентификацией отвечает daemon rpc_gssd, работающий в пространстве пользователей под root. Этот демон взаимодействует с ядром через специальную файловую систему rpc_pipefs. Для воспроизведения проблемы был организован ряд тестов.
Изначально попытки копировать большие файлы по 200 Гб с одной тестовой NFS-машины на другую не привели к ошибке. Тогда автор разработал свой хитрый прием — FUSE-файловую систему, которая генерировала псевдослучайные данные на лету, позволяя эмулировать большие файлы в оперативной памяти без значительных затрат ресурсов. Такой подход позволил создать множество параллельных задач копирования с минимальными накладными расходами. Для того чтобы получить больше информации из ядра в момент возникновения ошибки, было применено средство eBPF — расширенный фильтр, позволяющий встраивать дополнительный код и трассировать выполнение функций ядра. С помощью инструмента bpftrace была собрана статистика по функциям, возвращающим -EACCES, и получены стек-трейсы, что позволило локализовать место возникновения ошибки.
Ошибка оказалась связанной с функцией gss_cred_init, отвечающей за инициализацию Kerberos-учетных данных на уровне ядра, где иногда возвращалось значение EACCES. Анализ сетевого трафика с помощью Wireshark показал интересный момент: многие пакеты NFS были повторно переданы из-за высокой задержки и перегрузки маленького тестового сервера. В протоколе NFS и Kerberos очень важны последовательности и идентификаторы пакетов — XID (transaction identifier) и GSS sequence number (номер последовательности в контексте безопасности). Эти значения используются для отслеживания запросов и ответов с целью проверки достоверности. Самая суть бага заключалась в том, что при повторной передаче запросов с одинаковым XID ядро Linux обновляло внутренний счетчик GSS sequence number.
Однако сервер мог отвечать на старый запрос с предыдущим номером последовательности, что вызывало расхождение контрольной суммы HMAC. В итоге проверка подписи завершалась неудачей, и возвращался отказ в доступе. Эта ситуация становилась единой цепочкой проблем: каждый отказ приводил к повторной попытке передачи, создавая почти гарантированный непрерывный цикл ошибок. Интересно, что спецификация RFC2203 прямо описывает этот случай и рекомендует реализовать в клиенте кэш для хранения нескольких номеров последовательностей и соответствующих контрольных сумм в ожидании ответов. В ядре Linux до этого кейс не был реализован, тогда как система FreeBSD уже имела такую функциональность.
Отсутствие данного кэша и привело к описанной проблеме в Linux. После установления корня проблемы было подготовлено сразу два исправления. Первое — внедрение кэша номеров последовательностей, позволяющее при получении ответа сравнивать контрольные суммы по всем возможным номерам и находить соответствие даже для старых запросов. Второе — изменение логики повторных передач: при обнаружении ошибки контрольной суммы не инициировать немедленную повторную передачу, а оставить эту функцию стандартному таймауту. Такое поведение предотвращает самоподдерживающийся цикл ошибок и соответствует здравой логике — ошибка подписи может свидетельствовать о возможных сетевых проблемах или атаке, поэтому стоит аккуратно обрабатывать такие случаи.
Для отработки воспроизводимого сценария была реализована технология имитации задержек пакетов в сети. Используя механизм NFQUEUE ядра Linux, трафик фильтровался через пользовательский скрипт, который вручную задерживал пакеты, эмулируя высокую задержку и заставляя ошибку воспроизводиться стабильно. Реализация и тестирование патчей заслуживают отдельного упоминания: благодаря тесному взаимодействию с командой разработчиков Linux исправления быстро были внедрены и вошли в релиз ядра версии 6.16, что демонстрирует зрелость процесса поддержки и открытость проекта к таким сложным случаям. История с «Higgs-багсоном» в Linux — это прекрасный пример того, как даже маловероятные ошибки с глубокими инженерными корнями могут раскрыть важные особенности работы системных протоколов и ядра.
Она также показывает, как современные инструменты — от FUSE до eBPF и Wireshark — помогают инженерам без стресса анализировать и воспроизводить сложные сетевые и системные баги. К тому же, данная работа демонстрирует важность чтения и понимания официальных спецификаций, чтобы избежать повторных ошибок и обеспечить должный уровень надежности критических систем. Для системных администраторов и разработчиков, работающих с NFS и Kerberos, этот кейс напоминает о важности мониторинга, тщательного тестирования под нагрузкой и использования последних версий программного обеспечения. Такая подготовленность помогает вовремя обнаружить и устранить даже самые скрытые и редкие проблемы, предотвращая их влияние на стабильность и безопасность инфраструктуры. В конечном итоге данная история не просто о баге.
Это рассказ о кропотливом процессе анализа, инженерной смекалке, применении множества современных технологий для решения реальной задачи и о том, как открытое сообщество слаженно работает над улучшением ядра Linux для всех пользователей в мире.