Разработчик открывает ноутбук утром, запускает привычный docker compose up, и вместо контейнера получает зависшую виртуальную машину Docker Desktop, которую спасает только полная перезагрузка сервиса. Проблема не в конкретном образе и не в кривом Dockerfile, а в том, как модуль grpcfuse обрабатывает вложенные каталоги на bind-mounted папке хоста. Разберём, что именно почини Docker в версии 29.7.2, почему баг с рекурсией задевает буквально всех, кто монтирует локальные папки в контейнер, и как обновиться без потери данных.
Что случилось с grpcfuse и почему падала вся виртуальная машина
Уязвимость получила номер CVE-2026-8936 и относится к категории паники виртуальной машины из-за неограниченной рекурсии. Механизм срабатывания довольно приземлённый: контейнер создаёт глубоко вложенные каталоги внутри папки хоста, подключённой через bind mount, а затем происходит событие инвалидации dentry, то есть системного объекта, который описывает связь имени файла с его инодом в файловой системе. В этот момент модуль grpcfuse, отвечающий за проброс файловой системы между виртуальной машиной Docker Desktop и хостовой ОС, начинает рекурсивно обрабатывать цепочку вложенных директорий без верхнего предела на глубину. Результат предсказуем: стек переполняется, ядро внутри виртуальной машины паникует, и падает не отдельный контейнер, а вся VM целиком, вместе со всеми остальными запущенными сервисами.
Это принципиально отличает баг от типичных проблем с отдельными контейнерами. Обычно сбой одного процесса внутри контейнера не выходит за пределы его собственного namespace, и соседние контейнеры продолжают работать. Здесь же точка отказа находится на уровне гипервизора и общей файловой подсистемы, поэтому пострадать может весь стек локальной разработки: база данных в соседнем контейнере, редис, очередь сообщений, всё, что крутилось на этой же виртуальной машине в момент паники.
Наиболее уязвимая категория пользователей это разработчики на macOS и Linux с активным bind-mounting, то есть с типичной практикой монтирования исходного кода проекта прямо в контейнер для горячей перезагрузки при разработке. Именно такой паттерн создаёт предпосылки для срабатывания бага: если приложение или тестовый скрипт внутри контейнера генерирует вложенную структуру каталогов достаточной глубины, например при рекурсивном создании тестовых фикстур или временных файлов сборки, риск паники VM растёт.
Какую версию нужно поставить и что было в предыдущих патчах ветки 29
Актуальный релиз на 24-25 августа это Docker Engine 29.7.2, вышедший 5 августа 2026 года. Ему предшествовали версии 29.7.0 и 29.7.1 в той же ветке, которые закрывали другую проблему, CVE-2026-17106, связанную с обработкой архивов. Эта уязвимость представляла собой флаг выхода за пределы целевой директории в команде docker container cp, то есть теоретически позволяла записать файлы за границами ожидаемого пути назначения при распаковке архива. Исправление пришло через обновление зависимости github.com/moby/go-archive до версии 0.3.0.
Сама 29.7.2 в первую очередь содержит фикс паники демона при повторной передаче одной и той же переменной окружения в командах docker service create и docker service update. Помимо этого в релиз попали пакетные обновления компонентов: рантайм Go обновлён до версии 1.26.5, BuildKit до 0.32.0, containerd в статических бинарниках до 2.3.3, а runc до 1.4.3. Важно понимать разделение ответственности: перечисленные исправления относятся к Docker Engine, тогда как патч CVE-2026-8936 для grpcfuse пришёл именно в Docker Desktop, начиная с версии 4.76.0, поскольку сам модуль grpcfuse существует только в контексте виртуальной машины Docker Desktop на macOS и Windows с WSL2, а не в чистом Docker Engine на Linux-хосте без прослойки виртуализации.
Чеклист безопасного апгрейда Docker Engine 29.x
Перед обновлением стоит пройти по порядку несколько шагов, чтобы не потерять рабочее окружение и не столкнуться с сюрпризами после рестарта демона.
- Сделать резервную копию всех Compose-файлов проекта вместе с файлами переменных окружения и любыми конфигурациями оверрайдов, включая docker-compose.override.yml;
- Экспортировать секреты, если используется Docker Swarm, командой docker secret inspect для каждого секрета с последующим сохранением значений в защищённое хранилище, поскольку сами секреты нельзя выгрузить напрямую в открытом виде;
- Снять список активных именованных томов через docker volume ls и убедиться, что критичные данные, например состояние баз данных, синхронизированы или задублированы перед перезапуском демона;
- Проверить текущую версию через docker version и docker info, зафиксировать список запущенных контейнеров командой docker ps -a, чтобы после обновления сверить, что ничего не потерялось;
- Обновить сам пакет docker-ce, docker-ce-cli и containerd.io через штатный менеджер пакетов дистрибутива без принудительного пересоздания образов и без docker system prune, поскольку эта команда удалит неиспользуемые образы и тома без разбора;
- Перезапустить демон командой systemctl restart docker на Linux либо через штатный рестарт Docker Desktop, после чего повторно выполнить docker ps -a и сверить список контейнеров с зафиксированным на шаге четыре.
Такой порядок минимизирует риск того, что апгрейд затронет что-то кроме самого демона: контейнеры и образы, собранные под предыдущую версию Engine, в подавляющем большинстве случаев продолжают работать без пересборки, поскольку формат образов и рантайм-интерфейс OCI остаются обратно совместимыми в пределах минорных обновлений ветки 29.
Почему стоит отдельно проверить логи на признаки drainBody
Апрельский цикл патчей этого года запомнился отдельной серьёзной уязвимостью, CVE-2026-34040, с оценкой критичности 8.8 по шкале CVSS. Это была брешь в обходе плагинов авторизации, известных как AuthZ, которые используются в корпоративных и мультитенантных развёртываниях для тонкого контроля над тем, какой пользователь какие вызовы API может выполнять. Проблема была регрессией более ранней уязвимости 2024 года и заключалась в том, что запрос к Docker API с телом больше одного мегабайта заставлял middleware демона отбрасывать это тело перед передачей плагину авторизации. Плагин видел пустой запрос, не находил причин для отказа и одобрял операцию, тогда как сам демон Docker в это время выполнял исходную команду целиком, включая тело запроса, например создание привилегированного контейнера с полным доступом к файловой системе хоста.
Уязвимость была закрыта в версии 29.3.1 через удаление функции drainBody, которая молча отбрасывала тела запросов сверх лимита, а также через повышение предельного размера тела запроса до 4 мегабайт с полным отклонением запросов, превышающих этот порог, вместо тихой обрезки. Для тех, кто обновлялся постепенно и мог пропустить промежуточные версии между 29.3.1 и текущей 29.7.2, стоит отдельно проверить журналы демона на предмет исторических следов эксплуатации бага до момента патча. Сделать это можно командой journalctl -u docker с фильтром по строке "Request body is larger than", которая фиксировалась в логах именно в момент срабатывания механизма drainBody. Если такие записи находятся в логах за период до обновления до версии 29.3.1 или позднее, а в инфраструктуре при этом использовались AuthZ-плагины, стоит провести отдельный аудит на предмет создания привилегированных контейнеров в тот же временной промежуток.
Важная деталь для тех, кто использует Kubernetes: кластеры на containerd или CRI-O, которые являются рантаймом по умолчанию начиная с Kubernetes версии 1.24, не подвержены этой уязвимости в принципе, поскольку сам фреймворк AuthZ-плагинов существует только в архитектуре классического Docker Engine и отсутствует в этих рантаймах. Podman по той же причине тоже не затронут.
Как отличить панику grpcfuse от обычного зависания Docker Desktop
Практическая сложность бага CVE-2026-8936 в том, что симптом на первый взгляд неотличим от банального зависания демона из-за нехватки ресурсов или конфликта портов. Отличительный признак именно этой паники это полная неотзывчивость виртуальной машины Docker Desktop при том, что сам хост продолжает работать нормально: обычные процессы на macOS или Linux не тормозят, но любая команда docker, включая простой docker ps, зависает без ответа или завершается по таймауту. Стандартный рестарт через интерфейс Docker Desktop в момент активной паники ядра внутри VM часто тоже не срабатывает с первого раза, и требуется полная остановка процесса Docker Desktop через диспетчер задач или Activity Monitor с последующим повторным запуском.
Если такие зависания повторяются регулярно именно в момент операций с большим количеством вложенных директорий, например при развёртывании node_modules с глубокой структурой пакетов через bind mount или при работе с монорепозиториями, где build-система создаёт множество промежуточных временных папок, это весомый повод проверить версию Docker Desktop и обновиться до 4.76.0 или выше без промедления, поскольку рекурсия в grpcfuse срабатывает детерминированно при достаточной глубине вложенности, а не случайным образом.
Практические рекомендации для команд с активным bind-mounting
Помимо непосредственного обновления Docker Desktop до версии, закрывающей CVE-2026-8936, командам с интенсивным использованием bind mount стоит пересмотреть паттерны работы с временными файлами внутри контейнеров. Если сборочные скрипты или тестовые фреймворки создают глубоко вложенные структуры каталогов на смонтированной хостовой папке, разумно перенести такие операции во внутренний том контейнера, не связанный с хостовой файловой системой напрямую, а результат синхронизировать отдельным шагом уже после завершения генерации файлов. Это снижает нагрузку на файловую подсистему прослойки виртуализации независимо от того, устранён конкретный баг с рекурсией или нет, поскольку сама природа проброса файловой системы через grpcfuse или аналогичные механизмы остаётся потенциально уязвимой к похожим сценариям в будущем.
Также стоит закладывать в CI и локальные скрипты разработки проверку версии Docker перед стартом сборки, например через простой скрипт, сверяющий вывод docker version с минимально допустимой версией движка и Docker Desktop, чтобы исключить ситуацию, когда часть команды продолжает работать на уязвимой версии, а часть уже обновилась, что затрудняет диагностику подобных проблем при появлении отчётов о нестабильности от разных членов команды.
Числовая сторона проблемы: сколько уровней вложенности критичны
Для понимания масштаба риска полезно прикинуть, на какой глубине вложенности каталогов вообще начинаются проблемы у файловых систем и прослоек виртуализации в принципе. Классические лимиты POSIX ограничивают длину полного пути значением PATH_MAX, которое на большинстве Linux-дистрибутивов равно 4096 байтам, а длину одного компонента имени файла значением NAME_MAX, обычно 255 байтам. При средней длине имени каталога в 10-15 символов теоретический предел вложенности до упора в PATH_MAX составляет порядка 250-300 уровней. Именно в этом диапазоне и выше начинают проявляться патологии у файловых прослоек, которые изначально не рассчитывались на такую глубину рекурсии при обходе дерева каталогов, включая grpcfuse.
На практике реальные сценарии, провоцирующие баг, встречаются заметно раньше теоретического предела. Инструменты вроде npm и yarn при установке пакетов с глубокими цепочками зависимостей исторически создавали структуры node_modules с вложенностью в 15-30 уровней ещё до перехода на плоскую схему разрешения зависимостей, а некоторые сборочные системы для монорепозиториев генерируют временные директории кеша с глубиной 20-40 уровней при активном параллельном запуске задач. Этого достаточно, чтобы при неудачном стечении обстоятельств, а именно совпадении момента создания глубокой структуры с событием инвалидации dentry, сработала неограниченная рекурсия в обработчике.
Ещё одна цифра для контекста: время до полной паники виртуальной машины после срабатывания бага по независимым отчётам сообщества разработчиков занимало от нескольких секунд до одной-двух минут в зависимости от того, сколько ресурсов было выделено виртуальной машине Docker Desktop в настройках. При лимите оперативной памяти VM в 2-4 гигабайта, что типично для дефолтных настроек на ноутбуках с ограниченным объёмом ОЗУ, стек переполнялся быстрее из-за более жёстких ограничений на глубину рекурсивных вызовов внутри выделенного адресного пространства.
Как оценить риск для конкретного проекта заранее
Прежде чем полагаться исключительно на факт обновления Docker Desktop, стоит провести быструю ревизию собственных Dockerfile и Compose-конфигураций на предмет операций, потенциально создающих глубокую вложенность на смонтированных томах. Практический способ это временно добавить в сборочный процесс подсчёт максимальной глубины каталогов на смонтированном пути командой вида find /path/to/mount -type d | awk -F/ '{print NF-1}' | sort -rn | head -n 1, которая выведет число уровней вложенности самого глубокого найденного каталога. Если результат превышает условную отметку в 50-60 уровней, стоит внимательнее отнестись к обновлению и не откладывать патч даже при отсутствии видимых проблем на текущий момент, поскольку рекурсия срабатывает не при каждом обращении к дереву каталогов, а именно в момент конкретного события инвалидации dentry, которое может не происходить месяцами при спокойном режиме работы и внезапно участиться при смене паттерна нагрузки, например при переходе на более агрессивное кеширование в CI-раннере, монтированном как volume.
Итоговая картина по патчам последнего цикла
Версия 29.7.2 в ветке Docker Engine 29 закрывает узкую, но неприятную панику демона при повторных переменных окружения в сервисных командах и подтягивает свежие версии ключевых компонентов рантайма, включая Go, BuildKit, containerd и runc. Отдельно и параллельно в Docker Desktop 4.76.0 устранена куда более заметная для повседневной разработки проблема, паника виртуальной машины из-за неограниченной рекурсии в grpcfuse при работе с глубоко вложенными каталогами на bind mount. Эти два патча решают разные задачи и находятся в разных продуктах, но оба напрямую влияют на стабильность локальной разработки и заслуживают безотлагательного обновления, особенно в связке с ретроспективной проверкой логов на признаки куда более серьёзной прошлой уязвимости с обходом AuthZ-плагинов через механизм drainBody.