flawopen.com/Teardowns/cve-2024-21626-runc-container-breakout-leaky-vessels
CVE-2024-21626: как runc исправил выход из контейнера Leaky Vessels
CVE-2024-21626 сочетала внутреннюю утечку файлового дескриптора в runc с отсутствием проверки после chdir. В зависимости от пути запуска контейнер мог начать работу с каталогом в пространстве имён хоста либо получить доступ к файлам хоста через связанный сценарий exec.[1], [2], [3]
runc подготавливает изолированную комнату для контейнера. Внутренний дескриптор мог направить выбранный рабочий каталог за пределы этой комнаты; финальная проверка границы и закрытие внутренних дескрипторов блокируют такой переход.[1], [2]
Ключевые понятия и термины
runc- Среда OCI для создания и запуска контейнеров.[1]
Внутренний файловый дескриптор- Дескриптор ОС для открытого файла или каталога. Если он остаётся открыт при запуске дочернего процесса, доступ может перейти через границу процессов.[1], [3]
cwd- Рабочий каталог процесса. В Linux Getwd может вернуть ENOENT, если каталог находится за корнем пространства имён монтирования.[1], [2]
Выход из контейнера- Путь, по которому процесс контейнера достигает ресурсов хоста за пределами изоляции.[1]
Связи со стандартами
- Ссылка CWE для слабого места, описанного в этом руководстве: CWE-403
Анализ первопричины
До runc 1.1.12 внутренние файловые дескрипторы могли утекать в runc init. Они оставались внутренними для runc init и не наследовались процессом контейнера напрямую. В атаках 1 и 2 заданный или выбранный рабочий каталог мог разрешиться через дескриптор пространства имён монтирования хоста; после chdir runc не проверял, что итоговый cwd находится внутри корня контейнера. В отдельных вариантах обработка путей execve использовалась для перезаписи бинарного файла хоста. В версии 1.1.12 добавлены четыре уровня защиты: проверка cwd через поведение Linux Getwd/ENOENT, закрытие внутренних дескрипторов непосредственно перед execve, устранение конкретных утечек и установка close-on-exec для дескрипторов, кроме stdio, перед запуском runc init.[1], [2], [3], [4]
CVE-2024-21626: варианты атак и защита в runc 1.1.12
| Вариант из уведомления | Необходимые условия и граница | Соответствующее исправление или временная мера |
|---|---|---|
Атака 1 — вредоносный образ через runc run | Привилегированного пользователя нужно убедить запустить недоверенный образ. Настроенный рабочий каталог может разрешиться за пределами пространства имён монтирования контейнера. | Проверка unix.Getwd() после chdir завершается ошибкой, если cwd находится вне корня пространства имён. В качестве временной меры разрешайте для недоверенных образов только cwd /. |
Атака 2 — процесс контейнера и runc exec | Процесс внутри контейнера должен предугадать, какой cwd, отличный от /, выберет администратор в runc exec --cwd, и подготовить соответствующий путь перед запуском. | Та же проверка cwd после chdir блокирует запуск за пределами контейнера. До обновления ограничьте --cwd значением /. |
| Атака 3a — перезапись бинарного файла хоста при запуске образа | Вариант атаки 1 по-прежнему требует, чтобы привилегированный пользователь запустил вредоносный образ; путь пересекает границу execve к бинарному файлу хоста. | Закрывайте внутренние дескрипторы runc непосредственно перед execve, устраняйте известные утечки и устанавливайте O_CLOEXEC. Обновитесь до runc 1.1.12 или версии с исправлением поставщика. |
Атака 3b — перезапись бинарного файла хоста через runc exec | Этот вариант развивает атаку 2 и начинается с вредоносного процесса в работающем контейнере, на который может повлиять атакующий; при этом администратор с повышенными правами всё равно должен выполнить обычную операцию runc exec для этого контейнера. В уведомлении сказано, что взаимодействия пользователя требуется меньше, чем для пути через образ. | Закрытие перед execve и close-on-exec защищают этот отдельный путь. Сопровождающие не указывают практический обходной вариант; обновитесь. |
Пошаговый сценарий атаки
Воздействие и условия
В уведомлении описаны доступ к файловой системе и контроль хоста в четырёх вариантах. Для атак через образ привилегированный пользователь должен запустить недоверенный образ. Для атаки 3b администратор по-прежнему должен выполнить обычную операцию runc exec для контейнера, на который может повлиять атакующий.[1]
Исходный код: Уязвимый vs Защищённый вариант
// Упрощённое поведение до исправления; не дословный код runc
if err := os.Chdir(config.Cwd); err != nil {
return err
}
// Не хватало проверки, что итоговый cwd находится внутри корня namespace
// Проверка границы из runc 1.1.12 (фрагмент upstream)
if wd, err := unix.Getwd(); errors.Is(err, unix.ENOENT) {
return errors.New("cwd находится вне корня пространства имён монтирования контейнера")
} else if err != nil {
return fmt.Errorf("не удалось проверить безопасность cwd: %w", err)
} else if !filepath.IsAbs(wd) {
return fmt.Errorf("cwd не является абсолютным: %q", wd)
}
Чек-лист по защите системы для инженеров
- ✓Обновите runc до 1.1.12 или более новой версии, поддерживаемой поставщиком; проверьте фактическую версию в образе каждого узла.[1], [4]
- ✓Считайте запуск недоверенного образа выполнением кода; ограничьте, кто может запускать такие образы через Docker, Kubernetes и конвейеры сборки.[1]
- ✓В runtime-проектах проверяйте cwd после chdir с помощью поддерживаемой платформой проверки корня пространства имён и безопасно завершайте операцию при ошибке.[1], [2]
- ✓Устанавливайте O_CLOEXEC для внутренних дескрипторов и закрывайте дескрипторы, кроме stdio, в последней безопасной точке перед exec с учётом дескрипторов runtime.[1], [3]
- ✓Не считайте пользовательские пространства имён заменой исправлению runc; при поддержке используйте их как дополнительный уровень изоляции.[1]