FlawOpen

flawopen.com/Teardowns/cve-2024-21626-runc-container-breakout-leaky-vessels

● CVE-2024-21626 · CVSS 8.6[1], [5] · Высокий
Исследования · FlawOpen

CVE-2024-21626: как runc исправил выход из контейнера Leaky Vessels

CVE-2024-21626 сочетала внутреннюю утечку файлового дескриптора в runc с отсутствием проверки после chdir. В зависимости от пути запуска контейнер мог начать работу с каталогом в пространстве имён хоста либо получить доступ к файлам хоста через связанный сценарий exec.[1], [2], [3]

💡 Простыми словами (ELI5)

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 защищают этот отдельный путь. Сопровождающие не указывают практический обходной вариант; обновитесь.

Пошаговый сценарий атаки

Шаг 1

Утечка внутреннего дескриптора

При подготовке внутренний дескриптор runc, связанный с пространством имён монтирования хоста, мог остаться доступным runc init.[1], [3]

Шаг 2

Достижимый путь рабочего каталога

Вредоносный образ или сценарий runc exec при определённых условиях могли направить запрошенный cwd за корень контейнера.[1], [2]

Шаг 3

Отсутствие проверки границы

Старый код не проверял итоговый cwd после chdir; связанные варианты использовали обработку путей execve для перезаписи бинарного файла хоста.[2], [3]

Шаг 4

Воздействие и условия

В уведомлении описаны доступ к файловой системе и контроль хоста в четырёх вариантах. Для атак через образ привилегированный пользователь должен запустить недоверенный образ. Для атаки 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)
}

Чек-лист по защите системы для инженеров

Источники